Rails 扩展 ActiveRecord::Base
Posted
技术标签:
【中文标题】Rails 扩展 ActiveRecord::Base【英文标题】:Rails extending ActiveRecord::Base 【发布时间】:2011-01-20 16:50:26 【问题描述】:我已经阅读了一些关于如何扩展 ActiveRecord:Base 类的信息,这样我的模型就会有一些特殊的方法。扩展它的简单方法是什么(分步教程)?
【问题讨论】:
什么样的扩展?我们真的需要更多。 【参考方案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 控制台中快速原型化解决方案。
【讨论】:
你必须require
environment.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 FROMabstract_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 < ActiveRecord::Base
上,您将拥有:FooBar.bar
和 FooBar#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
【讨论】:
以上是关于Rails 扩展 ActiveRecord::Base的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Rails 之外使用 Rails 3 的 ActiveSupport 核心扩展 [重复]