I am building an app where users can create recipes, see all recipes created, view their own recipes in a member area and finally i would like for users to add "favorites" to their account.
I am new to Rails but have read through docs and this is my understanding of what it should look like in the backend. Could someone confirm that this looks correct or advise of any errors please, with explanations if I have done something wrong (which is probably the case)?
So this is my code:
User Model
has_many :recipes
has_many_favorites, :through => :recipes
Recipe Model
belongs_to :user
has_many :ingredients #created seperate db for ingredients
has_many :prepererations #created seperate db for prep steps
Favorite Model
belongs_to :user
has_many :recipes, :through => :user
#this model has one column for the FK, :user_id
Favorites Controller
def create
@favrecipes =current_user.favorites.create(params[:user_id])
end
I then wanted to have a button to post to the db, so I have this:
<%= button_to("Add to Favorites" :action => "create", :controller => "favorites" %>
I think I am probably missing something in my routes but I am unsure.
The particular setup you describe mixes several types of associations.
First we have a User model and second a Recipe model. Each recipe belonging to one user, hence we have a User :has_many recipes, Recipe belongs_to :user association. This relationship is stored in the recipe's user_id field.
$ rails g model Recipe user_id:integer ...
$ rails g model User ...
class Recipe < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :recipes
end
Next we need to decide on how to implement the story that a user should be able to mark favorite recipes.
This can be done by using a join model - let's call it FavoriteRecipe - with the columns :user_id and :recipe_id. The association we're building here is a has_many :through association.
A User
- has_many :favorite_recipes
- has_many :favorites, through: :favorite_recipes, source: :recipe
A Recipe
- has_many :favorite_recipes
- has_many :favorited_by, through: :favorite_recipes, source: :user
# returns the users that favorite a recipe
Adding this favorites has_many :through association to the models, we get our final results.
$ rails g model FavoriteRecipe recipe_id:integer user_id:integer
# Join model connecting user and favorites
class FavoriteRecipe < ActiveRecord::Base
belongs_to :recipe
belongs_to :user
end
---
class User < ActiveRecord::Base
has_many :recipes
# Favorite recipes of user
has_many :favorite_recipes # just the 'relationships'
has_many :favorites, through: :favorite_recipes, source: :recipe # the actual recipes a user favorites
end
class Recipe < ActiveRecord::Base
belongs_to :user
# Favorited by users
has_many :favorite_recipes # just the 'relationships'
has_many :favorited_by, through: :favorite_recipes, source: :user # the actual users favoriting a recipe
end
##
# Association "A"
# Find recipes the current_user created
current_user.recipes
# Create recipe for current_user
current_user.recipes.create!(...)
# Load user that created a recipe
@recipe = Recipe.find(1)
@recipe.user
##
# Association "B"
# Find favorites for current_user
current_user.favorites
# Find which users favorite @recipe
@recipe = Recipe.find(1)
@recipe.favorited_by # Retrieves users that have favorited this recipe
# Add an existing recipe to current_user's favorites
@recipe = Recipe.find(1)
current_user.favorites << @recipe
# Remove a recipe from current_user's favorites
@recipe = Recipe.find(1)
current_user.favorites.delete(@recipe) # (Validate)
There may be several approaches on how to implement Controller actions and routing. I quite like the one by Ryan Bates shown in Railscast #364 on the ActiveRecord Reputation System. The part of a solution described below is structured along the lines of the voting up and down mechanism there.
In our Routes file we add a member route on recipes called favorite. It should respond to post requests. This will add a favorite_recipe_path(@recipe) url helper for our view.
# config/routes.rb
resources :recipes do
put :favorite, on: :member
end
In our RecipesController we can now add the corresponding favorite action. In there we need to determine what the user wants to do, favoriting or unfavoriting. For this a request parameter called e.g. type can be introduced, that we'll have to pass into our link helper later too.
class RecipesController < ...
# Add and remove favorite recipes
# for current_user
def favorite
type = params[:type]
if type == "favorite"
current_user.favorites << @recipe
redirect_to :back, notice: 'You favorited #{@recipe.name}'
elsif type == "unfavorite"
current_user.favorites.delete(@recipe)
redirect_to :back, notice: 'Unfavorited #{@recipe.name}'
else
# Type missing, nothing happens
redirect_to :back, notice: 'Nothing happened.'
end
end
end
In your view you can then add the respective links to favoriting and unfavoriting recipes.
<% if current_user %>
<%= link_to "favorite", favorite_recipe_path(@recipe, type: "favorite"), method: :put %>
<%= link_to "unfavorite", favorite_recipe_path(@recipe, type: "unfavorite"), method: :put %>
<% end %>
That's it. If a user clicks on the "favorite" link next to a recipe, this recipe is added to the current_user's favorites.
I hope that helps, and please ask any questions you like.
The Rails guides on associations are pretty comprehensives and will help you a lot when getting started.
Thanks for the guide, Thomas! It works great.
Just wanted to add that in order for your favorite method to work correctly you need to wrap the text in double quotes instead of single quotes for the string interpolation to function.
redirect_to :back, notice: 'You favorited #{@recipe.name}'
->
redirect_to :back, notice: "You favorited #{@recipe.name}"
https://rubymonk.com/learning/books/1-ruby-primer/chapters/5-strings/lessons/31-string-basics
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With