Rails Models: как бы вы создали предопределенный набор атрибутов? - PullRequest
38 голосов
/ 30 июня 2011

Я пытаюсь найти лучший способ спроектировать модель рельсов.Для примера предположим, что я создаю базу данных символов, которая может иметь несколько разных фиксированных атрибутов.Например:

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

... и т. Д.

Итак, для модели персонажа есть несколько атрибутов, в которых я хочу иметь фиксированный список возможных выборов.

Я хочу, чтобы пользователи могли создавать персонажей, и в форме я хочу, чтобы они выбирали одного из каждого из доступных вариантов.Я также хочу, чтобы пользователи могли выполнять поиск с использованием каждого из этих атрибутов ... (т. Е. «Показывать мне« хороших »персонажей из жанра« Suspense »и с коричневыми волосами).

Я могу придумать пару способов сделать это ...


1: создать строку для каждого атрибута и проверить ограниченный ввод.

Вв этом случае я бы определил строковый столбец «Мораль» в таблице символов, затем имел бы константу класса с указанными в ней параметрами и затем проверил ее по этой константе класса.

Поиск хороших символов будет выглядеть как Character.where(:morality=>'Good').

Это хорошо и просто, недостатком является то, что если я захочу добавить некоторые подробности к атрибуту, например, чтобы было описание "Good" и "Evil", и страницу, на которой пользователи могли быпросмотреть все символы для данной морали.

2: создать модель для каждого атрибута

В этом случае Character belongs_to Morality, будет модель Moralityи moralities таблица с двумя записями в ней: Morality id:1, name:Good и т. д.

Поиск хороших символов будет выглядеть как Morality.find_by_name('Good').characters ... или Character.where(:morality=> Morality.find(1).

Это работает нормально, но это означает, что у вас есть несколько таблиц, которые существуют только для хранения небольшого числа предопределенных атрибутов.

3: Создать модель STI для атрибутов

В этом случае я мог бы сделать то же самое, что и # 2, за исключением создания общей таблицы "CharacterAttributes" и затем подкласса.это для "MoralityAttribute", "GenreAttribute" и т. д. Это делает только одну таблицу для многих атрибутов, в противном случае она выглядит примерно так же, как идея # 2.


Итак, вот три способа, которыми я могуПодумайте, чтобы решить эту проблему.

Мой вопрос: как бы вы это реализовали и почему?

Вы бы использовали один из подходов, приведенных выше, и если да, токоторый из?Вы бы сделали что-то другое?Мне было бы особенно интересно услышать соображения по поводу эффективности вашего подхода.Я знаю, что это широкий вопрос, спасибо за любой вклад.

РЕДАКТИРОВАТЬ: Я добавляю вознаграждение 250 (более 10% моей репутации !!) по этому вопросупотому что я мог бы действительно использовать более расширенное обсуждение плюсов / минусов / вариантов.Я буду отдавать голоса тем, кто взвешивает что-то конструктивное, и если кто-то может дать мне действительно убедительный пример того, какой подход они используют и ПОЧЕМУ это будет стоить + 250.

Я действительно мучаюсьнад дизайном этого аспекта моего приложения, и теперь пришло время его реализовать.Заранее благодарим за любую полезную дискуссию !!


ЗАКЛЮЧИТЕЛЬНОЕ ПРИМЕЧАНИЕ:

Спасибо всем за вдумчивые и интересные ответы, все они хороши и былиочень полезно для меня.В конце (входящий прямо перед истечением срока действия награды!) Я действительно оценил ответ Blackbird07.В то время как все предлагали хорошие предложения, лично для меня он был самым полезным.Я действительно не знал об идее перечисления прежде, и с тех пор, как я изучил его, я обнаружил, что он решает многие проблемы, с которыми я столкнулся в своем приложении.Я призываю всех, кто узнает об этом вопросе, прочитать все ответы, есть много хороших подходов.

Ответы [ 6 ]

23 голосов
/ 14 июля 2011

Я предполагаю, что у вас будет несколько таких атрибутов с множественным выбором, и вы хотели бы сохранить порядок.

Я бы порекомендовал сохранить его в базе данных подход только , если вы хотите изменить варианты во время выполнения, иначе это быстро приведет к снижению производительности ; Если модель имеет три таких атрибута, для ее получения потребуется четыре вызова базы данных вместо одного.

Жесткое кодирование вариантов в проверки является быстрым способом, но его становится утомительным для поддержания. Вы должны убедиться, что все подобные валидаторы, выпадающие списки и т. Д. Используют совпадающие значения. И это становится довольно трудным и громоздким, если список становится длинным. Это практично, если у вас есть 2-5 вариантов, которые действительно не сильно изменятся, например male, female, unspecified

Что бы я рекомендовал , чтобы вы использовали файл конфигурации YAML . Таким образом, вы можете иметь один аккуратный документ для всех ваших выборов

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

и т.д ...

11 голосов
/ 20 июля 2011

Проще говоря, вы спрашиваете, как перечислить атрибуты ActiveRecord. В Интернете и даже в SO много дискуссий по поводу использования перечислений в rails-приложениях, например, здесь , здесь или здесь и многие другие.

Я никогда не использовал один из многих драгоценных камней для перечислений, , но active_enum gem звучит особенно подходящим для вашего варианта использования . Он не имеет недостатков набора атрибутов с поддержкой activerecord и делает поддержание значений атрибутов простым делом. Он даже поставляется с помощниками формы для формы или простой формы (которая, как я полагаю, может помочь вам при выборе атрибутов при поиске персонажа).

2 голосов
/ 14 июля 2011

Я предлагаю использовать базу данных NoSQL, такую ​​как MongoDB.

MongoDB поддерживает встроенные документы.Встроенный документ сохраняется в той же записи, что и родительский.Так что это очень быстро для поиска, это как доступ к общему полю.Но встраиваемые документы могут быть очень богатыми.

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

Этот подход имеет то преимущество, что добавление нового типа морали - это просто добавление новой модели, которая наследуется от морали.,Вам не нужно ничего менять.

Добавление новых типов морали не снижает производительности.Индекс заботится о поддержке быстрых операций запроса.

Доступ к полям встраивания очень быстрый.Это похоже на доступ к общему полю.

Преимущество этого подхода перед простым файлом YML состоит в том, что вы можете иметь очень богатые встраиваемые документы.Каждый из этих документов может идеально соответствовать вашим потребностям.Нужно описание поля?добавь это.

Но я бы объединил два варианта.Файл YML может быть очень полезен для ссылки, которую можно использовать, например, в полях выбора.Хотя встраивание документа дает вам желаемую гибкость.

2 голосов
/ 30 июня 2011

Если изменение какого-либо из этих атрибутов было бы сильно связано с изменением в коде (т. Е. Когда вводится новый цвет волос, создается новая страница или выполняется новое действие), то я бы сказал,добавьте их как строковый хеш (вариант 1).Вы можете сохранить их в модели символов в виде завершенных хэшей с другими метаданными.

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]
1 голос
/ 18 июля 2011

Я буду следовать 2 принципам: СУХОЙ, счастье разработчиков над кодом усложняет.

Прежде всего, предопределенные символьные данные будут в модели как константа. Во-вторых, о проверке, здесь мы немного метапрограммируем, а также осуществляем поиск по областям.

#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 %>
0 голосов
/ 17 ноября 2015

В случае нескольких значений одним из вариантов является использование битовых полей, реализованных в геме FlagShihTzu .Это хранит несколько флагов в одном целочисленном поле.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...