Rails extending ActiveRecord::Base

后端 未结 9 1495
眼角桃花
眼角桃花 2020-11-22 12:38

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)?<

相关标签:
9条回答
  • 2020-11-22 13:06

    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 :)

    0 讨论(0)
  • 2020-11-22 13:12

    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
    
    0 讨论(0)
  • 2020-11-22 13:13

    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.

    0 讨论(0)
  • 2020-11-22 13:13

    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 :)
    
    0 讨论(0)
  • 2020-11-22 13:13

    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 !

    0 讨论(0)
  • 2020-11-22 13:14

    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.

    0 讨论(0)
提交回复
热议问题