Question

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
Was it helpful?

Solution

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.

OTHER TIPS

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

http://railsbrain.com/api/rails-2.3.2/doc/index.html?a=M001885&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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top