I have a table called recipes and another called ingredients. Ingredients can either connect to foods or recipes (one recipe can contain other recipes). I have implemented it with the food part of it but I'm not sure how to implement the recipe part of it. I created another table called the food part of it called recipe_as_ingredients which I will neet to populate when a recipe is saved with the recipes selected as ingredients in the current recipe. I would probably have to update the table before each save and manually delete lines if deleted for the recipes. Is there a better way of implemnting it?
Here are the models:
class Recipe < ActiveRecord::Base
has_many :ingredients
end
class Ingredient < ActiveRecord::Base
belongs_to :element, :polymorphic => true
belongs_to :recipe
end
class Food < ActiveRecord::Base
has_many :ingredients, :as => :element
end
class RecipeAsIngredient < ActiveRecord::Base
has_many :ingredients, :as => :element
end
I manually set the element type in a function before saving so I would need to to similar for a recipe as ingredient but make sure that recipe_as_ingredient contains a record to connect to the ingredient:
self.element_id = numid.id
self.element_type = 'Food'
p.s. I could not connect the recipe without the recipe_as_ingredients connection tables because it made saving the record impossible because recipe was both a master record and detail record and the id field was always nil.
You're so close.
Move:
has_many :ingredients, :as => :element
Onto the Recipe model.
RecipeAsIngrident should exist. If you want to get all the Foods used in the recipe then you want a function on Recipe to sum up the descendants. This would be a calculated total, so you wouldn't need callbacks for updating totals etc, however I would add logic to prevent a recipe being deleted which is used for something else.
Ask if you need more clarification.
Related
Im having troubles with using .joins and .where
In my app the users can search for recipes using their ingredients as parameters.
For example, if you have rice and tomatoes, it will show you all recipes with rice and tomatoes (but if the recipe use a third ingredient it is not displayed).
Anyway, I have recipe and ingredient model and recipe model and a model for the join table called has_ingredient.
The model is basically something like this:
recipe --< has_ingredient >-- ingredient
Everything is working when I create recipes, it actually store the id of recipes and id of ingredients in the has_ingredient table.
anyway, for searching I created a separated model for it, I can actually select ingredients and send them to the controller via get method, but I having problems with this
#result=Recipe.joins(:has_ingredient).where(has_ingredient: {ing_id: params[:ingredients]})
I want to store in #result the list of recipes, but when I try to search i get the following error
Association named 'has_ingredient' was not found on Recipe; perhaps you misspelled it?
Im not sure what is the problem and Im having a hard time with RoR
I think you should set it up like this:
class Recipe < ActiveRecord::Base
has_many :recipe_ingredients
has_many :ingredients, through: :recipe_ingredients
end
class Ingredient < ActiveRecord::Base
has_many :recipe_ingredients
has_many :recipes, through: :recipe_ingredients
end
Then RecipeIngredient would be the join model.
class RecipeIngredient < ActiveRecord::Base
belongs_to :recipe
belongs_to :ingredient
end
You can read this great basics guide for more information on how to set everything up:
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
If you set it up like this you can then use joins in the query like this:
#result = Recipe.joins(:ingredients).where(ingredients: { id: params[:ingredients] })
In the joins part of the query you have to use the association name and in the where clause you have to use the actual table name. (in this case it is both ingredients).
Using the association and table name might fix the problem for your current setup, even though I advise not to use the name has_ingredient for the model.
I want to create a relationship from recipe to ingredients. Basically:
recipe has_many ingredients
ingredients belongs_to recipe
But if I add a ingredient to a recipe it should look if there is an existing ingredient with the same name and should use that one.
Is there a smooth solution?
You need to expand your schema out: you need to differentiate between an ingredient like "all purpose flour", of which you want one in your database, and then "100 grams of all purpose flour" which might be used in a specific recipe.
I would do something like this:
Recipe
has_many :recipe_ingredients
#fields - name
RecipeIngredient
belongs_to :ingredient
belongs_to :recipe
#fields - quantity
Ingredient
has_many :recipe_ingredients
#fields - name
Now, when you build a recipe, you're building a list of associated recipe_ingredients, each of which points to an ingredient (like the "all-purpose flour" ingredient) and has a quantity, eg "100 grams".
Note - i could have added "has_many :ingredients, :through => :recipe_ingredients" to Recipe, but i don't actually think this is a useful association: ingredients only make sense for the recipe when they have a quantity - i don't think you would ever want to say "recipe.ingredients" as this doesn't give you the quantity info.
Having all realations set up in a standart way (HABTM) you could add ingrediants by name like this:
# reciept.rb
def add_ingredient_by_name(ingredient_name)
self.ingredients << Ingredient.find_or_create_by(name: ingredient_name)
end
Update:
Also I'd add a uniqueness constraint for ingredient name for safety:
# ingredient.rb
validates_uniqueness_of :name
Three models:
User
List
Item
A User can have many Lists and a List can have many Items. Each List can have an Item added to it by ANY User. This means, for example, that you can create a list and I can add items to it. Make sense? Let's keep going.
I want to be able to find all Items created by X User at any point in time.
class User < ActiveRecord::Base
has_many :lists
has_many :items
end
class List < ActiveRecord::Base
belongs_to :user
has_many :items
end
class Item < ActiveRecord::Base
belongs_to :list
belongs_to :user
end
I don't like this. It has a funky smell but I can't put my finger on it. I feel like there should be something better. When creating a new Item I would have to write something that looks like the following:
list = List.find(params[:list_id])
#item = list.items.new(params[:item])
user = User.first
#item.user = user
#item.save!
So, what am I missing? Are my relationships wrong (very likely)? Tell me! :)
It seems like there can be two different relationships between items and users: 1) items are added to lists by users, and 2) lists are created by users.
Only one user can create a list. Many users can add items to lists after they are created.
So modeling this requires two different relationships
class User < ActiveRecord::Base
has_many :lists
has_many :items_added, :class_name => "Item", :foreign_key => "added_by_user_id"
end
class List < ActiveRecord::Base
belongs_to :user
has_many :items
end
class Item < ActiveRecord::Base
belongs_to :list
belongs_to :added_by_user, :class_name => "User", :foreign_key => "user_id"
end
It makes sense to assume that you all these relationships are required -- that is, a list needs a user_id when it's created to show who created it. Likewise, an item needs to record who added it to the list.
You'd need to add the added_by_user_id column to your items table to make this work and keep track of which user added the item.
So you could do this:
# To create a user and a list is easy!
user = User.create(user_params)
list = user.create_list(list_params)
# but when adding an item, you need to know which user is adding it
item = list.create_item({:added_by_user => current_user, :item_name => 'name', etc})
Now you can find all items added by a user using:
all_user_items = user.items_added
I haven't tested this and it's a bit complicated so I may have 1-2 mistakes in the code, but generally that's one way this could be modeled.
It has been almost a week since I'm trying to find a solution to my confusion... Here it is:
I have a Program model.
I have a ProgramCategory model.
I have a ProgramSubcategory model.
Let's make it more clear:
ProgramCategory ======> Shows, Movies,
ProgramSubcategory ===> Featured Shows, Action Movies
Program ==============> Lost, Dexter, Game of Thrones etc...
I want to able to associate each of these models with eachother. I've got what I want to do particularly with many-to-many association. I have a categories_navigation JOIN model/table and all of my other tables are connected to it. By this way, I can access all fields of all of these models.
BUT...
As you know, has_many :through style associations are always plural. There is nothing such as has_one :through or belongs_to through. BUT I want to play with SINGULAR objects, NOT arrays. A Program has ONLY ONE Subcategory and ONLY ONE Category. I'm just using a join table to only make connection between those 3. For example, at the moment I can access program.program_categories[0].title but I want to access it such like program.program_category for example.
How can I have 'has_many :through's abilities but has_one's singular usage convention all together? :|
P.S: My previous question was about this situation too, but I decided to start from scratch and learn about philosophy of associations. If you want so you may check my previous post here: How to access associated model through another model in Rails?
Why a join table where you have a direct relationship? In the end, a program belongs to a subcategory, which in turn belongs to one category. So no join table needed.
class Program < ActiveRecord::Base
belongs_to :subcategory # references the "subcategory_id" in the table
# belongs_to :category, :through => :subcategory
delegate :category, :to => :subcategory
end
class Subcategory < ActiveRecord::Base
has_many :programs
belongs_to :category # references the "category_id" in the table
end
class Category < ActiveRecord::Base
has_many :subcategories
has_many :programs, :through => :subcategories
end
Another point of view is to make categories a tree, so you don't need an additional model for "level-2" categories, you can add as many levels you want. If you use a tree implementation like "closure_tree" you can also get all subcategories (at any level), all supercategories, etc
In that case you skip the Subcategory model, as it is just a category with depth=2
class Program < ActiveRecord::Base
belongs_to :category # references the "category_id" in the table
scope :in_categories, lambda do |cats|
where(:category_id => cats) # accepts one or an array of either integers or Categories
end
end
class Category < ActiveRecord::Base
acts_as_tree
has_many :programs
end
Just an example on how to use a tree to filter by category. Suppose you have a select box, and you select a category from it. You want to retrieve all the object which correspond to any subcategory thereof, not only the category.
class ProgramsController < ApplicationController
def index
#programs = Program.scoped
if params[:category].present?
category = Category.find(params[:category])
#programs = #programs.in_categories(category.descendant_ids + [category.id])
end
end
end
Tree-win!
I want to make a cooking site but don't know the correct was to build database.
My models are: Recipe and Ingredient.
Ingredient in recipe should be autocomplete field. The problem is that user can put there any text. ("cucumber" or "cucamber") and it will be different ingredients.
I want to make a search by ingredients and links to them. What is the best way to do it?
A recipe has many items, which in turn keeps a reference to an ingredient, an amount and a measure type. So you can go with:
rails generate model Recipe name:string description:text
rails generate model Item recipe:references ingredient:references amount:decimal measure:string
rails generate model Ingredient name:string
and then add to your classes:
class Recipe < ActiveRecord::Base
has_many :items
has_many :ingredients, :through => :items
# this allows things like #recipes = Recipe.using("cucumber")
scope :using, lambda do |text|
joins(:ingredients).where("ingredients.name LIKE ?", "%#{text}%")
end
end
class Item < ActiveRecord::Base
belongs_to :recipe
belongs_to :ingredient
VALID_MEASURES = %w[oz kg tbsp] # use for "select" tags in forms
validates :measure, :inclusion => VALID_MEASURES
end
class Ingredient < ActiveRecord::Base
belongs_to :item
end
From here you start building your views, autocomplete, whatever your imagination allows.