Rails Models: how would you create a pre-defined set of attributes?

Andrew picture Andrew · Jun 30, 2011 · Viewed 8.7k times · Source

I'm trying to figure out the best way to design a rails model. For purposes of the example, let's say I'm building a database of characters, which may have several different fixed attributes. For instance:

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

... and so on.

So, for the Character model there are several attributes where I want to basically have a fixed list of possible selections.

I want users to be able to create a character, and in the form I want them to pick one from each of the available options. I also want to be able to let users search using each of these attributes... ( ie, "Show me Characters which are 'Good', from the 'Suspense' genre, and have 'Brown' hair).

I can think of a couple ways to do this...


1: Create a string for each attribute and validate limited input.

In this case I would define an string column "Morality" on the character table, then have a class constant with the options specified in it, and then validate against that class constant.

Finding good characters would be like Character.where(:morality=>'Good').

This is nice and simple, the downside is if I wanted to add some more detail to the attribute, for instance to have a description of "Good" and "Evil", and a page where users could view all the characters for a given morality.

2: Create a model for each attribute

In this case Character belongs_to Morality, there would be a Morality model and a moralities table with two records in it: Morality id:1, name:Good etc.

Finding good characters would be like Morality.find_by_name('Good').characters... or Character.where(:morality=> Morality.find(1).

This works fine, but it means you have several tables that exist only to hold a small number of predefined attributes.

3: Create a STI model for attributes

In this case I could do the same as #2, except create a general "CharacterAttributes" table and then subclass it for "MoralityAttribute" and "GenreAttribute" etc. This makes only one table for the many attributes, otherwise it seems about the same as idea #2.


So, those are the three ways I can think of to solve this problem.

My question is, how would you implement this, and why?

Would you use one of the approaches above, and if so which one? Would you do something different? I'd especially be interested to hear performance considerations for the approach you would take. I know this is a broad question, thank you for any input.

EDIT: I'm adding a Bounty of 250 (more than 10% of my reputation!!) on this question because I could really use some more extended discussion of pros / cons / options. I'll give upvotes to anyone who weighs in with something constructive, and if someone can give me a really solid example of which approach they take and WHY it'll be worth +250.

I'm really agonizing over the design of this aspect of my app and it's now time to implement it. Thanks in advance for any helpful discussion!!


FINAL NOTE:

Thank you all for your thoughtful and interesting answers, all of them are good and were very helpful to me. In the end (coming in right before the bounty expired!) I really appreciated Blackbird07's answer. While everyone offered good suggestions, for me personally his was the most useful. I wasn't really aware of the idea of an enum before, and since looking into it I find it solves many of the issues I've been having in my app. I would encourage everyone who discovers this question to read all the answers, there are many good approaches offered.

Answer

edgerunner picture edgerunner · Jul 14, 2011

I assume that you are going to have more than a few of these multiple-choice attributes, and would like to keep things tidy.

I would recommend the store it in the database approach only if you want to modify the choices at runtime, otherwise it would quickly become a performance hit; If a model has three such attributes, it would take four database calls instead of one to retreive it.

Hardcoding the choices into validations is a fast way, but it becomes tedious to maintain. You have to make sure that every similar validator and drop-down list etc. use matching values. And it becomes quite hard and cumbersome if the list becomes long. It's only practical if you have 2-5 choices that really won't change much, like male, female, unspecified

What I'd recommend is that you use a configuration YAML file. This way you can have a single tidy document for all your choices

# config/choices.yml

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

Then you can load this file into a constant as a Hash

# config/initializers/load_choices.rb

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

Use it in your models;

# app/models/character.rb

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

Use them in views;

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

etc…