update rails new inner includes active ruby-on-rails ruby-on-rails-3 activerecord attributes model

ruby on rails - rails - Modelos de rieles: ¿cómo crearías un conjunto predefinido de atributos?



scope rails (6)

Estoy tratando de descubrir la mejor manera de diseñar un modelo de rieles. A los fines del ejemplo, digamos que estoy creando una base de datos de caracteres, que puede tener varios atributos fijos diferentes. Por ejemplo:

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

... y así.

Entonces, para el modelo de caracteres hay varios atributos donde básicamente quiero tener una lista fija de posibles selecciones.

Quiero que los usuarios puedan crear un personaje, y en el formulario quiero que escojan uno de cada una de las opciones disponibles. También deseo poder permitir que los usuarios busquen utilizando cada uno de estos atributos ... (es decir, "Muéstrenme caracteres que sean ''Buenos'', del género ''Suspenso'', y que tengan pelo ''marrón'').

Puedo pensar en un par de maneras de hacer esto ...

1: crea una cadena para cada atributo y valida la entrada limitada.

En este caso, definiría una columna de cadena "Moralidad" en la tabla de caracteres, luego tendría una constante de clase con las opciones especificadas en ella, y luego validaría contra esa constante de clase.

Encontrar buenos personajes sería como Character.where(:morality=>''Good'') .

Esto es agradable y simple, la desventaja es si quería agregar más detalles al atributo, por ejemplo tener una descripción de "Bueno" y "Malvado", y una página donde los usuarios puedan ver todos los personajes de una moralidad dada .

2: crea un modelo para cada atributo

En este caso, el Character belongs_to Morality , habría un modelo de Morality y una tabla de moralities con dos registros: Morality id:1, name:Good etc.

Encontrar buenos personajes sería como Morality.find_by_name(''Good'').characters Character.where(:morality=> Morality.find(1) ... o Character.where(:morality=> Morality.find(1) .

Esto funciona bien, pero significa que tiene varias tablas que existen solo para contener una pequeña cantidad de atributos predefinidos.

3: crear un modelo de STI para los atributos

En este caso podría hacer lo mismo que # 2, excepto crear una tabla general "CharacterAttributes" y luego subclasificarla para "MoralityAttribute" y "GenreAttribute", etc. Esto hace solo una tabla para los muchos atributos, de lo contrario parece ser la misma como idea # 2.

Entonces, esas son las tres formas en que puedo pensar para resolver este problema.

Mi pregunta es, ¿cómo implementarías esto y por qué?

¿Utilizarías uno de los enfoques anteriores y, en caso afirmativo, cuál? ¿Harías algo diferente? Especialmente me interesaría escuchar las consideraciones de rendimiento para el enfoque que tomaría. Sé que esta es una pregunta amplia, gracias por cualquier aporte.

EDITAR: Estoy agregando un Bounty de 250 (¡más del 10% de mi reputación!) En esta pregunta porque realmente podría usar una discusión más extensa de pros / cons / options. Daré votos a todos los que analicen algo constructivo, y si alguien puede darme un ejemplo muy sólido de qué enfoque adoptan y POR QUÉ valdrá +250.

Estoy realmente angustiado por el diseño de este aspecto de mi aplicación y ahora es el momento de implementarlo. Gracias de antemano por cualquier discusión útil !!

NOTA FINAL:

Gracias a todos por sus respuestas reflexivas e interesantes, todas ellas son buenas y muy útiles para mí. Al final (¡justo antes de que expirara la recompensa!) Realmente aprecié la respuesta de Blackbird07. Si bien todos ofrecieron buenas sugerencias, para mí, personalmente, la suya fue la más útil. Realmente no estaba al tanto de la idea de una enumeración antes, y desde que la busqué descubrí que resuelve muchos de los problemas que he tenido en mi aplicación. Animaría a todos los que descubran esta pregunta a leer todas las respuestas, se ofrecen muchos buenos enfoques.


En pocas palabras, usted está preguntando cómo enumerar los atributos de ActiveRecord. Hay muchas discusiones en la web e incluso en SO para usar enumeraciones en aplicaciones de rieles, por ejemplo here , here o here para nombrar algunas.

Nunca utilicé una de las muchas gemas que hay para enumeraciones, pero active_enum gem suena particularmente adecuado para su caso de uso . No tiene las desventajas de un conjunto de atributos respaldados por registros activos y hace que el mantenimiento de los valores de los atributos sea sencillo. Incluso viene con ayudantes de formulario para formtastic o simple (que supongo que podría ayudarte a seleccionar los atributos en tu búsqueda de personajes).


Mi sugerencia es usar una base de datos NoSQL como MongoDB.

MongoDB admite documentos incrustados. Un documento incrustado se guarda en la misma entrada que el padre. Por lo tanto, es muy rápido para la recuperación, es como acceder a un campo común. Pero los documentos insertados pueden ser muy ricos.

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

Operaciones:

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

Este enfoque tiene la ventaja de que agregar un nuevo tipo de Moralidad es simplemente agregar un nuevo Modelo que hereda de la Moralidad. No necesitas cambiar nada más.

Agregar nuevos tipos de Morality no tiene ninguna penalización de rendimiento. El índice se ocupa de mantener las operaciones de consultas rápidas.

Acceder a los campos de inserción es muy rápido. Es como acceder a un campo común.

La ventaja de este enfoque sobre solo un archivo YML es que puede tener documentos embebidos muy ricos. Cada uno de estos documentos puede crecer perfectamente según sus necesidades. ¿Necesitas un campo de descripción? agregarlo

Pero combinaría las dos opciones. El archivo YML podría ser muy útil para tener una referencia que pueda usar en cuadros de selección, por ejemplo. Si bien tener el documento de incrustaciones le da la flexibilidad deseada.


Para el caso de valores múltiples, una opción es usar los campos de bit implementados en la gema FlagShihTzu . Esto almacena un número de banderas en un solo campo entero.


Seguiré 2 principios: DRY, la felicidad de los desarrolladores sobre el código se complica.

En primer lugar, los datos de caracteres predefinidos estarán en el modelo como una constante. El segundo es acerca de la validación, haremos un poco de metaprogramación aquí, así como la búsqueda con alcances.

#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 %>


Si un cambio en cualquiera de estos atributos estaría fuertemente ligado a un cambio en el código (es decir: cuando se introduce un nuevo color de cabello, se crea una nueva página o se implementa una nueva acción), entonces yo añadiría agregarlos como un hash de cadena (opción 1). Puede almacenarlos en el modelo de caracteres como hashes finalizados con otros metadatos.

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

Editar para agregar el código del comentario:

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


Supongo que va a tener más que unos pocos de estos atributos de opción múltiple, y me gustaría mantener las cosas ordenadas.

Recomendaría almacenarlo en el enfoque de base de datos solo si desea modificar las opciones en tiempo de ejecución, de lo contrario, se convertiría rápidamente en un golpe de rendimiento; Si un modelo tiene tres de estos atributos, tomaría cuatro llamadas a la base de datos en lugar de una para recuperarlo.

La codificación de las elecciones en validaciones es una manera rápida, pero se vuelve tedioso de mantener. Debe asegurarse de que cada validador y lista desplegable similares, etc., usen valores coincidentes. Y se vuelve bastante difícil y engorroso si la lista se vuelve larga. Es solo práctico si tiene 2-5 opciones que realmente no cambiarán mucho, como male, female, unspecified

Lo que recomendaría es que use un archivo de configuración YAML . De esta forma, puede tener un único documento ordenado para todas sus opciones

# config/choices.yml morality: - Good - Evil genre: - Action - Suspense - Western hair_color: - Blond - Brown - Black

Entonces puedes cargar este archivo en una constante como Hash

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

Úselo en sus modelos;

# app/models/character.rb class Character < ActiveRecord::Base validates_inclusion_of :morality, in: Choices[''morality''] validates_inclusion_of :genre, in: Choices[''genre''] # etc… end

Úselos en vistas;

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

etc ...