我无法轻易找到关于SO的相关主题所以我们走了 - 一个经典的问题:我有一个User或Person模型,我想模拟那个人的物理属性/属性(眼睛颜色,头发颜色,肤色) ,性别,一些生活方式属性,如每晚睡眠时间(<8小时,~8小时,> 8小时),吸烟,每日日晒等。
我通常通过为每个属性创建一个单独的rails模型(数据库表)来解决这个问题,因为之后很容易添加更多选项,编辑它们,用作<select>
的源,即
class Person
belongs_to :eye_color
belongs_to :skin_color
belongs_to :hair_color
belongs_to :sleep_per_night
belongs_to :sun_exposure
attr_accessible :gender # boolean, m/f
end
class HairColor
has_many :people
attr_accessible :value
end
class EyeColor
has_many :people
attr_accessible :value
end
Person.last.eye_color
...
EyeColor.first.people
但如果有很多属性(即10-15种不同的phisycal和生活方式属性)会怎样。对我来说,似乎打破了DRY规则,也就是说,我留下了很多小表,比如eye_colors
,它们有3-5条记录。每个表只有一个有意义的列 - 值。
我在考虑你们如何解决这些问题,可能是通过创建一个模型,即具有以下结构的PersonProperty
person_properties[type, value]
所以以前使用单独模型的解决方案,即eye_color和hair_color看起来像这样(类型/类和值):
# PersonProperty/person_properties:
1. type: 'HairColor', value: 'red'
2. type: 'HairColor', value: 'blond'
3. type: 'SkinColor', value: 'white'
4. type: 'EyeColor', value: 'green'
5. type: 'HairColor', value: 'black'
6. type: 'SkinColor', value: 'yellow'
7. type: 'SleepPerNight', value: 'less than 8h'
8. type: 'SleepPerNight', value: 'more than 8h'
9. type: 'DailySunExposure', value: 'more than 1h'
...
19. type: 'EyeColor', value: 'blue'
...
通过将PersonProperty模型拆分为两个,可以更加规范化上面的示例。或者你可能会提出别的建议吗?
答案 0 :(得分:2)
在这种情况下,我不会建议使用has_one关系。用户可以belongs_to :eye_color
,因此您可以在用户之间映射眼睛颜色。一个EyeColor has_many :users
,这样你就可以@eye_color.users
并让所有用户使用特定的EyeColor。否则,您必须为每个用户(或至少是有眼睛的用户)创建一个EyeColor。
我建议您使用PersonProperty解决方案的原因是因为它更容易维护,并且因为将这些类型的关系委托给您的数据库会带来性能提升。
更新:如果您想要动态属性,我建议您按照以下方式设置模型:
class Person < ActiveRecord::Base
has_many :person_attributes
attr_accessible :gender
accepts_nested_attributes_for :person_attributes
end
class PersonAttribute < ActiveRecord::Base
belongs_to :person_attribute_type
belongs_to :person_attribute_value
belongs_to :person
attr_accessible :person_id, :person_attribute_value_id
end
class PersonAttributeValue < ActiveRecord::Base
has_many :person_attributes
belongs_to :person_attribute_type
attr_accessible :value, :person_attribute_type_id
end
class PersonAttributeType < ActiveRecord::Base
has_many :person_attribute_values
attr_accessible :name, :type
end
这样您就可以执行以下操作:
@person_attribute_type = PersonAttributeType.create(:name => 'Eye color', :type => 'string')
['green', 'blue', 'brown'].each do |color|
@person_attribute_type.person_attribute.values.build(:value => color)
end
@person_attribute_type.save
@person = Person.new
@person_attribute = @person.person_attributes.build
@person_attribute.person_attribute_value = @person_attribute_type.person_attribute_values.find(:value => 'green')
当然,您可能无法通过命令行填充数据库。您可能会非常好奇这将如何以一种形式起作用:
class PersonController
# ...
def new
@person = Person.new
PersonAttributeType.all.each do |type|
@person.person_attributes.build(:person_attribute_type = type)
end
end
def create
@person = Person.new(params[:person])
if @person.save
# ...
else
# ...
end
end
def edit
@person = Person.find(params[:id])
PersonAttributeType.where('id NOT IN (?)', @person.person_attributes.map(&:person_attribute_type_id)).each do |type|
@person.person_attributes.build(:person_attribute_type = type)
end
end
# ...
现在的形式,基于Formtastic:
semantic_form_for @person do |f|
f.input :gender, :as => :select, :collection => ['Male', 'Female']
f.semantic_fields_for :person_attributes do |paf|
f.input :person_attribute_value, :as => :select, :collection => paf.object.person_attribute_type.person_attributes_values, :label => paf.object.person_attribute_type.name
end
f.buttons
end
请注意这一切都是未经测试的,所以试着去理解我在这里要做的事情。
BTW,我现在意识到班级人物 属性 可能有点不走运,因为你必须accepts_nested_attributes_for :person_attributes
这意味着你必须attr_accessible :person_attributes_attributes
,但是我希望你得到我的漂移。