1

I have two models: User and HairColor. A user has only one hair color, but can have many hair color preferences. What's the best way to model this?

Here's what I started to do:

#models/user.rb
class User < ActiveRecord::Base
  belongs_to :hair_color
  has_many :preferences, 
  has_many :hair_colors, :through => :preferences
end

#models/hair_color.rb
class HairColor < ActiveRecord::Base 
  has_many :users
end

#models/preference.rb
class Preference < ActiveRecord::Base 
  belongs_to :user
  belongs_to :hair_color
end

Is using has_many :through the right approach? Also what if I want to extend this to other attributes such as "eye color"? (A user has one eye color, but can prefer many eye colors"

Chanpory
  • 3,015
  • 6
  • 37
  • 49

1 Answers1

1

There will be a limited amount of hair colors, so the preference table needs to have a hair_color_id and be set up like so:

#models/user.rb
class User < ActiveRecord::Base
  has_one :hair_color
  has_many :preferences
end

#models/hair_color.rb
class HairColor < ActiveRecord::Base 
  belongs_to :user
  belongs_to :preference
end

#models/preference.rb
class Preference < ActiveRecord::Base 
  belongs_to :user
  has_many :hair_color
end

I believe that's correct. Let me know if you run into any snags.


When you add eye color or any other characteristic, you'll probably have to do something different with preference. I'd have 4 columns at that point: id, user_id, foreign_id, foreign_type

foreign_id would be the id from the eye_color/hair_color table, and foreign_type would be "eye" or "hair" or something. Then in your model, you'd have something like this:

#models/hair_color.rb
class HairColor < ActiveRecord::Base 
  belongs_to :user
  has_many :preferences, :foreign_key => :foreign_id, :conditions => { "preferences.foreign_type" => "hair" }
end

That gets a little crazy, but it's the most DRY way of doing it. You'd put the same thing in your eye_color.rb and just replace "hair" with "eye" for the foreign_type.

Chuck Callebs
  • 16,293
  • 8
  • 56
  • 71
  • Thanks! Will try this out. Also, to add other preferences such as Eye Color, would I just need to add an `eye_color_id` column to the preference table? Or should I separate the preferences into distinct models, such as "HairColorPreference" and "EyeColorPreference"? Not sure which would be the most DRY. – Chanpory Dec 10 '10 at 19:29
  • Updated for further question. – Chuck Callebs Dec 10 '10 at 19:42
  • Chuck, this is great. I was tearing my hair out trying to figure out and understand the has_many :through association. This does seem like the most DRY approach. So now, I'll try it out and then attempt to get a form working for selecting preferences within a view template. New to Ruby on Rails, but hopefully it's not too hard. Thanks! – Chanpory Dec 10 '10 at 20:19
  • No problem man! Rails is really confusing when you first get started, but after a while you start to appreciate it's conventions and the "magic" that goes on behind the scenes. Good luck! – Chuck Callebs Dec 10 '10 at 20:34
  • Also, for future reference, most people I've seen use :through as simply a way to change their naming conventions. So if you wanted User.Choices, you'd have to put :has_many :choices, :through => :preferences. There are other uses for it, but don't over-estimate it's utility. – Chuck Callebs Dec 10 '10 at 20:36
  • Working on this now, and just noticed the `has_one` relationship for hair color. With this, I would need to include a `user_id` column in the `hair_colors` table. This means the hair color fields would repeat very often. Would it be better to use `belongs_to :hair_color` within the User model instead? It seems it would be more DRY this way to have the foreign key inside the users table instead. – Chanpory Dec 11 '10 at 01:48
  • Also just notice that you have `has_many :preferences` in the HairColor model instead of User model in your second example. :-) – Chanpory Dec 11 '10 at 01:51