Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 884
Andrew Avatar asked Jun 30 '11 14:06

Andrew


People also ask

What are rails attributes?

In Rails 5, model attributes go through the attributes API when they are set from user input (or any setter) and retrieved from the database (or any getter). Rails has used an internal attributes API for it's entire lifetime. When you set an integer field to “5”, it will be cast to 5.

What are virtual attributes rails?

What is 'Virtual Attribute'? The Virtual Attribute is a class attribute, which has no representation in the database. It becomes available after object initialization and remains alive while the object itself is available (like the instance methods).

What is a model in a Rails application?

A Rails Model is a Ruby class that can add database records (think of whole rows in an Excel table), find particular data you're looking for, update that data, or remove data. These common operations are referred to by the acronym CRUD--Create, Remove, Update, Destroy.


2 Answers

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…

like image 149
edgerunner Avatar answered Oct 16 '22 09:10

edgerunner


Put simply, you're asking how to enumerate ActiveRecord attributes. There are a lot of discussions around the web and even on SO for using enums in rails applications, e.g. here, here or here to name a few.

I never used one of the many gems there are for enums, but active_enum gem sounds particularly suited for your use case. It doesn't have the downsides of an activerecord-backed attribute set and makes maintenance of attribute values a piece of cake. It even comes with form helpers for formtastic or simple form (which I assume could help you for attribute selection in your character search).

like image 30
emrass Avatar answered Oct 16 '22 08:10

emrass