The particular setup you describe mixes several types of associations.
A) User and Recipe
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
B) FavoriteRecipe
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
C) Interacting with the associations
##
# 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)
D) Controller Actions
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.