在多个模型中发现的通用代码应该放在哪里?

27
我有两个模型,它们包含相同的方法:
def foo
  # do something
end

我应该把这放在哪里?

我知道在Rails应用程序中,通常代码放在lib目录中。

但是如果我把它放在一个名为'Foo'的新类中的lib中,并且我需要将其功能添加到我的两个ActiveRecord模型中,我应该这样做:

class A < ActiveRecord::Base
includes Foo

class B < ActiveRecord::Base
includes Foo

然后,AB都将包含foo方法,就像我在每个中定义它一样?

6个回答

39

创建一个模块,可以将其放在lib目录中:

module Foo
  def foo
    # do something
  end
end

然后你可以在每个模型类中include该模块:

class A < ActiveRecord::Base
  include Foo
end

class B < ActiveRecord::Base
  include Foo
end

AB模型现在将定义一个foo方法。

如果您按照Rails的命名约定使用模块名称和文件名称(例如,在foo.rb中使用Foo和在foo_bar.rb中使用FooBar),那么Rails将自动为您加载该文件。否则,您需要使用require_dependency 'file_name'来加载您的lib文件。


14

您有两个选择:

  1. 使用一个常用逻辑模块,并将其包含到A和B中
  2. 使用一个公共类C,该类扩展ActiveRecord,并使A和B扩展C。

如果共享功能对每个类不是核心功能,但适用于每个类,则使用方法#1。例如:

(app/lib/serializable.rb)
module Serializable
  def serialize
    # do something to serialize this object
  end
end

如果共享的功能对每个类都是通用的,并且 A 和 B 具有自然关系,请使用 #2:

(app/models/letter.rb)
class Letter < ActiveRecord::Base
  def cyrilic_equivilent
    # return somethign similar
  end
end

class A < Letter
end

class B < Letter
end

在这种情况下,需要一个“letters”关系,对吧?那么将ActiveRecord扩展到A和B上是否可行呢? - Ron
2
选项#2让Rails假设A和B都存储在名为“letters”的表中。如果您只想要共享逻辑,同时保持A和B在不同的表中,则抽象父类是正确的选择,正如@Ron在下面指出的那样(https://dev59.com/P3I-5IYBdhLWcg3wu7Pv#20749863)。 - EK0

6

以下是我是如何做到的...首先创建mixin:

module Slugged
  extend ActiveSupport::Concern

  included do
    has_many :slugs, :as => :target
    has_one :slug, :as => :target, :order => :created_at
  end
end

然后将其混合到每个需要它的模型中:

class Sector < ActiveRecord::Base
  include Slugged

  validates_uniqueness_of :name
  etc
end

它看起来几乎很漂亮!

为了完成示例,虽然与问题无关,但这是我的slug模型:

class Slug < ActiveRecord::Base
  belongs_to :target, :polymorphic => true
end

5
一种选择是将它们放入一个新目录,例如app/models/modules/。然后,您可以将其添加到config/environment.rb中:
Dir["#{RAILS_ROOT}/app/models/modules/*.rb"].each do |filename|
  require filename
end

这将需要该目录中的每个文件,因此如果您在模块目录中放置以下文件,则可以实现如下效果:
module SharedMethods
  def foo
    #...
  end
end

那么你只需在模型中使用它,因为它将自动加载:

class User < ActiveRecord::Base
  include SharedMethods
end

将这些mixin放在与使用它们的类相邻的位置上,比将它们放在lib目录中更加有组织。


如果两个模型都调用“before_save :before_method”,并且我也将其放在SharedMethods中,那么这也可以工作吗?还是它只适用于方法定义? - Bryan Locke
另外,你的 'require' 代码出现在 environment.rb 中的位置是否重要? - Bryan Locke
1
你可能想把它放在“Rails::Initializer.run do |config| ... end”部分。 - nicholaides
是的,“before_method”方法可以在mixin中,但代码“before_save:before_method”不能。 - nicholaides
可以将 before_save 调用添加到 mixin 中。我已经添加了一个展示如何实现的答案。 - EmFi

5
如果您需要将 ActiveRecord::Base 代码作为常规功能的一部分,使用抽象类也可能很有用。例如:
class Foo < ActiveRecord::Base
  self.abstract_class = true
  #Here ActiveRecord specific code, for example establish_connection to a different DB.
end

class A < Foo; end
class B < Foo; end
< p> 就是这么简单。另外,如果代码与ActiveRecord无关,请将 ActiveSupport::Concerns 作为更好的方法。


4

正如其他人提到的那样,使用include Foo是完成任务的方式...然而,这似乎不能为基本模块提供所需的功能。以下是许多Rails插件采用的形式,以添加类方法和新回调,以及新实例方法。

module Foo #:nodoc:

  def self.included(base) # :nodoc:
    base.extend ClassMethods
  end

  module ClassMethods
    include Foo::InstanceMethods

    before_create :before_method
  end

  module InstanceMethods
    def foo
      ...
    end

    def before_method
      ...
    end
  end 

end

我尝试了那种方法,但它仍然告诉我没有定义方法(在我的情况下是 scope)。 - Jonah

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接