【问题标题】:Rails extending ActiveRecord::BaseRails 扩展 ActiveRecord::Base
【发布时间】:2011-01-20 16:50:26
【问题描述】:

我已经阅读了一些关于如何扩展 ActiveRecord:Base 类的信息,这样我的模型就会有一些特殊的方法。扩展它的简单方法是什么(分步教程)?

【问题讨论】:

  • 什么样的扩展?我们真的需要更多。

标签: ruby-on-rails extend rails-activerecord


【解决方案1】:

有几种方法:

使用 ActiveSupport::Concern(首选)

阅读ActiveSupport::Concern 文档了解更多详情。

lib 目录中创建一个名为active_record_extension.rb 的文件。

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)

config/initializers 目录中创建一个名为extensions.rb 的文件,并将以下行添加到文件中:

require "active_record_extension"

继承(首选)

请参考 Toby 的answer

猴子补丁(应该避免)

config/initializers 目录中创建一个名为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

Jamie Zawinski 关于正则表达式的名言可以重新用于说明与猴子修补相关的问题。

有些人在遇到问题时会想“我知道,我会用 猴子补丁。”现在他们有两个问题。

猴子修补简单快捷。但是,节省的时间和精力总是被提取回来 在未来的某个时候;复利。这些天来,我限制猴子修补以在 Rails 控制台中快速原型化解决方案。

【讨论】:

  • 你必须requireenvironment.rb末尾的文件。我在答案中添加了这个额外的步骤。
  • @HartleyBrody 这只是一个偏好问题。如果您使用继承,则必须引入一个新的ImprovedActiveRecord 并从中继承,当您使用module 时,您正在更新相关类的定义。我曾经使用继承(由于多年的 Java/C++ 经验)。这些天我主要使用模块。
  • 有点讽刺意味的是,您的链接实际上是在上下文化并指出人们如何误用和过度使用这句话。但说真的,我很难理解为什么“猴子补丁”在这种情况下不是最好的方法。如果您想添加到多个类,那么模块显然是要走的路。但是,如果您的目标是扩展一个类,那么这难道不是 Ruby 以这种方式扩展类如此简单的原因吗?
  • @MCB,每个大型项目都很少有关于由于猴子补丁而引入的难以定位的错误的故事。这是 Avdi 的一篇关于修补弊端的文章:devblog.avdi.org/2008/02/23/…。 Ruby 2.0 引入了一个名为Refinements 的新功能,它解决了猴子补丁(yehudakatz.com/2010/11/30/ruby-2-0-refinements-in-practice)的大部分问题。有时,一个功能只是为了迫使你去试探命运。有时你会这样做。
  • @TrantorLiu 是的。我已经更新了答案以反映最新的文档(看起来 class_methods 是在 2014 年推出的 github.com/rails/rails/commit/…
【解决方案2】:

您可以只扩展类并简单地使用继承。

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

class Foo < AbstractModel
end

class Bar < AbstractModel
end

【讨论】:

  • 我喜欢这个想法,因为这是一种标准的做法,但是...我收到一个错误表 'moboolo_development.abstract_models' 不存在:SHOW FIELDS FROM abstract_models。我应该把它放在哪里?
  • self.abstract_class = true 添加到您的AbstractModel。 Rails 现在会将模型识别为抽象模型。
  • 哇!没想到这是可能的。早些时候尝试过,但当 ActiveRecord 在数据库中寻找 AbstractModel 时感到窒息而放弃了。谁知道一个简单的二传手会帮我把事情弄干! (我开始畏缩......这很糟糕)。谢谢托比和哈里什!
  • 在我的情况下,这绝对是最好的方法:我不是在这里用外来方法扩展我的模型能力,而是为我的应用程序的类似行为对象重构常用方法。 继承在这里更有意义。没有首选方式,但有两种解决方案,具体取决于您想要实现的目标!
  • 这在 Rails4 中对我不起作用。我创建了 abstract_model.rb 并放入我的模型目录中。在模型内部它有 self.abstract_class= true 然后我继承了我的其他模型... User 。在控制台中我得到:用户(调用'User.connection'建立连接)
【解决方案3】:

你也可以使用ActiveSupport::Concern 并且更像 Rails 核心惯用语:

module MyExtension
  extend ActiveSupport::Concern

  def foo
  end

  module ClassMethods
    def bar
    end
  end
end

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

根据@daniel 的评论[编辑]

然后你的所有模型都将包含foo 方法作为实例方法,并将ClassMethods 中的方法包含为类方法。例如。在FooBar &lt; ActiveRecord::Base 上,您将拥有:FooBar.barFooBar#foo

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

【讨论】:

  • 请注意,InstanceMethods 自 Rails 3.2 起已弃用,只需将您的方法放入模块主体即可。
  • 我将ActiveRecord::Base.send(:include, MyExtension) 放在初始化器中,然后这对我有用。 Rails 4.1.9
【解决方案4】:

在 Rails 4 中,使用关注点模块化和干燥模型的概念已成为亮点。

关注点基本上允许您将模型的相似代码或跨多个模型的代码分组到单个模块中,然后在模型中使用此模块。这是一个例子:

考虑文章模型、事件模型和评论模型。一篇文章或一个事件有许多 cmets。评论属于文章或事件。

传统上,模型可能如下所示:

评论模型:

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

文章型号:

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

事件模型

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

我们可以注意到,有一段重要的代码是事件模型和文章模型共有的。使用关注点,我们可以在单独的可注释模块中提取此公共代码。

为此在 app/model/concerns 中创建一个 commentable.rb 文件。

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

现在你的模型看起来像这样:

评论模型:

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

文章型号:

class Article < ActiveRecord::Base
    include Commentable
end

事件模型

class Event < ActiveRecord::Base    
    include Commentable
end

在使用关注点时我想强调的一点是关注点应该用于“基于域”的分组而不是“技术”分组。例如,域分组类似于“可评论” , 'Taggable' 等。基于技术的分组将类似于 'FinderMethods'、'ValidationMethods'。

这是一个link to a post,我发现它对于理解模型中的关注点非常有用。

希望这篇文章有帮助:)

【讨论】:

    【解决方案5】:

    第 1 步

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

    第 2 步

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

    第 3 步

    There is no step 3 :)
    

    【讨论】:

    • 我猜第 2 步必须放在 config/environment.rb 中。它对我不起作用:(。你能再写一些帮助吗?谢谢。
    【解决方案6】:

    Rails 5 提供了用于扩展 ActiveRecord::Base 的内置机制。

    这是通过提供额外的层来实现的:

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

    所有模型都继承自该模型:

    class Post < ApplicationRecord
    end
    

    参见例如this blogpost.

    【讨论】:

      【解决方案7】:

      使用 Rails 5,所有模型都继承自 ApplicationRecord,它提供了包含或扩展其他扩展库的好方法。

      # 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
      

      假设特殊方法模块需要在所有模型中都可用,请将其包含在 application_record.rb 文件中。如果我们想将其应用于一组特定的模型,则将其包含在相应的模型类中。

      # 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
      

      如果要将模块中定义的方法作为类方法,请将模块扩展为 ApplicationRecord。

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

      希望对他人有所帮助!

      【讨论】:

        【解决方案8】:

        为了补充这个话题,我花了一段时间研究如何测试这些扩展(我选择了ActiveSupport::Concern 路线。)

        这是我如何设置模型来测试我的扩展。

        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
        

        【讨论】:

          【解决方案9】:

          我有

          ActiveRecord::Base.extend Foo::Bar
          

          在初始化器中

          对于像下面这样的模块

          module Foo
            module Bar
            end
          end
          

          【讨论】:

            猜你喜欢
            • 2015-11-14
            • 1970-01-01
            • 2011-05-28
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-04-11
            • 1970-01-01
            相关资源
            最近更新 更多