38 votes

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

Je suis à essayer de comprendre la meilleure façon de concevoir un rails de modèle. Pour les fins de l'exemple, disons que je suis en train de construire une base de données de caractères, qui peuvent avoir différents des attributs fixes. 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.

Ainsi, pour le modèle de Personnage il y a plusieurs attributs où je veux en gros de fixer une liste de choix possibles.

Je veux que les utilisateurs soient en mesure de créer un personnage, et dans la forme que je veux chercher un de chacune des options disponibles. Je veux aussi être capable de laisser les utilisateurs de la recherche à l'aide de 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 à un couple de façons de le faire...


1: Créer une chaîne de caractères pour chaque attribut et de valider la participation limitée.

Dans ce cas, je voudrais définir une colonne de type chaîne "Moralité" sur la table de caractères, alors une constante de classe avec les options spécifiées dans elle, puis valider à l'encontre de cette constante de classe.

Trouver de bons personnages serait comme Character.where(:morality=>'Good').

C'est simple et efficace, l'inconvénient est que si je voulais ajouter un peu plus de détails pour l'attribut, par exemple, une description de "Bons" et "méchants", et une page où les utilisateurs pourraient afficher tous 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 Morality modèle moralities tableau avec deux dossiers qu'il contient: Morality id:1, name:Good etc.

Trouver de bons personnages 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 STI modèle pour les attributs

Dans ce cas, je pourrais faire la même chose que #2, à l'exception de la création d'un "CharacterAttributes" la table, puis la sous-classe pour "MoralityAttribute" et "GenreAttribute" etc. Cela ne fait qu'une seule table pour les nombreux attributs, sinon, il semble sur la même comme idée #2.


Donc, ce sont les trois façons que je peux penser à résoudre ce problème.

Ma question est, comment voulez-vous mettre en oeuvre, et pourquoi?

Voulez-vous utiliser l'une des méthodes ci-dessus, et si oui, lequel? Voulez-vous faire quelque chose de différent? Je serais particulièrement intéressé à entendre les facteurs de performance de l'approche que vous prenez. Je sais que c'est une vaste question, je vous remercie pour toute entrée.

EDIT: Je vais ajouter une Prime de 250 (plus de 10% de ma réputation!!) sur cette question, parce que je ne pouvais vraiment utiliser un peu plus de discussion étendue des avantages / inconvénients / options. Je vais vous donner upvotes à toute personne qui se pèse avec quelque chose de constructif, et si quelqu'un peut me donner un excellent exemple de l'approche qu'ils prennent et POURQUOI il va être intéressant de +250.

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


NOTE FINALE:

Merci à tous pour votre réfléchie et intéressante réponses, elles sont toutes bonnes et ont été très utiles pour moi. En fin de compte (arrivant juste avant le prime expiré!) J'ai vraiment apprécié Blackbird07 de réponse. Alors que tout le monde a offert de bonnes suggestions, pour moi, personnellement sa a été la plus utile. Je n'étais pas vraiment au courant de l'idée d'un enum avant, et depuis que la recherche en elle je trouve qu'il résout de nombreux problèmes que j'ai eu dans mon application. J'encourage tout le monde qui découvre que cette question de lire toutes les réponses, il y a beaucoup de bonnes approches proposées.

23voto

edgerunner Points 9581

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

Je vous recommande de les stocker dans la base de données approche uniquement si vous souhaitez modifier le choix au moment de l'exécution, sinon il devient rapidement un gain de performance; Si un modèle a trois de ces attributs, il faudrait de quatre appels de base de données au lieu d'une pour le recupérer.

Coder en dur le choix dans les validations est un moyen rapide, mais il devient fastidieux à entretenir. Vous devez vous assurer que tous semblables programme de validation et de liste déroulante etc. l'utilisation des valeurs correspondantes. Et il devient assez difficile et fastidieux si la liste est longue. C'est pratique si vous avez de 2 à 5 choix qui ne fera pas beaucoup de changement, comme male, female, unspecified

Ce que je recommande , c'est que vous utilisez une configuration de fichier YAML. De cette façon, vous pouvez avoir une seule rangée de document pour l'ensemble de vos choix

# config/choices.yml

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

Ensuite, vous pouvez charger ce fichier dans une constante en Hash

# config/initializers/load_choices.rb

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

L'utiliser 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 des vues;

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

etc...

11voto

emrass Points 3394

Mettez tout simplement, vous vous demandez comment énumérer ActiveRecord attributs. Il y a beaucoup de discussions sur le web et même sur pour utiliser les énumérations dans les applications rails, par exemple ici, ici ou ici pour en nommer quelques-uns.

Je n'ai jamais utilisé l'un des nombreux joyaux sont pour les enums, mais active_enum gem sons particulièrement adapté à votre cas d'utilisation. Il n'a pas les inconvénients d'un activerecord adossés ensemble d'attributs et rend la maintenance de valeurs d'attribut d'un morceau de gâteau. Il vient même avec la forme des aides pour formtastic ou de forme simple (qui, je suppose pourrait vous aider pour la sélection d'attributs dans votre recherche de caractère).

2voto

tamersalama Points 1915

Si une modification de l'un de ces attributs était fortement liée à une modification du code (par exemple, lorsqu'une nouvelle couleur de cheveux est introduite, une nouvelle page est créée ou une nouvelle action est mise en œuvre), je dirais alors de les ajouter en tant que une chaîne de hachage (option 1). Vous pouvez les stocker dans le modèle de personnage sous la forme d'un hachage finalisé 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 du commentaire:

Étant 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]
 

2voto

Nerian Points 6733

Ma suggestion est d'utiliser une base de données NoSQL comme MongoDB.

MongoDB soutenir les documents incorporés. Un document incorporé est enregistré dans la même entrée que le parent. Il est donc très rapide pour la récupération, c'est comme un accès à un champ commun. Mais intégrer les documents 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 de l'ajout d'un nouveau type de Moralité est simplement l'ajout d'un nouveau Modèle qui hérite de la Morale. Vous n'avez pas besoin de changer quoi que ce soit d'autre.

L'ajout de nouveaux Moralité types n'ont pas toute l'exécution de la peine. L'indice de prendre soin de maintaing rapide opérations de requête.

Accéder à l'incorporer des champs est très rapide. C'est comme un accès à un champ commun.

L'avantage de cette approche sur un fichier YML est que vous pouvez avoir très riche intégrer les documents. Chacun de ces documents peut grandissent parfaitement à vos besoins. Besoin d'un champ de description? l'ajouter.

Mais je tiens à combiner les deux options. Le fichier YML pourrait être très utile pour avoir une référence que vous pouvez utiliser dans les listes de sélection par exemple. Tout en ayant intègre document vous donne de la flexibilité souhaitée.

1voto

mikhailov Points 4981

Je vais suivre 2 principes: DRY, le bonheur des développeurs sur le code compliquent.

Tout d’abord, les données de caractère prédéfinies seront constantes dans le modèle. La seconde concerne 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 %>
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X