我定义了以下类,其中包含许多具有微小变化的常见代码。
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等),然后在具体类中引用它们。
到目前为止,我已经尝试过,
组合 - 我尝试在模块中定义公共代码,然后在具体类中使用include或extend来访问它,如下所示。
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类方法?或者,是否有更好的方法来实现相同的目标?
答案 0 :(得分:1)
你的第一个选择实际上是最好的选择。 Rails使用单表继承,这意味着所有子类的数据都保存在同一个表中,这就是您遇到错误的原因。
你应该做的是创建一个名为Component
的新模型,并在其中添加所有组件中通用的所有字段以及一个名为type
的额外字段,该字段应为字符串字段。
您的组件模型将具有所有常用字段,逻辑和验证。
class Component < ActiveRecord::Base
...
end
然后让每个组件类子类化为Component。
class ThirdPartyComponent < Component
...
end
答案 1 :(得分:0)
问题是您需要belongs_to方法在包含模块的类上运行,而不是模块本身。
查看包含http://www.ruby-doc.org/core-2.0/Module.html#method-i-included的模块#,这将允许您在包含模块的模块上运行代码。 Remmeber认为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
答案 2 :(得分:0)
我在Extending a Ruby class with a standalone piece of code偶然发现了以下答案,并通过一些实验让它发挥作用。所以最终的代码是,
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
(不完全确定为什么会出现这种情况但它有效)。