Rails扩展ActiveRecord :: Base


160

我已经阅读了一些有关如何扩展ActiveRecord:Base类的内容,因此我的模型将具有一些特殊的方法。扩展它的简单方法是什么(逐步教程)?


什么样的扩展?我们确实还需要继续。
jonnii 2010年

Answers:


336

有几种方法:

使用ActiveSupport :: Concern(首选)

有关更多详细信息,请阅读ActiveSupport :: Concern文档。

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

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的答案

猴子修补(应避免)

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控制台中快速创建解决方案的原型。


3
您必须require在末尾添加文件environment.rb。我已经在回答中添加了这一额外步骤。
Harish Shetty

1
@HartleyBrody只是一个偏好问题。如果使用继承,则必须引入一个新的ImprovedActiveRecord并从中继承,而在使用时module,您将更新相关类的定义。我曾经使用继承(由于多年的Java / C ++经验)。这些天我主要使用模块。
Harish Shetty

1
具有讽刺意味的是,您的链接实际上是上下文化的,并指出了人们是如何误用和滥用引号的。但说真的,我很难理解为什么“猴子补丁”在这种情况下不是最好的方法。如果要添加到多个类中,那么显然可以使用模块。但是,如果您的目标是扩展一个类,那不是Ruby为什么使这种方式的扩展类如此容易吗?
MCB

1
@MCB,每个大项目都有一些关于猴子修补导致难以定位的错误的故事。这是Avdi撰写的有关补丁的弊端的文章:devblog.avdi.org/2008/02/23/…。Ruby 2.0引入了一个称为的新功能Refinements,该功能解决了猴子修补的大多数问题(yehudakatz.com/2010/11/30/ruby-2-0-refinements-in-practice)。有时有一个功能只是强迫您诱惑命运。有时候你会的。
Harish Shetty

1
@TrantorLiu是的。我已经更新了答案以反映最新的文档(类class_methods于2014年github.com/rails/rails/commit/…引入)
Harish Shetty

70

您可以只扩展类并仅使用继承。

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。我应该放在哪里?
xpepermint 2010年

23
添加self.abstract_class = true到您的中AbstractModel。Rails现在将模型识别为抽象模型。
Harish Shetty

哇!认为这不可能。较早尝试过,当ActiveRecord AbstractModel在数据库中阻塞时放弃了。谁知道一个简单的二传手会帮助我干些事情!(我开始畏缩...这很糟糕)。感谢托比和哈里斯!
dooleyo 2013年

就我而言,这绝对是执行此操作的最佳方法:我不是在这里用外部方法扩展我的模型能力,而是为我的应用程序的类似行为对象重构公共方法。继承在这里更有意义。除了您要实现的目标之外,没有2种解决方案的首选方法
Augustin Riedinger 2014年

在Rails4中,这对我不起作用。我创建了abstract_model.rb并放入了我的模型目录。在模型内部,它具有self.abstract_class = true。 然后,我对其他模型进行继承... User <AbstractModel。在控制台中,我得到:用户(调用“ User.connection”以建立连接)
Joel Grannas 2015年

21

您还可以使用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 < ActiveRecord::Base您将拥有:FooBar.barFooBar#foo

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


5
请注意,InstanceMethods从Rails 3.2开始不推荐使用,只需将您的方法放入模块主体即可。
Daniel Rikowski 2012年

我放入ActiveRecord::Base.send(:include, MyExtension)一个初始化程序,然后为我工作。Rails 4.1.9
6ft

18

使用Rails 4时,使用关注点对模型进行模块化和DRY处理的概念已成为重点。

基本上,您可以通过关注将一个模型的相似代码或跨多个模型的代码组合到一个模块中,然后在模型中使用此模块。这是一个例子:

考虑文章模型,事件模型和评论模型。文章或事件有很多评论。评论属于文章或事件。

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

评论模型:

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

我们可以注意到,事件和文章模型都有大量的通用代码。使用关注点,我们可以在单独的模块Commentable中提取此通用代码。

为此,在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

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

这是我发现对理解模型中的问题非常有用的文章链接

希望这篇文章对您有所帮助:)


7

第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'

第三步

There is no step 3 :)

1
我猜第2步必须放在config / environment.rb中。它不是为我工作:(能否请您写一些更多的帮助THX。?
xpepermint

5

滑轨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

参见例如此博客文章


4

只是为了补充这个话题,我花了一段时间研究如何测试这些扩展(我走了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

4

使用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

希望对别人有帮助!


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.