这是一个Rails / ActiveRecord问题。
我有一个基本上必须代表事件或表演的模型。每个事件都有许多属性:归属基本上类似于“在这种情况下,人员X具有角色Y”。
我得出结论,允许用户编辑此数据的最佳方法是提供一个自由文本字段,该字段需要结构化格式,我将其称为角色字符串:
singer: Elvis Costello, songwriter: Paul McCartney, ...
我使用自动完成功能来完成角色名称(歌手,词曲作者......)和人名。角色和人员都存储在数据库中。
为了实现这一点,我在Event模型中创建了一个虚拟属性:
def role_string
# assemble a role string from the associations in the model
end
def role_string=(s)
# parse a string in the above role string format,
# look up the People and Events mentioned, and update
# what's in the database
end
这一切都很好。当角色字符串格式正确并且角色字符串给出的关联都签出时,整个过程非常有效。
但是如果角色字符串格式不正确怎么办?好的,我想,我可以使用正则表达式和标准验证来检查格式:
validates_format_of :role_string, :with => /(\w+:\s*\w+)(,\s*\w+:\s*\w+)*/
但是如果角色字符串隐含的关联无效呢?例如,如果我提供上述角色字符串会发生什么情况,并且Elvis Costello
没有引用有效的人?
我想,好吧,我可以在属性validates_each
上使用:role_string
来查找关联,并在给定的名称之一与任何内容不匹配时抛出错误,例如。
我的问题是两个:首先,我不喜欢这种方法,因为要验证关联,我必须解析字符串并查找它们,这与我在role_string=
本身所做的重复,除了实际将关联保存到数据库。
其次,......我如何指出在分配给这个虚拟属性时发生了错误?
答案 0 :(得分:3)
首先,您错误地将此人归因于该事件。您应该将Person的ID传递给事件,而不是该人名的字符串。例如,如果ID为230404且名称为“Elvis Costello”的人将其姓名更改为“Britney Spears?”,该怎么办?好吧,如果发生这种情况,ID将保持不变,但名称会改变。但是,您将无法再引用该人。
您应该设置关联,以便外键在多种情况下引用人员:
has_one :singer, :class_name => "Person", :foreign_key => "singer_id"
has_one :songwriter, :class_name => "Person", :foreign_key => "songwriter_id"
这样,您可以让多个人在不同的角色下与一个事件相关联,并且您可以引用Person可能拥有的多个属性。例如:
Event.first.singer.name # => "Elvis Costello"
Event.first.songwriter.name # => "Britney Spears"
您可以研究关联的可用验证(validates_associated
),以及表单中是否存在ID(validates_presence_of
)。我建议您创建自己的自定义验证,以确保Person在before_save中有效。像(未经测试)的东西:
def before_save
unless Person.exists?(self.songwriter_id)
self.errors.add_to_base("Invalid songwriter. Please try again!")
return false
end
end
此外,我注意到您正在寻找一种方法供用户选择应该用于您的活动中的角色的用户。以下是您在表单中可以做的部分:
select(:event, :singer_id, Person.find(:all).collect {|p| [ p.name, p.id ] }, { :include_blank => 'None' })
希望这有帮助!