Ruby : implement an acts_as - ruby-on-rails

I want to implement an acts_as.
This would be something like this :
Module Organisable
def acts_as_organisable
send :include, InstanceMethods
has_many ...
before_destroy ...
??
end
module InstanceMethods
def method1
end
def method2
end
end
end
And it would be used like :
def myClass < ActiveRecord::Base
acts_as_organisable
end
I tried it but it does'nt work. If it's possible i'd like pass parameters too (acts_as_organisable(param) ).
This module must be placed in another engine.
For the files organisation i think to :
MyEngine
Models
MyEngine
MyEngineClass
Organisable
Organisable
But not sure.

You need add your module to lazy load hook. Like this:
ActiveSupport.on_load(:active_record) do
include Organisable
end
For more details see Understanding Ruby and Rails: Lazy load hooks

Related

How to override class_method in rails model concern

How does one override a class method defined in a model concern?
This is a bit tricky since you’re not really overriding a class method right? Because it’s using the concern api of definining class methods in the class_methods block.
so say I have a concern that looks like this:
module MyConcern
extend ActiveSupport::Concern
class_methods do
def do_something
#some code
end
end
end
In model.. how would I override that method so that I could call it like we do with super when using inheritance? So in my model I’d like to go:
def self.do_something
#call module do_something
end
?
If you've included MyConcern in the model that defines self.do_something, you should just be able to use super:
module MyConcern
extend ActiveSupport::Concern
class_methods do
def do_something
puts "I'm do_something in the concern"
end
end
end
class UsesMyConcern < ActiveRecord::Base
include MyConcern
def self.do_something
super
end
end
UsesMyConcern.do_something
# => "I'm do_something in the concern"
If you haven't or don't want to include MyConcern in the model and you want to invoke do_something on the module without creating any intermediary objects, you can change your model to:
class UsesMyConcern < ActiveRecord::Base
def self.do_something
MyConcern::ClassMethods.instance_method(:do_something).bind(self).call
end
end
UsesMyConcern.do_something
# => "I'm do_something in the concern"
ActiveSupport::Concern.class_methods defines a ClassMethods module in the concern if there isn't one already, and that's where we can find the do_something method.
Why not simply call the module's method: MyConcern.do_something?
I'm not sure if there's an easy of doing super for modules (though I can see why that may be useful).
The next best solution could be doing something like calling #included_modules and manually iterating with #responds_to?:
def self.do_something
self.super_module(__method__)
end
def self.super_module(method)
self.included_modules.find { |m| m.responds_to? method }.public_send(method)
end
The old way using alias_method_chain: https://ernie.io/2011/02/03/when-to-use-alias_method_chain/
The new way (requires > ruby 2.0.0) you really should use this, as there will be a DEPRECATION WARNING when using it in rails 5.0:
http://paweljaniak.co.za/2014/09/30/understanding-ruby-module-prepend-and-include/

How to create several class and instance methods in Rails model

I am doing this
def self.a
...
end
def a
class.a
end
But for several method I would have to replicate instance methods.
I was thinking in a module
module A
def a; end
end
And then use it in my model like this:
extend A
include A
But I am not sure where to put it according Rails folder structure, or even if to put module inside my model.
Any advice?
Opt 1 - Extend Self
If you want to have all of your instance methods as class methods as well, you can simply use extend self
class A
def foo
...
end
def bar
...
end
extend self
end
This would allow you to call foo as either A.foo or A.new.foo.
Opt 2 - Included Module
If you only want some of your instance methods to be available as class methods, then you should create a module as you proposed. You could put that module in the lib/ folder and either require it or add lib to your autoload paths.
You can also include the module directly in the class like so:
class A
def not_shared
...
end
module SharedMethods
def foo
...
end
def bar
...
end
end
extend SharedMethods
include SharedMethods
end
Opt 3 - Delegate
If you're using Rails (or just ActiveSupport), you can also make use of the delegate method it adds to class/module.
class A
def not_shared
...
end
def foo
...
end
def bar
...
end
delegate :foo, :bar, to: 'self.class'
end
See here for details:
http://rdoc.info/docs/rails/3.0.0/Module:delegate
You you want to create a module, like shared_methods.rb, you'd put the file in the /lib directory.
You would include the module like this:
class NewClass
include SharedMethods
...
end

Rails: dynamically define class method based on parent class name within module/concern

I want to dynamically generate a class method in a Mixin, based on the class name that include this Mixin.
Here is my current code:
module MyModule
extend ActiveSupport::Concern
# def some_methods
# ...
# end
module ClassMethods
# Here is where I'm stuck...
define_method "#{self.name.downcase}_status" do
# do something...
end
end
end
class MyClass < ActiveRecord::Base
include MyModule
end
# What I'm trying to achieve:
MyClass.myclass_status
But this give me the following method name:
MyClass.mymodule::classmethods_status
Getting the base class name inside the method definition works (self, self.name...) but I can't make it works for the method name...
So far, I've tried
define_method "#{self}"
define_method "#{self.name"
define_method "#{self.class}"
define_method "#{self.class.name}"
define_method "#{self.model_name}"
define_method "#{self.parent.name}"
But none of this seems to do the trick :/
Is there any way I can retrieve the base class name (not sure what to call the class that include my module). I've been struggling with this problem for hours now and I can't seem to figure out a clean solution :(
Thanks!
I found a clean solution: using define_singleton_method (available in ruby v1.9.3)
module MyModule
extend ActiveSupport::Concern
included do
define_singleton_method "#{self.name}_status" do
# do stuff
end
end
# def some_methods
# ...
# end
module ClassMethods
# Not needed anymore!
end
end
You can't do it like that - at this point it is not yet known which class (or classes) are including the module.
If you define a self.included method it will be called each time the module is included and the thing doing the including will be passed as an argument. Alternatively since you are using AS::Concern you can do
included do
#code here is executed in the context of the including class
end
You can do something like this:
module MyModule
def self.included(base)
(class << base; self; end).send(:define_method, "#{base.name.downcase}_status") do
puts "Hey!"
end
base.extend(ClassMethods)
end
module ClassMethods
def other_method
puts "Hi!"
end
end
end
class MyClass
include MyModule
end
MyClass.myclass_status
MyClass.other_method
Works for extend:
module MyModule
def self.extended who
define_method "#{who.name.downcase}_status" do
p "Inside"
end
end
end
class MyClass
extend MyModule
end
MyClass.myclass_status

Accessing other methods in a Ruby module

I am writing my first Rails gem, which adds a method to ActiveRecord. I can't seem to figure out a simple way to call other methods from within the method I am adding to ActiveRecord. Is there a pattern for this I should be using?
module MyModule
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def my_class_method
# This doesn't work
some_utility_method
end
end
def some_utility_method
# Do something useful
end
end
ActiveRecord::Base.send(:include, MyModule)
Once you've included MyModule, ActiveRecord::Base will have my_class_method as a class method (equivalently, an instance method of the Class object ActiveRecord::Base), and some_utility_method as an instance method.
So, inside my_class_method, self is the Class ActiveRecord::Base, not an instance of that class; it does not have some_utility_method as an available method
Edit:
If you want a utility method private to the Module, you could do it like this:
module MyModule
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def my_class_method
# This doesn't work
MyModule::some_utility_method
end
end
def self.some_utility_method
# Do something useful
end
end
ActiveRecord::Base.send(:include, MyModule)

Virtual attributes in plugin

I need some help with virtual attributes. This code works fine but how do I use it inside a plugin. The goal is to add this methods to all classes that uses the plugin.
class Article < ActiveRecord::Base
attr_accessor :title, :permalink
def title
if #title
#title
elsif self.page
self.page.title
else
""
end
end
def permalink
if #permalink
#permalink
elsif self.page
self.page.permalink
else
""
end
end
end
Thanks
You can run the plugin generator to get started.
script/generate plugin acts_as_page
You can then add a module which defines acts_as_page and extends it into all models.
# in plugins/acts_as_page/lib/acts_as_page.rb
module ActsAsPage
def acts_as_page
# ...
end
end
# in plugins/acts_as_page/init.rb
class ActiveRecord::Base
extend ActsAsPage
end
This way the acts_as_page method is available as a class method to all models and you can define any behavior into there. You could do something like this...
module ActsAsPage
def acts_as_page
attr_writer :title, :permalink
include Behavior
end
module Behavior
def title
# ...
end
def permalink
# ...
end
end
end
And then when you call acts_as_page in the model...
class Article < ActiveRecord::Base
acts_as_page
end
It will define the attributes and add the methods. If you need things to be a bit more dynamic (such as if you want the acts_as_page method to take arguments which changes the behavior) try out the solution I present in this Railscasts episode.
It appears that you want a Module for this
# my_methods.rb
module MyMethods
def my_method_a
"Hello"
end
end
The you want to include it into the classes you want to use it for.
class MyClass < ActiveRecord::Base
include MyMethods
end
> m = MyClass.new
> m.my_method_a
=> "Hello!"
Take a look here for more information on mixing in modules. You can put the module wherever in a plugin if you like, just ensure its named correctly so Rails can find it.
Create a module structure like YourPlugin::InstanceMethods and include it this module like this:
module YourPlugin
module InstanceMethods
# your methods
end
end
ActiveRecord::Base.__send__(:include, YourPlugin::InstanceMethods)
You have to use __send__ to make your code Ruby 1.9 compatible. The __send__ line is usually placed at the init.rb file on your plugin root directory.

Resources