模型人(用户)配置文件属性/属性 - 最佳实践,静态与动态属性

时间:2013-04-08 12:45:24

标签: ruby-on-rails model

我无法轻易找到关于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模型拆分为两个,可以更加规范化上面的示例。或者你可能会提出别的建议吗?

1 个答案:

答案 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,但是我希望你得到我的漂移。