ActiveSupport::Concern ClassMethod not being attached to host class - ruby-on-rails

I have this in models/concerns/sluggable.rb:
module Sluggable
extend ActiveSupport::Concern
module ClassMethods
def sluggify(str, type)
# ...
end
end
end
And I've included it in models/slug.rb:
class Slug < ActiveRecord::Base
include Sluggable
# ...
before_save :create_slug
private
def create_slug
self.slug = Slug.sluggify(self.title, 'item')
end
end
I'm under the impression that the sluggify method from the module should now be available as a class method of Slug but it appears to not be the case. I get an undefined method: sluggify error when trying to insert the model into the database.
The Sluggable module is available from the Rails console. And I can confirm that my code is correct:
module Sluggable
def sluggify(str, type)
# ...
end
end
class SluggableTest
extend Sluggable
end
slug = SluggableTest.sluggify('string to sluggify', 'item')
So why can't I get it to work as a concern inside the model?

Try adding the include Sluggable on the top of the Slug class like this:
class Slug < ActiveRecord::Base
include Sluggable
# ...
before_save :create_slug
private
def create_slug
self.slug = Slug.sluggify(self.title, 'item')
end
end
Because, you are right. sluggify method will be attached to the Slug class once you include the Sluggable module in the Slug class and will be available as a class method of the Slug class. I think, because you don't have include Sluggable on top of your Slug class, the sluggify method is being called before the Sluggable module is included into the Slug class and hence it's getting and undefined method error, which makes sense!

you define your method inside nested module, but doesn't include it or extend it. Following is the right template:
module Sluggable
module ClassMethods
end
module InstanceMethods
end
def self.included(receiver)
receiver.extend ClassMethods
receiver.send :include, InstanceMethods
end
end

Related

Using ActiveSupport::Concern and create accessors and validations dynamically

I want to use ActiveSupport::Concern in order to extend the ActiveRecord::Base funcionality, dynamically.
So I have a class (in app/models/foo.rb)
class Foo < ActiveRecord::Base
end
and in lib/activ_record_extention.rb I have
module ActiveRecordExtension
extend ActiveSupport::Concern
module ClassMethods
c= atrr_name
attr_accessible c.to_sym
end
end
ActiveRecord::Base.send(:include, ActiveRecordExtension)
But when I run the server I get :
undefined method `attr_accessible' for ActiveRecord Extension:Module (NoMethodError)
I guess you need something like this:
module ActiveRecordExtension
extend ActiveSupport::Concern
included do
attr_accessible # Still need to call the API method
def mass_assignment_authorizer
# Here we can access object's attributes
super << some_attr
end
end
end
ActiveRecord::Base.send(:include, ActiveRecordExtension)
I updated my answer.

What is the preferred way to add some method in active record base?

I want to create a module which provides some common methods to the classes which are inherited from active record base.
Following is the two-way we can achieve it.
1)
module Commentable
def self.extended(base)
base.class_eval do
include InstanceMethods
extend ClassMethods
end
end
module ClassMethods
def test_commentable_classmethod
puts 'test class method'
end
end
module InstanceMethods
def test_commentable_instance_method
puts 'test instance method'
end
end
end
ActiveRecord::Base.extend(Commentable)
2)
module Commentable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def test_commentable_classmethod
puts 'test class method'
end
end
def test_commentable_instance_method
puts 'test instance methods'
end
end
ActiveRecord::Base.send(:include, Commentable)
Which one is the preferred way to handle this?
And
What to use when?
As of Rails 5, the recommended way is to make a module and include it in the models where it is needed, or everywhere using ApplicationRecord, which all models inherit from. (You can easily implement this pattern from scratch in older versions of Rails.)
# app/models/concerns/my_module.rb
module MyModule
extend ActiveSupport::Concern
module ClassMethods
def has_some_new_fancy_feature(options = {})
...
end
end
end
# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
include MyModule
end
Modules are a form of multiple-inheritance, and sometimes add unnecessary complexity. Check if a decorator, service, or other kind of object makes more sense first. Not everything needs be a fancy macro that adds 50 callbacks to your model. You will hate your life if you do this too much.
If you want to monkey-patch (DON'T DO THIS), here is my old answer:
# config/initializers/activerecord_extensions.rb
ActiveRecord::Base.send(:include, MyModule)
Or without monkey-patching (see Mori's response):
# app/models/base_model.rb
class BaseModel < ActiveRecord::Base
self.abstract_class = true
include MyModule
end
Edit: Several months down the road in a large project, I have realized its better to have every model inherit from a new base model class, as Mori explains. The problem with including modules directly into ActiveRecord::Base is this can interfere with third-party code that also relies on ActiveRecord. It is just better not to monkey-patch when you don't have to. In this case, creating a new base class can end up being simpler in the long run.
Another way is make your own base class by inheriting from ActiveRecord::Base and then letting your models inherit from that base class. This has the advantage of making it clear that your models aren't running on vanilla ActiveRecord:
class MyBase < ActiveRecord::Base
self.abstract_class = true
def self.a_class_method
end
def an_instance_method
end
end
class Foo < MyBase
end
Foo.a_class_method
Foo.new.an_instance_method
reffering with Mori's answer...you can do something like:-
Module ActiveRecordUtilities
class MyBase < ActiveRecord::Base
self.abstract_class = true
def self.a_class_method
end
def an_instance_method
end
end
end##class ends
end##module ends
and can use it ...suppose in user.rb
include ActiveRecordUtilities::MyBase
User.a_class_method
#user.instance_method
============================OR====================
module MyUtils
def do_something_funky
# Some exciting code
end
end
class Account < ActiveRecord::Base
belongs_to :person, :extend => MyUtils
end
And then call it like this:
#account = Account.first
#account.person.do_something_funky

Where to put helper methods needed in multiple models in a Rails app?

As an example:
module ModelHelper
def self.special_function(some_parameter)
do_some_special_thing
end
end
class Student < ActiveRecord::Base
def to_special
ModelHelper.special_function(a_variable_of_here)
end
end
class Teacher < ActiveRecord::Base
def to_special
ModelHelper.special_function(another_variable_of_here)
end
end
Where do I put model_helper.rb?
I generally make a file in lib and include it. Something like lib/special_model.rb:
module SpecialModel
included do
def to_special
do_some_special_thing
end
end
end
Then in app/models/student.rb:
class Student
include SpecialModel
end
You may also want to look at ActiveSupport::Concern for some rails help when working with modules:
http://api.rubyonrails.org/classes/ActiveSupport/Concern.html

Error from moving acts_as_taggable into an included module

If I write this, everything works fine:
class A < ActiveRecord::Base
acts_as_taggable
end
But if I take acts_as_taggable and put it into a module that class A includes, I get an error:
module B
def self.included(base)
base.class_eval do
extend ClassMethods
include InstanceMethods
end
end
module ClassMethods
acts_as_taggable
end
module InstanceMethods
end
end
class A < ActiveRecord::Base
include B
The error from the code above is:
undefined local variable or method `acts_as_taggable' for C::ClassMethods:Module
Is it not correct to call acts_as_taggable from an included module?
Does it need to be in the class definition itself?
When Ruby loads the file containing your module B and reaches the acts_as_taggable line, it will try to execute the acts_as_taggable class method of ClassMethods (which doesn't exist because it is actually a class method of ActiveRecord::Base).
You can use the included method to call acts_as_taggable when your module is included though. included is passed the class the module is being included in, so the following will work:
module B
def self.included(base)
base.acts_as_taggable
# ...
end
# ...
end

Extend model in plugin with "has_many" using a module

I have a some code in an engine style plugin which includes some models. In my app I want to extend one of these models. I have managed to add both instance and class methods to the model in question by including a module from within an initializer.
However I cannot seem to add associations, callbacks etc. I get a 'method not found' error.
/libs/qwerty/core.rb
module Qwerty
module Core
module Extensions
module User
# Instance Methods Go Here
# Class Methods
module ClassMethods
has_many :hits, :uniq => true # no method found
before_validation_on_create :generate_code # no method found
def something # works!
"something"
end
end
def self.included(base)
base.extend(ClassMethods)
end
end
end
end
end
/initializers/qwerty.rb
require 'qwerty/core/user'
User.send :include, Qwerty::Core::Extensions::User
You should be able to do this. A little more concise IMHO.
module Qwerty::Core::Extensions::User
def self.included(base)
base.class_eval do
has_many :hits, :uniq => true
before_validation_on_create :generate_code
end
end
end
I think this should work
module Qwerty
module Core
module Extensions
module User
# Instance Methods Go Here
# Class Methods
module ClassMethods
def relate
has_many :hits, :uniq => true # no method found
before_validation_on_create :generate_code # no method found
end
def something # works!
"something"
end
end
def self.included(base)
base.extend(ClassMethods).relate
end
end
end
end
end
The old code is wrong cause the validation and the association are called upon module loading, and this module knows nothing about ActiveRecord. That's a general aspect of Ruby, code inside class or module bodies is called directly when loaded. You don't want that. To get around that you can use the above solution.
In Rails 3, this sounds like a good use case for ActiveSupport::Concern:
module Qwerty::Core::Extensions::User
extend ActiveSupport::Concern
included do
has_many :hits, :uniq => true
before_validation_on_create :generate_code
end
end
class User
include Querty::Core::Extensions::User
# ...
end
Here are the ActiveSupport::Concern docs and the most helpful blog article on it I've found.

Resources