views:

249

answers:

2

While I'm not a complete Ruby/Rails newb, I'm still pretty green and I'm trying to figure out how to structure some model relationships. The simplest example I can think of is the idea of "recipes" for cooking.

A recipe consists of one or more ingredients and the associated quantity of each ingredient. Assume we have a master list in the database of all ingredients. That suggests two simple models:

class Ingredient < ActiveRecord::Base
  # ingredient name, 
end

class Recipe < ActiveRecord::Base
  # recipe name, etc.
end

If we just wanted to associate Recipes with Ingredients, that's as simpling as adding the appropriate belongs_to and has_many.

But what if we want to associate additional information with that relationship? Each Recipe has one or more Ingredients, but we want to indicate the quantity of the Ingredient.

What is the Rails way to model that? Is it something along the lines of a has_many through?

class Ingredient < ActiveRecord::Base
  # ingredient name
  belongs_to :recipe_ingredient
end

class RecipeIngredient < ActiveRecord::Base
  has_one :ingredient
  has_one :recipe
  # quantity
end

class Recipe < ActiveRecord::Base
  has_many :recipe_ingredients
  has_many :ingredients, :through => :recipe_ingredients
end
A: 

http://railsbrain.com/api/rails-2.3.2/doc/index.html?a=M001888&amp;name=has_and_belongs_to_many

http://railsbrain.com/api/rails-2.3.2/doc/index.html?a=M001885&amp;name=has_many

How about:

  1. class Ingredient (belongs to recipe, has many ingredientrecipecounts)
  2. class Recipe (has many ingredients, has many ingredientrecipecounts)
  3. class IngredientRecipeCount (belongs to ingredient, belongs to recipe)

This is not so much the Rails way as just establishing one more relation between the data in the database. It's not really a "has and belongs to many" because each ingredient only has one count per recipe, and each recipe one count per ingredient.. Which is the same count.

Trevoke
+3  A: 

Recipes and Ingredients have a has and belongs to many relationship, but you want to store additional information for link.

Essentially what you are looking for is a rich join model. But, a has_and_belongs_to_many relationship is not flexible enough to store the additional information you require. Instead you will need to use a has_many :through relatinship.

This is how I would set it up.

recipes columns: instructions

class Recipe < ActiveRecord::Base
  has_many :recipe_ingredients
  has_many :ingredients, :through => :recipe_ingredients
end

recipe_ingredients columns: recipe_id, ingredient_id, quantity

class RecipeIngredients < ActiveRecord::Base
  belongs_to :recipe
  belongs_to :ingredient
end

ingredient columns: name

class Ingredient < ActiveRecord::Base
  has_many :recipe_ingredients
  has_many :recipes, :through => :recipe_ingredients
end

This will provide a basic representation of what you're looking to do. You may want to add a validation to RecipeIngredients to ensure that each ingredient is listed once per recipe, and a callback to fold duplicates into one entry.

EmFi
Thanks. That's the kind of info I was hoping for! Can you explain something though? Why does the join model, RecipeIngredients, use "belongs_to" instead of "has_one"?
organicveggie
The short version is that's the way join models work. The long version is, each side of a relationship must have a belongs_to and a has_one/has_many side. The model that belongs_to another model must have a foreign key (other_model_id) to link it to an instance of the other model. Belongs_to usually denotes a dependent relationship. In your case a RecipeIngredient has no meaning unless it belongs to a recipe and an ingredient. Where as Ingredients and Recipes have meaning without being linked to a RecipeIngredient.
EmFi