6

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?

krunal shah
  • 15,347
  • 24
  • 90
  • 136

3 Answers3

10

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.

wmakley
  • 1,113
  • 7
  • 17
8

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
Mori
  • 25,288
  • 10
  • 60
  • 69
0

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
Milind
  • 4,136
  • 1
  • 21
  • 51