Modèles de Rails: comment créer un ensemble prédéfini d'attributs?

j'essaie de trouver la meilleure façon de concevoir un modèle de rails. Pour les besoins de l'exemple, disons que je construis une base de données de caractères, qui peut avoir plusieurs attributs fixes différents. Par exemple:

Character
- Morality (may be "Good" or "Evil")
- Genre (may be "Action", "Suspense", or "Western")
- Hair Color (may be "Blond", "Brown", or "Black")

... et ainsi de suite.

donc, pour le modèle de caractère il y a plusieurs attributs où je veux fondamentalement avoir une liste fixe de sélections possibles.

je veux que les utilisateurs soient en mesure de créer un caractère, et dans la forme que je veux chercher un de chacune des options disponibles. Je veux également être en mesure de permettre aux utilisateurs de rechercher en utilisant chacun de ces attributs... ( c'est à dire, "Montrez-moi les Personnages qui sont "Bons", de la "Suspense" genre, et ont 'Marron' cheveux).

je peux penser à deux façons de le faire...


1: créez une chaîne de caractères pour chaque attribut et validez les entrées limitées.

dans ce cas, je définirais une colonne de chaîne" moralité " sur la table de caractères, puis j'aurais une constante de classe avec les options qui y sont spécifiées, puis je validerais par rapport à cette constante de classe.

trouver de bons caractères serait comme Character.where(:morality=>'Good') .

c'est agréable et simple, l'inconvénient est si je voulais ajouter plus de détails à l'attribut, par exemple pour avoir une description de "bon" et "mal", et une page où les utilisateurs pourraient voir tout les personnages pour une morale.

2: Créer un modèle pour chaque attribut

dans ce cas Character belongs_to Morality , il y aurait un modèle Morality et une table moralities avec deux enregistrements: Morality id:1, name:Good etc.

trouver de bons caractères serait comme Morality.find_by_name('Good').characters ... ou Character.where(:morality=> Morality.find(1) .

Cela fonctionne bien, mais cela signifie que vous avez plusieurs tables qui n'existent que pour tenir un petit nombre d'attributs prédéfinis.

3: Créer un modèle STI pour les attributs

dans ce cas, je pourrais faire la même chose que #2, sauf créer un tableau général" CharacterAttributes "et ensuite le sous-classe pour" MoralityAttribute "et" GenreAttribute " etc. Cela ne fait qu'un tableau pour les nombreux attributs, sinon il semble à peu près identique à l'idée #2.


donc, ce sont les trois les moyens que je peux penser à résoudre ce problème.

ma question Est, comment mettriez-vous en œuvre ceci, et pourquoi?

utiliseriez-vous l'une des approches ci-dessus, et si oui, laquelle? Voulez-vous faire quelque chose de différent? Je serais particulièrement intéressé d'entendre des considérations de rendement pour l'approche que vous adopteriez. Je sais qu'il s'agit d'une question vaste, merci pour toute contribution.

EDIT: Je suis l'ajout d'une Prime de 250 (plus de 10% de ma réputation!!) sur cette question parce que je pourrais vraiment utiliser une discussion plus approfondie des avantages / inconvénients / options. Je vais donner des notes à quiconque qui pèse avec quelque chose de constructif, et si quelqu'un peut me donner un exemple vraiment solide de l'approche qu'ils prennent et pourquoi il sera la valeur +250.

je suis vraiment agonisant sur la conception de cet aspect de mon application et il est maintenant temps de le mettre en œuvre. Merci d'avance pour toute utile discussion!!


NOTE FINALE:

merci à tous pour vos réponses réfléchies et intéressantes, toutes sont bonnes et m'ont été très utiles. En fin de compte (arrivant juste avant le prime expiré! J'ai vraiment apprécié la réponse de Blackbird07. Alors que tout le monde a offert de bonnes suggestions, pour moi personnellement Son a été le plus utile. Je n'étais pas vraiment conscient de l'idée d'un enum avant, et depuis la recherche je trouve qu'il résout beaucoup de problèmes que j'ai eu dans mon application. J'encourage tous ceux qui découvrent cette question à lire toutes les réponses, il y a beaucoup de bonnes approches proposées.

38
demandé sur Andrew 2011-06-30 18:40:42

6 réponses

pour faire simple, vous demandez comment énumérer les attributs ActiveRecord. Il y a beaucoup de discussions autour du web et même sur ainsi pour l'utilisation des enums dans les applications de rails, par exemple ici , ici ou ici pour n'en nommer que quelques-uns.

Je n'ai jamais utilisé l'un des nombreux gemmes il ya pour les enums, mais active_enum gem sons particulièrement adapté à votre cas d'utilisation . Il n'a pas les inconvénients d'un ensemble d'attribut appuyé par activerecord et fait de la maintenance des valeurs d'attribut un jeu d'enfant. Il est même livré avec des aides de forme pour formtastic ou forme simple (qui je suppose pourrait vous aider pour la sélection d'attribut dans votre recherche de caractères).

11
répondu emrass 2017-05-23 12:34:37

je suppose que vous allez avoir plus de quelques-uns de ces choix multiples attributs, et tiens à garder les choses en ordre.

je recommande le le stocker dans la base de données approche seulement si vous voulez modifier les choix à l'exécution, sinon il serait rapidement devenu un succès de performance; si un modèle a trois tels attributs, il faudrait quatre appels de base de données au lieu d'un à le recupérer.

codifier les choix en validations est un moyen rapide, mais il devient fastidieux à maintenir. Vous devez vous assurer que chaque validateur similaire et liste déroulante etc. utilisez les valeurs correspondantes. Et cela devient assez difficile et encombrant si la liste devient longue. C'est seulement pratique si vous avez 2-5 choix qui ne changeront pas vraiment beaucoup, comme male, female, unspecified

Ce que j'avais recommander est que vous utilisez un fichier de configuration YAML . De cette façon, vous pouvez avoir un seul tidy document pour tous vos choix

# config/choices.yml

morality:
  - Good
  - Evil
genre:
  - Action
  - Suspense
  - Western
hair_color:
  - Blond
  - Brown
  - Black

alors vous pouvez charger ce fichier dans une constante comme Hash

# config/initializers/load_choices.rb

Choices = YAML.load_file("#{Rails.root}/config/choices.yml")

utilisez - le dans vos modèles;

# app/models/character.rb

class Character < ActiveRecord::Base
  validates_inclusion_of :morality, in: Choices['morality']
  validates_inclusion_of :genre, in: Choices['genre']
  # etc…
end

les utiliser dans les vues;

<%= select @character, :genre, Choices['genre'] %>

etc ...

20
répondu edgerunner 2011-07-14 18:52:19

si un changement dans l'un de ces attributs serait fortement lié à un changement dans le code (c'est-à-dire: quand une nouvelle couleur de cheveux est introduite, une nouvelle page est créée ou une nouvelle action est mise en œuvre), alors je dirais les ajouter comme un hachage de chaîne de caractères (option 1). Vous pouvez stocker dans le Caractère du modèle finalisé hachages avec d'autres méta-données.

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])

Modifier pour ajouter le code dans le commentaire:

Donné 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]
2
répondu tamersalama 2011-06-30 18:58:02

ma suggestion est d'utiliser une base de données NoSQL telle que MongoDB.

MongoDB support des documents intégrés. Un document incorporé est enregistré dans la même entrée que le parent. Donc c'est très rapide pour la récupération, c'est comme accéder à un champ commun. Mais les documents d'intégration peuvent être très riches.

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                  

opérations:

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']) 

cette approche a l'avantage que l'ajout d'un nouveau type de moralité est juste ajouter un nouveau Modèle qui hérite de la Morale. Tu n'as pas besoin de changer autre chose.

ajout de nouveaux types de moralité n'ont pas de pénalité de performance. L'index s'occupe du maintien des opérations de requête rapide.

accéder aux champs embed est très rapide. C'est comme accéder à un champ commun.

l'avantage de cette approche par rapport à un simple fichier YML est que vous pouvez avoir des documents embed très riches. Chacun de ces documents peut parfaitement grandir à vos besoins. Besoin d'un champ de description? l'ajouter.

mais je combinerais les deux options. Le fichier YML pourrait être très utile pour avoir une référence que vous pouvez utiliser dans des boîtes sélectionnées par exemple. Tout en ayant enceds document vous donne la flexibilité désirée.

2
répondu Nerian 2011-07-14 19:52:27

je vais suivre 2 principes: sec, développeurs bonheur sur code compliqué.

tout d'abord, les données de caractère prédéfinies seront dans le modèle comme une constante. Le second est sur la validation, nous allons faire un peu de métaprogrammation ici, ainsi que la recherche avec des portées.

#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 %>
1
répondu Anatoly 2011-07-18 15:28:19

pour le cas de valeurs multiples, une option est d'utiliser des champs de bits comme implémenté dans le FlagShihTzu gem. Cela stocke un certain nombre de drapeaux dans un seul champ entier.

0
répondu robd 2015-11-16 21:45:26