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.
Related
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
I have rails application with two models, StudentProfile and ClassProfile that are associated via a join table (model Enrollment).
In my ClassProfile model, I have:
has_many :enrollments
has_many :student_profiles, through: :enrollments
and in my StudentProfile model, I have:
has_one :enrollment
has_one :class_profile, through: :enrollment
My enrollments table also has a status integer field.
I would like to put a method in the ClassProfile model called "roster" that returns all the student_profiles that have an enrollment status of 1. Right now, I have the following:
def roster
self.student_profiles
end
Needless to say, all this does is return all students in a class, regardless of enrollment status. I feel like this should be simple, but I've seen no examples of how to add filtering on the join table (enrollments). Is this something I can accomplish in the ClassProfile model, or do I need to do something in Enrollment (or elsewhere)?
Update
From looking through the query reference mentioned by #QMFNP, here's the code that worked:
self.student_profiles.includes(:enrollment).where('enrollments.status = ?', 1)
Needed to change :enrollments to :enrollment because it's a has_one association. And the enrollments field that I'm filtering on is status, so changed enrollments.id to enrollments.status.
Thanks!
This can be accomplished by specifying your query conditions on the association like so:
def roster
self.student_profiles.includes(:enrollment).where('enrollments.status = ?', 1)
end
This should return your expected results. More information about querying on Active Record associations can be found here:
http://guides.rubyonrails.org/active_record_querying.html
Hope that helps!
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.
I'm learning my way around Rails and am working on a sample app to keep track of beer recipes.
I have a model called Recipe which holds the recipe name and efficiency.
I have a model called Ingredient which is using STI - this is subclassed into Malt, Hop, and Yeast.
Finally, to link the recipes and ingredients, I am using a join table called rec_items which holds the recipe_id, ingredient_id, and info particular to that recipe/ingredient combo, such as amount and boil time.
Everything seems to be working well - I can find all my malts by using Malt.all, and all ingredients by using Ingredient.all. I can find a recipe's ingredients using #recipe.ingredients, etc...
However, I'm working on my recipe's show view now, and am confused as to the best way to accomplish the below:
I want to display the recipe name and associated info, and then list the ingredients, but separated by ingredient type. So, if I have a Black IPA # 85% efficiency and it has 5 malts and 3 hops varieties, the output would be similar to:
BLACK IPA (85%)
Ingredient List
MALTS:
malt 1
malt 2
...
HOPS:
hop 1
...
Now, I can pull #recipe.rec_items and iterate through them, testing each rec_item.ingredient for type == "Malt", then do the same for the hops, but that doesn't seem very Rails-y nor efficient. So what is the best way to do this? I can use #recipe.ingredients.all to pull all the ingredients, but can't use #recipe.malts.all or #recipe.hops.all to pull just those types.
Is there a different syntax I should be using? Should I using #recipe.ingredient.find_by_type("Malt")? Doing this in the controller and passing the collection to the view, or doing it right in the view? Do I need to specify the has_many relationship in my Hop and Malt models as well?
I can get it working the way I want using conditional statements or find_by_type, but my emphasis is on doing this "the Rails way" with as little DB overhead as possible.
Thanks for the help!
Current bare-bones code:
Recipe.rb
class Recipe < ActiveRecord::Base
has_many :rec_items
has_many :ingredients, :through => :rec_items
end
Ingredient.rb
class Ingredient < ActiveRecord::Base
has_many :rec_items
has_many :recipes, :through => :rec_items
end
Malt.rb
class Malt < Ingredient
end
Hop.rb
class Hop < Ingredient
end
RecItem.rb
class RecItem < ActiveRecord::Base
belongs_to :recipe
belongs_to :ingredient
end
recipes_controller.rb
class RecipesController < ApplicationController
def show
#recipe = Recipe.find(params[:id])
end
def index
#recipes = Recipe.all
end
end
Updated to add
I'm now unable to access the join table attributes, so I posted a new question:
Rails - using group_by and has_many :through and trying to access join table attributes
If anyone can help with that, I'd appreciate it!!
It's been a while since I've used STI, having been burned a time or two. So I may be skipping over some STI-fu that would make this easier. That said...
There are many ways of doing this. First, you could make a scope for each of malt, hops, and yeast.
class Ingredient < ActiveRecord::Base
has_many :rec_items
has_many :recipes, :through => :rec_items
named_scope :malt, :conditions => {:type => 'Malt'}
named_scope :hops, :conditions => {:type => 'Hops'}
...
end
This will allow you to do something line:
malts = #recipe.ingredients.malt
hops = #recipe.ingedients.hops
While this is convenient, it isn't the most efficient thing to do, database-wise. We'd have to do three queries to get all three types.
So if we're not talking a ton of ingredients per recipe, it'll probably be better to just pull in all #recipe.ingredients, then group them with something like:
ingredients = #recipe.ingredients.group_by(&:type)
This will perform one query and then group them into a hash in ruby memory. The hash will be keyed off of type and look something like:
{"Malt" => [first_malt, second_malt],
"Hops" => [first_hops],
"Yeast" => [etc]
}
You can then refer to that collection to display the items however you wish.
ingredients["Malt"].each {|malt| malt.foo }
You can use group_by here.
recipe.ingredients.group_by {|i| i.type}.each do |type, ingredients|
puts type
ingredients.each do |ingredient|
puts ingredient.inspect
end
end
The utility of STI in this instance is dubious. You might be better off with a straight-forward categorization:
class Ingredient < ActiveRecord::Base
belongs_to :ingredient_type
has_many :rec_items
has_many :recipes, :through => :rec_items
end
The IngredientType defines your various types and ends up being a numerical constant from that point forward.
When you're trying to display a list this becomes easier. I usually prefer to pull out the intermediate records directly, then join out as required:
RecItem.sort('recipe_id, ingredient_type_id').includes(:recipe, :ingredient).all
Something like that gives you the flexibility to sort and group as required. You can adjust the sort conditions to get the right ordering. This might also work with STI if you sort on the type column.
I am developing an application like the stackoverflow, which questions or articles have at less one tag. And one tags must have one or more articles.
So, I am doing this in migration in RoR. I am consider which relationship is suitable for both table. In article table, should use a "has_many", and in the tag table, should use "has_many".
But I am thinking is it necessary to add one more table in the middle, something like....
So, the first one is like that:
class Article < ActiveRecord::Base
has_many :tags
end
class Tag < ActiveRecord::Base
has_many :articles
end
or something like this:
class Article < ActiveRecord::Base
has_many :articleTagList
has_many :tags, :through => : articleTagLists
end
class Tag < ActiveRecord::Base
has_many :articleTagList
has_many :articles, :through => :articleTagLists
end
class ArticleTagList < ActiveRecord::Base
belongs_to :article
belongs_to :tag
end
Many-to-Many relationships in a normalized database will always need a third "look-up table."
If you denormalize you can get away with just having the tag id's in one field with a delimiter between them. But you also have to provide the logic to handle retrieval.
I'd personally just go with the normalized option.
If you don't want to store any information on the middle table (for example the name of the user who added tag X to the question Y), you can use the has_and_belongs_to_many:
http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_and_belongs_to_many
If you want to store something, you need to create the middle model, as your example. In your example, the ArticleTagList model should be called ArticlesTag and the database table should be articles_tags, by convention.