0

我定义了以下类,这些类具有许多具有细微变化的通用代码。

class ThirdPartyComponent < ActiveRecord::Base
  belongs_to :prev_version, :class_name => 'ThirdPartyComponent', :foreign_key => 'prev_version_id'
  has_one :next_version, :class_name => 'ThirdPartyComponent', :foreign_key => 'prev_version_id'

  attr_accessible :name, :version, :installer, :install_script

  mount_uploader :installer, ComponentFileUploader
  mount_uploader :install_script, ComponentFileUploader

  validates :name, :presence => true
  validates :version, :presence => true, :format => { :with => /\A\d{1,3}\.\d{1,2}\z/ }
  validates :installer, :presence => true
  validates :install_script, :presence => true
  validate :increased_version

  def increased_version
    # Check to ensure that version number is greater than the previous version number for the same component set
    unless prev_version.nil?
      version > prev_version.version
    end
  end 

  def all_previous_versions
    prev_versions = all_versions
    prev_versions.shift
    prev_versions
  end

  def all_versions
    current_version = self
    all_versions = [current_version]
    while !current_version.prev_version.nil?
      all_versions << current_version.prev_version
      current_version = current_version.prev_version
    end
    all_versions
  end
end

class RegistryComponent < ActiveRecord::Base

  belongs_to :prev_version, :class_name => 'RegistryComponent', :foreign_key => 'prev_version_id'
  has_one :next_version, :class_name => 'RegistryComponent', :foreign_key => 'prev_version_id'

  attr_accessible :name, :version, :registry_file

  mount_uploader :registry_file, ComponentFileUploader

  validates :name, :presence => true
  validates :version, :presence => true, :format => { :with => /\A\d{1,3}\.\d{1,2}\z/ }
  validates :registry_file, :presence => true
  validate :increased_version

  def increased_version
    # Check to ensure that version number is greater than the previous version number for the same component set
    unless prev_version.nil?
      version > prev_version.version
    end
  end

  def all_previous_versions
    prev_versions = all_versions
    prev_versions.shift
    prev_versions
  end

  def all_versions
    current_version = self
    all_versions = [current_version]
    while !current_version.prev_version.nil?
      all_versions << current_version.prev_version
      current_version = current_version.prev_version
    end
    all_versions
  end
end

我还在考虑在未来添加一些其他组件,同样具有非常相似的功能。

我想将这些类中的通用代码提取到一个文件中(包括 ActiveRecord 方法调用,如 validates 等),然后在具体类中引用它们。

到目前为止,我已经尝试过,

  1. 继承 - 我创建了一个从 ActiveRecord 继承的基类,然后每个类都从基类继承。结果:Rails 抱怨它找不到名称与基类匹配的数据库表。
  2. 继承 - 我考虑将基类创建为无表模型(参见http://railscasts.com/episodes/219-active-model),但后来我意识到具体类也将缺乏完整的 ActiveRecord 功能
  3. 组合 - 我尝试在模块中定义公共代码,然后在具体类中使用包含或扩展来访问它,如下所示。

    module ComponentBase
      belongs_to :prev_version, :class_name => self.class.name, :foreign_key => 'prev_version_id'
      has_one :next_version, :class_name => self.class.name, :foreign_key => 'prev_version_id'
    
      attr_accessible :name, :version
    
      validates :name, :presence => true
      validates :version, :presence => true, :format => { :with => /\A\d{1,3}\.\d{1,2}\z/ }
      validate :increased_version
    
      def increased_version
        # Check to ensure that version number is greater than the previous version number for the same component set
        unless prev_version.nil?
          version > prev_version.version
        end
      end
    
      def all_previous_versions
        prev_versions = all_versions
        prev_versions.shift
        prev_versions
      end
    
      def all_versions
        current_version = self
        all_versions = [current_version]
        while !current_version.prev_version.nil?
          all_versions << current_version.prev_version
          current_version = current_version.prev_version
        end
        all_versions
      end
    end
    
    class RegistryComponent < ActiveRecord::Base
      include ComponentBase
    
      attr_accessible :registry_file
    
      mount_uploader :registry_file, ComponentFileUploader
    
      validates :registry_file, :presence => true
    end
    

    这导致了一个belongs_to没有为 定义方法的错误ComponentBase。这看起来是最有希望的解决方案,但是有没有办法在包含它们的类的上下文中执行 ActiveRecord 类方法?或者,我是否有更好的方法来实现相同的目标?

4

3 回答 3

1

你的第一个选择实际上是最好的选择。Rails 使用单表继承,这意味着您所有子类的数据都保存在同一个表中,这就是您遇到错误的原因。

您应该做的是创建一个名为的新模型Component,并向其中添加所有组件通用的所有字段以及一个称为type字符串字段的额外字段。

然后,您的组件模型将具有所有公共字段、逻辑和验证。

class Component < ActiveRecord::Base
  ...
end

然后让你的每个组件类子类化 Component。

class ThirdPartyComponent < Component
  ...
end
于 2013-07-10T03:17:52.910 回答
0

问题是您需要belongs_to 方法在包含模块的类上运行,而不是在模块本身上运行。

查看 module#included http://www.ruby-doc.org/core-2.0/Module.html#method-i-included,这将允许您在包含模块的模块上运行代码。记住 Module 是 Class 的祖先,所以这适用于类和模块。

在这种情况下,您将希望在包含模块的类上运行 belongs_to,因此您可以使用以下内容作为示例:

module ComponentBase
    def self.included(mod)
        mod.class_eval do
            belongs_to :prev_version, :class_name => self.class.name, :foreign_key => 'prev_version_id'
        end
    end
end
于 2013-07-10T01:06:20.557 回答
0

我在用一段独立的代码扩展一个 Ruby 类时偶然发现了以下答案,并通过一些实验让它工作。所以我最终得到的最终代码是,

module ComponentBase

  def self.included(base)
    base.class_eval do
      belongs_to :prev_version, :class_name => base, :foreign_key => 'prev_version_id'
      has_one :next_version, :class_name => base, :foreign_key => 'prev_version_id'

      attr_accessible :name, :version

      validates :name, :presence => true
      validates :version, :presence => true, :format => { :with => /\A\d{1,3}\.\d{1,2}\z/ }
      validate :increased_version
    end
  end

  def increased_version
    # Check to ensure that version number is greater than the previous version number for the same component set
    unless prev_version.nil?
      version > prev_version.version
    end
  end

  def all_previous_versions
    prev_versions = all_versions
    prev_versions.shift
    prev_versions
  end

  def all_versions
    current_version = self
    all_versions = [current_version]
    while !current_version.prev_version.nil?
      all_versions << current_version.prev_version
      current_version = current_version.prev_version
    end
    all_versions
  end
end

class RegistryComponent < ActiveRecord::Base
  include ComponentBase

  attr_accessible :registry_file

  mount_uploader :registry_file, ComponentFileUploader

  validates :registry_file, :presence => true
end

解决方案是使用included每次将模块包含在其他地方时调用的回调。然后,调用class_eval基本模块以运行类上下文中的方法(即作为类方法)。最棘手的部分是在这种情况下获取类名,但事实证明我可以使用base(不完全确定为什么会这样,但它有效)。

于 2013-07-10T01:08:44.647 回答