0

我无法轻易找到关于 SO 的相关主题,所以我们开始了 - 一个经典问题:我有一个用户或个人模型,我想为那个人的物理属性/属性(眼睛颜色、头发颜色、肤色、性别、一些生活方式属性,例如每晚的睡眠时间(<8h、~8h、>8h)、吸烟、每天晒太阳等。

我通常通过为每个属性创建一个单独的 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 种不同的物理和生活方式属性)怎么办。对我来说,这似乎打破了 DRY 规则,也就是说,我留下了许多小表,比如eye_colors3-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 模型一分为二,上述示例可能会更加规范化。或者,也许你建议别的东西?

4

1 回答 1

2

在这种情况下,我不建议使用 has_one 关系。一个 User 可以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

请注意,这一切都未经测试,因此请尝试了解我在这里要做什么。

顺便说一句,我现在意识到类名 Person Attribute 可能有点不走运,因为你必须这样做accepts_nested_attributes_for :person_attributes意味着你必须这样做attr_accessible :person_attributes_attributes,但我希望你明白我的意思。

于 2013-04-08T14:15:54.767 回答