我的ActiveRecord模型中有以下类:
def Property < ActiveRecord::Base
# attribute: value_type (can hold values like :integer, :string)
end
def PropertyValue < ActiveRecord::Base
belongs_to property
# attribute: string_value
# attribute: integer_value
end
PropertyValue对象仅用于保存字符串值或整数值,具体取决于在关联的Property对象的value_type属性中指定的类型。显然,我们不应该使用这个底层的string_value / integer_value机制来打扰PropertyValue类的用户。所以我想在PropertyValue上使用虚拟属性“value”,它可以这样做:
def value
unless property.nil? || property.value_type.nil?
read_attribute((property.value_type.to_s + "_value").to_sym)
end
end
def value=(v)
unless property.nil? || property.value_type.nil?
write_attribute((property.value_type.to_s + "_value").to_sym, v)
end
end
我想为用户提供一个视图来填充一堆属性值,当发布视图时,我想根据从视图传入的属性列表实例化PropertyValue对象。我习惯使用build(attributes)操作。但是,现在出现的问题是我无法控制属性初始化发生的顺序。因此,当尚未建立与Property属性的关联时,value属性的赋值将不起作用,因为无法确定value_type。什么是正确的“Rails”方式来处理这个问题?
BTW,作为一种解决方法,我尝试了以下方法:def value=(v)
if property.nil? || property.value_type.nil?
@temp_value = v
else
write_attribute((property.value_type.to_s + "_value").to_sym, v)
end
end
def after_initialize
value = @temp_value
end
除了我认为这是一个非常丑陋的解决方案之外,它实际上并不适用于“构建”操作。 @temp_value在“value =(v)”操作中设置。此外,执行中的“after_initialize”。 但是,“value = @temp_value”并没有奇怪地调用“value =(v)”操作!所以我真的被卡住了。
编辑:构建代码 我确实意识到构建Property对象的代码会很方便。我是从Product类做的,它与Property有一个has_many关联。然后代码如下所示:
def property_value_attributes=(property_value_attributes)
property_value_attributes.each do |attributes|
product_property_values.build(attributes)
end
end
同时我弄清楚我在after_initialize操作中做错了什么;它应该是:
def after_initialize
@value = @temp_value
end
另一个问题是新建的property_value对象上的属性关联永远不会被设置,直到实际的save()发生,这是在“after_initialize”之后。我通过将相应属性对象的value_type添加到视图中然后通过post上设置的属性传入它来实现此功能。这样我就不必实例化一个Property对象来获取value_type。缺点:我需要在PropertyValue类上使用冗余的“value_type”访问器。
所以它有效,但我仍然非常感兴趣,如果有更清洁的方法来做到这一点。另一种方法是确保在使用其他属性初始化属性对象之前首先将属性对象附加到新的PropertyValue,但随后该机制泄漏到“客户端对象”中,这也不会太干净。
我希望某种方式可以覆盖初始化程序功能,这样我就可以影响属性的分配顺序。在C#或Java等语言中非常常见的东西。但在Rails中......?
答案 0 :(得分:1)
一个选项是先保存Property对象,然后再添加PropertyValue对象。如果需要,可以将整个事物包装在事务中,以确保在无法保存相应的PropertyValues时回滚属性。
我不知道您从表单中收集的数据是什么样的,但假设它如下所示:
@to_create = { :integer => 3, :string => "hello", :string => "world" }
你可以这样做:
Property.transaction do
@to_create.keys.each do |key|
p = Properties.create( :value_type => key.to_s )
p.save
pval = p.property_value.build( :value => @to_create[key] )
pval.save
end
end
这样您就不必担心对Property或Property.value_type进行nil检查。
作为旁注,你确定首先需要做这一切吗?我见过的大多数数据库设计都有这种非常通用的元信息最终是高度不可扩展的,并且几乎总是错误的解决方案。它需要大量的连接才能获得一组相对简单的信息。
假设您有一个包含属性/值对的父类Foo。如果Foo有十个属性,则需要20个连接。这是很多数据库开销。
除非你真的需要对PropertyValues运行SQL查询(例如“获取所有具有属性”栏的“垃圾”),你可以通过向Foo添加一个名为“properties”的属性来简化这一过程,然后序列化你的属性哈希并将其放在该字段中。这将简化您的代码,数据库设计,并加快您的应用程序。
答案 1 :(得分:1)
哦,jeeezzzz ...这非常简单,现在我对此感到困惑。我只需要覆盖PropertyValue类上的“initialize(attributes = {})”方法,如下所示:
def initialize(attributes = {})
property = Property.find(attributes[:property_id]) unless attributes[:property_id].blank?
super(attributes)
end
现在,我始终确保在设置其他属性之前填充属性关联。我很快就没有意识到Rails的“build(attributes = {})”和“create(attributes = {})”操作最终归结为“new(attributes = {})”。
答案 2 :(得分:0)
可能你应该尝试使用ActiveRecord get / set方法,即:
def value
send("#{property.value_type}_value") unless property || property.value_type
end
def value=(v)
send("#{property.value_type}_value=", value) unless property || property.value_type
end