Rails模型:你将如何创build一个预定义的属性集?
我试图找出devise一个轨道模型的最佳方法。 为了这个例子的目的,假设我正在构build一个字符数据库,它可能有几个不同的固定属性。 例如:
Character - Morality (may be "Good" or "Evil") - Genre (may be "Action", "Suspense", or "Western") - Hair Color (may be "Blond", "Brown", or "Black")
… 等等。
所以,对于angular色模型,我有几个属性,我想基本上有一个固定的可能select列表。
我希望用户能够创build一个angular色,并以我希望他们从每个可用选项中挑选一个angular色。 我也希望能够让用户使用这些属性中的每一个进行search…(例如,“向我展示'好'的angular色,从'悬念'stream派,并有'布朗'头发)。
我可以想到一些方法来做到这一点…
1:为每个属性创build一个string并validation有限的input。
在这种情况下,我将在字符表上定义一个string列“Morality”,然后在其中指定一个选项,然后对该类常量进行validation。
find好的angular色就像Character.where(:morality=>'Good')
。
这是好的,简单的,坏处是如果我想添加一些更多的细节的属性,例如有一个“好”和“邪恶”的描述,和一个页面,用户可以查看所有的字符为给定的道德。
2:为每个属性创build一个模型
在这种情况下, Character belongs_to Morality
,就会有一个Morality
模式和一个有两个logging的moralities
表: Morality id:1, name:Good
等
find好的字符就像Morality.find_by_name('Good').characters
…或者Character.where(:morality=> Morality.find(1)
。
这工作正常,但这意味着你有几个表只存在less数预定义的属性。
3:为属性创build一个STI模型
在这种情况下,我可以做同样的#2,除了创build一个通用的“CharacterAttributes”表,然后子类为“道德属性”和“GenreAttribute”等。这使得许多属性只有一个表,否则似乎大致相同作为想法#2。
所以,这是我能想到解决这个问题的三种方法。
我的问题是,你将如何实现这一点,为什么?
你会使用上面的方法之一,如果是的话,哪一个? 你会做一些不同的事情吗? 我特别希望听到你将采取的方法的性能考虑。 我知道这是一个广泛的问题,谢谢你的任何意见。
编辑:我在这个问题上增加了250(我的声望超过10%!)的赏金,因为我真的可以使用一些更广泛的优点/缺点/选项的讨论。 如果有人能给我提供一个他们采取的方法的一个非常可靠的例子,那么为什么会有价值+250。
我真的为我的应用程序的这个方面的devise苦恼,现在是时候来实现它。 预先感谢任何有帮助的讨论!
最后说明:
谢谢大家的周到和有趣的答案,他们都很好,对我很有帮助。 最后(就在赏金过期之前!)我非常赞赏Blackbird07的回答。 虽然每个人都提出了很好的build议,但对我个人而言,他是最有用的。 我之前并没有真正意识到一个枚举的概念,而且由于研究它,我发现它解决了我在应用程序中遇到的许多问题。 我鼓励每个发现这个问题的人阅读所有的答案,提供了很多好的方法。
简而言之,你在问如何枚举ActiveRecord属性。 围绕Web有很多的讨论,甚至在SO应用中使用枚举,例如在这里 , 这里或这里举几个例子。
我从来没有使用枚举的许多gem之一, 但active_enumgem听起来特别适合您的使用情况 。 它没有activerecord支持的属性集的缺点,并且使属性值的维护变得小菜一碟。 它甚至带有formtastic或简单forms(我认为可以帮助您在字符search中进行属性select)的表单助手。
我认为你将会有不止一个这样的多项select属性,并且希望保持整洁。
只有当你想在运行时修改select ,我才会build议将它存储在数据库方法中,否则它很快就会成为性能问题。 如果一个模型有三个这样的属性,则需要四次数据库调用而不是一次来检索它。
将选项硬编码为validation是一种快速的方法,但是维护起来很繁琐 。 您必须确保每个类似的validation器和下拉列表等使用匹配的值。 如果名单变长,则变得相当困难和麻烦。 如果你有2-5个select,实际上不会有太大的变化,比如male, female, unspecified
我build议你使用一个configurationYAML文件 。 这样你可以有一个整齐的文件,为您的所有select
# config/choices.yml morality: - Good - Evil genre: - Action - Suspense - Western hair_color: - Blond - Brown - Black
然后你可以加载这个文件到一个常量作为Hash
# config/initializers/load_choices.rb Choices = YAML.load_file("#{Rails.root}/config/choices.yml")
在你的模型中使用它;
# app/models/character.rb class Character < ActiveRecord::Base validates_inclusion_of :morality, in: Choices['morality'] validates_inclusion_of :genre, in: Choices['genre'] # etc… end
在视图中使用它们;
<%= select @character, :genre, Choices['genre'] %>
等等…
如果任何这些属性的变化与代码的变化有很强的联系(例如:当引入一个新的头发颜色,创build一个新的页面或实现一个新的动作),那么我会说他们添加为一个string哈希(选项1)。 您可以将它们存储在Character模型中,作为与其他元数据的最终散列值。
class Character < ActiveRecord::Base MORALITY = {:good => ['Good' => 'Person is being good'], :evil => ['Evil' => 'Person is being Evil']} ... end Character.where(:morality => Character::MORALITY[:good][0])
编辑添加评论中的代码:
鉴于Character::MORALITY = {:good => {:name => 'Good', :icon => 'good.png'}, ...
- Character::MORALITY.each do |k,v| = check_box_tag('morality', k.to_s) = image_tag(v[:icon], :title => v[:name]) = Character::MORALITY[@a_character.morality.to_sym][:name]
我的build议是使用一个NoSQL数据库,如MongoDB。
MongoDB支持embedded式文档。 embedded式文档与父文件保存在相同的条目中。 所以检索速度非常快,就像访问一个公共领域一样。 但是embedded文件可以非常丰富。
class Character include Mongoid::Document embeds_one :morality embeds_many :genres embeds_one :hair_colour index 'morality._type' index 'genres._type' end class Morality include Mongoid::Document field :name, default: 'undefined' field :description, default: '' embedded_in :character end class Evil < Morality include Mongoid::Document field :name, default: 'Evil' field :description, default: 'Evil characters try to harm people when they can' field :another_field end class Good < Morality include Mongoid::Document field :name, default: 'Good' field :description, default: 'Good characters try to help people when they can' field :a_different_another_field end
操作:
character = Character.create( morality: Evil.new, genres: [Action.new, Suspense.new], hair_colour: Yellow.new ) # very very fast operations because it is accessing an embed document character.morality.name character.morality.description # Very fast operation because you can build an index on the _type field. Character.where('morality._type' => 'Evil').execute.each { |doc| p doc.morality } # Matches all characters that have a genre of type Western. Character.where('genres._type' => 'Western') # Matches all characters that have a genre of type Western or Suspense. Character.any_in('genres._type' => ['Western','Suspense'])
这种方法的优点是,增加一种新型的道德只是增加了一个从道德inheritance的新模型。 你不需要改变任何东西。
添加新的道德types不会有任何性能损失。 索引负责维护快速的查询操作。
访问embedded字段非常快。 这就像访问一个共同的领域。
这种方法相对于YML文件的优点是可以拥有非常丰富的embedded文档。 这些文件中的每一个都可以完美地满足您的需求。 需要描述字段? 添加它。
但是我会结合这两个选项。 例如,YML文件可以用于在“select”框中使用的引用。 embedded文件给你想要的灵活性。
我将遵循2个原则:DRY,开发者对代码的幸福复杂化。
首先,预定义的Character数据将作为常量存放在模型中。 第二个是关于validation,我们将在这里做一些元编程,以及用范围进行search。
#models/character.rb class Character < ActiveRecord::Base DEFAULT_VALUES = {:morality => ['Good', 'Evil'], :genre => ['Action', 'Suspense', 'Western'], :hair_color => ['Blond', 'Brown', 'Black']} include CharacterScopes end #models/character_scopes.rb module CharacterScopes def self.included(base) base.class_eval do DEFAULT_VALUES.each do |k,v| validates_inclusion_of k.to_sym, :in => v define_method k do where(k.to_sym).in(v) end # OR scope k.to_sym, lambda {:where(k.to_sym).in(v)} end end end end #app/views/characters/form.html <% Character::DEFAULT_VALUES.each do |k,v] %> <%= select_tag :k, options_from_collection_for_select(v) %> <% end %>
对于多值的情况,一个选项是使用在FlagShihTzugem中实现的位域。 这将一些标志存储在一个整数字段中。