Rails extending ActiveRecord::Base

Ruby on-RailsExtendRails Activerecord

Ruby on-Rails Problem Overview


I've done some reading about how to extend ActiveRecord:Base class so my models would have some special methods. What is the easy way to extend it (step by step tutorial)?

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

There are several approaches :

#Using ActiveSupport::Concern (Preferred)

Read the ActiveSupport::Concern documentation for more details.

Create a file called active_record_extension.rb in the lib directory.

require 'active_support/concern'

module ActiveRecordExtension

  extend ActiveSupport::Concern

  # add your instance methods here
  def foo
     "foo"
  end

  # add your static(class) methods here
  class_methods do
    #E.g: Order.top_ten        
    def top_ten
      limit(10)
    end
  end
end

# include the extension 
ActiveRecord::Base.send(:include, ActiveRecordExtension)

Create a file in the config/initializers directory called extensions.rb and add the following line to the file:

require "active_record_extension"

#Inheritance (Preferred)

Refer to Toby's answer.

#Monkey patching (Should be avoided)

Create a file in the config/initializers directory called active_record_monkey_patch.rb.

class ActiveRecord::Base     
  #instance method, E.g: Order.new.foo       
  def foo
   "foo"
  end

  #class method, E.g: Order.top_ten        
  def self.top_ten
    limit(10)
  end
end

The famous quote about Regular expressions by Jamie Zawinski can be re-purposed to illustrate the problems associated with monkey-patching.

> Some people, when confronted with a problem, think “I know, I'll use > monkey patching.” Now they have two problems.

Monkey patching is easy and quick. But, the time and effort saved is always extracted back sometime in the future; with compound interest. These days I limit monkey patching to quickly prototype a solution in the rails console.

Solution 2 - Ruby on-Rails

You can just extend the class and simply use inheritance.

class AbstractModel < ActiveRecord::Base  
  self.abstract_class = true
end

class Foo < AbstractModel
end

class Bar < AbstractModel
end

Solution 3 - Ruby on-Rails

You can also use ActiveSupport::Concern and be more Rails core idiomatic like:

module MyExtension
  extend ActiveSupport::Concern

  def foo
  end

  module ClassMethods
    def bar
    end
  end
end

ActiveRecord::Base.send(:include, MyExtension)

[Edit] following the comment from @daniel

Then all your models will have the method foo included as an instance method and the methods in ClassMethods included as class methods. E.g. on a FooBar < ActiveRecord::Base you will have: FooBar.bar and FooBar#foo

http://api.rubyonrails.org/classes/ActiveSupport/Concern.html

Solution 4 - Ruby on-Rails

With Rails 4, the concept of using concerns to modularize and DRY up your models has been in highlights.

Concerns basically allow you to group similar code of a model or across multiple models in a single module and then use this module in the models. Here is a example:

Consider a Article model, a Event model and a Comment Model. A article or A event has many comments. A comment belongs to either article or event.

Traditionally, the models may look like this:

Comment Model:

class Comment < ActiveRecord::Base
  belongs_to :commentable, polymorphic: true
end

Article Model:

class Article < ActiveRecord::Base
  has_many :comments, as: :commentable 
   
  def find_first_comment
	comments.first(created_at DESC)
  end

  def self.least_commented
   #return the article with least number of comments
  end
end

Event Model

class Event < ActiveRecord::Base
  has_many :comments, as: :commentable 
   
  def find_first_comment
	comments.first(created_at DESC)
  end

  def self.least_commented
   #returns the event with least number of comments
  end
end

As we can notice, there is a significant piece of code common to both Event and Article Model. Using concerns we can extract this common code in a separate module Commentable.

For this create a commentable.rb file in app/model/concerns.

module Commentable
	extend ActiveSupport::Concern

	included do 
		has_many :comments, as: :commentable 
	end

    # for the given article/event returns the first comment
	def find_first_comment
		comments.first(created_at DESC)
	end
	  
	module ClassMethods		
		def least_commented
           #returns the article/event which has the least number of comments
		end
	end	
end

And Now your models look like this :

Comment Model:

    class Comment < ActiveRecord::Base
	  belongs_to :commentable, polymorphic: true
    end

Article Model:

class Article < ActiveRecord::Base
	include Commentable
end

Event Model

class Event < ActiveRecord::Base	
	include Commentable
end

One point I will like to highlight while using Concerns is that Concerns should be used for 'domain based' grouping rather than 'technical' grouping. For example, a domain grouping is like 'Commentable', 'Taggable' etc. A technical based grouping will be like 'FinderMethods', 'ValidationMethods'.

Here is a link to a post that I found very useful for understanding concerns in Models.

Hope the writeup helps :)

Solution 5 - Ruby on-Rails

Step 1

module FooExtension
  def foo
    puts "bar :)"
  end
end
ActiveRecord::Base.send :include, FooExtension

Step 2

# Require the above file in an initializer (in config/initializers)
require 'lib/foo_extension.rb'

Step 3

There is no step 3 :)

Solution 6 - Ruby on-Rails

Rails 5 provides a built-in mechanism for extending ActiveRecord::Base.

This is achieved by providing additional layer:

# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
  # put your extensions here
end

and all models inherit from that one:

class Post < ApplicationRecord
end

See e.g. this blogpost.

Solution 7 - Ruby on-Rails

With Rails 5, all models are inherited from ApplicationRecord & it gives nice way to include or extend other extension libraries.

# app/models/concerns/special_methods.rb
module SpecialMethods
  extend ActiveSupport::Concern
  
  scope :this_month, -> { 
    where("date_trunc('month',created_at) = date_trunc('month',now())")
  }

  def foo
    # Code
  end
end

Suppose the special methods module needs to be available across all models, include it in application_record.rb file. If we wants to apply this for a particular set of models, then include it in the respective model classes.

# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
  include SpecialMethods
end

# app/models/user.rb
class User < ApplicationRecord
  include SpecialMethods

  # Code
end

If you want to have the methods defined in the module as class methods, extend the module to ApplicationRecord.

# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
  extend SpecialMethods
end

Hope it help others !

Solution 8 - Ruby on-Rails

Just to add to this topic, I spent a while working out how to test such extensions (I went down the ActiveSupport::Concern route.)

Here's how I set up a model for testing my extensions.

describe ModelExtensions do
  describe :some_method do
    it 'should return the value of foo' do
      ActiveRecord::Migration.create_table :test_models do |t|
        t.string :foo
      end

      test_model_class = Class.new(ActiveRecord::Base) do
        def self.name
          'TestModel'
        end

        attr_accessible :foo
      end

      model = test_model_class.new(:foo => 'bar')

      model.some_method.should == 'bar'
    end
  end
end

Solution 9 - Ruby on-Rails

I have

ActiveRecord::Base.extend Foo::Bar

in an initializer

For a module like below

module Foo
  module Bar
  end
end

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionxpepermintView Question on Stackoverflow
Solution 1 - Ruby on-RailsHarish ShettyView Answer on Stackoverflow
Solution 2 - Ruby on-RailsToby HedeView Answer on Stackoverflow
Solution 3 - Ruby on-RailsnikolaView Answer on Stackoverflow
Solution 4 - Ruby on-RailsAaditi JainView Answer on Stackoverflow
Solution 5 - Ruby on-RailsVitaly KushnerView Answer on Stackoverflow
Solution 6 - Ruby on-RailsAdobeView Answer on Stackoverflow
Solution 7 - Ruby on-RailsAshik SalmanView Answer on Stackoverflow
Solution 8 - Ruby on-RailsWill TomlinsView Answer on Stackoverflow
Solution 9 - Ruby on-RailsEd RichardsView Answer on Stackoverflow