From 2a4f37e6a9cc577174e82232aec5027a911bf7bb Mon Sep 17 00:00:00 2001 From: demesameneshoa Date: Thu, 21 Dec 2023 02:11:31 +0300 Subject: [PATCH 01/13] feat: modify recipes controller to route to the recipe details view --- app/controllers/recipes_controller.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/controllers/recipes_controller.rb b/app/controllers/recipes_controller.rb index c067a88..865181e 100644 --- a/app/controllers/recipes_controller.rb +++ b/app/controllers/recipes_controller.rb @@ -1,4 +1,7 @@ class RecipesController < ApplicationController + # uncomment the below line to enable authentication on the public recipes page + # public pages will be only visible across loggd in user and not to the public + # before_action :authenticate_user! before_action :set_recipe, only: %i[show edit update destroy] # GET /recipes or /recipes.json @@ -11,7 +14,11 @@ def public_index end # GET /recipes/1 or /recipes/1.json - def show; end + def show + @recipe = Recipe.includes(:recipe_foods).find(params[:id]) + @recipe_food = @recipe.recipe_foods.includes(:food) + # @food= Food.where(food_id: params[:food_id]) + end # GET /recipes/new def new From d243b73163e7aa8f7d7cea0dca3974d9d6ca1dbc Mon Sep 17 00:00:00 2001 From: demesameneshoa Date: Thu, 21 Dec 2023 02:13:04 +0300 Subject: [PATCH 02/13] feat: create the recipe details view - create the toggle option in recipe new form - create the view for recipe details - create the link and options for the recipe food view --- app/views/recipes/_recipe.html.erb | 66 +++++++++++++++++------------- app/views/recipes/show.html.erb | 53 +++++++++++++++++++++--- 2 files changed, 85 insertions(+), 34 deletions(-) diff --git a/app/views/recipes/_recipe.html.erb b/app/views/recipes/_recipe.html.erb index 6dd4cc2..6803e35 100644 --- a/app/views/recipes/_recipe.html.erb +++ b/app/views/recipes/_recipe.html.erb @@ -1,32 +1,42 @@ -
-

- Name: - <%= recipe.name %> -

+
+

+ <%= recipe.name %> +

+
+
+

+ Preparation time: + <%= recipe.preparation_time %> +

-

- Preparation time: - <%= recipe.preparation_time %> -

+

+ Cooking time: + <%= recipe.cooking_time %> +

-

- Cooking time: - <%= recipe.cooking_time %> -

- -

- Description: - <%= recipe.description %> -

- -

- Public: - <%= recipe.public %> -

- -

- User: - <%= recipe.user_id %> -

+

+

+ Description: +

<%= recipe.description %>

+
+

+
+ +
+ <%= form_with(model: recipe) do |form| %> +
+ <%= form.check_box :public, class: "custom-control-input", + id: "publicswitch", onchange: "submitForm(this);" %> + +
+ <% end %> + +
+
\ No newline at end of file diff --git a/app/views/recipes/show.html.erb b/app/views/recipes/show.html.erb index 62a835a..ae6760a 100644 --- a/app/views/recipes/show.html.erb +++ b/app/views/recipes/show.html.erb @@ -1,10 +1,51 @@ -

<%= notice %>

- <%= render @recipe %> -
- <%= link_to "Edit this recipe", edit_recipe_path(@recipe) %> | - <%= link_to "Back to recipes", recipes_path %> +
+ + <%= link_to "Add ingredient", new_recipe_food_path(recipe_id: @recipe.id), class: "btn btn-outline-success" %> +
+ +
+
- <%= button_to "Destroy this recipe", @recipe, method: :delete %> +
+ <% if @recipe_food.empty? %> +
+

There are no ingredients for this recipe

+
+ <% else %> + + + + + + + + + + + + <% @recipe_food.each do |ingredient| %> + + + + + + + + <% end %> + +
FoodQuantityValueActions
<%= ingredient.food.name %><%= "#{ingredient.quantity}#{ingredient.food.measurement_unit}" %><%= "$#{ingredient.food.price*ingredient.quantity}" %><%= link_to "Modify", edit_recipe_food_path(ingredient), class: "btn btn-ountline-danger", method: :update, data: { "turbo-method": :update } %><%= link_to "Delete", ingredient, class: "btn btn-ountline-danger", method: :delete, data: { "turbo-method": :delete } %>
+ <% end %>
+ + <% if @recipe.user.id == current_user.id %> + <%= link_to "Back to recipes", recipes_path, + class: "btn btn-secondary mb-1" %> + <%= link_to "Edit this recipe", edit_recipe_path(@recipe), + class: "btn btn-outline-warning mb-1" %> + + <%= button_to "Delete recipe", @recipe, method: :delete, + class: "btn btn-outline-danger" %> + <% end %> +
\ No newline at end of file From 2d07ef776c9c4371c32cc451a0e14d7de2ede119 Mon Sep 17 00:00:00 2001 From: demesameneshoa Date: Thu, 21 Dec 2023 02:15:24 +0300 Subject: [PATCH 03/13] feat: add recipe food path to the route file --- config/routes.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/routes.rb b/config/routes.rb index c4248aa..97a6690 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,7 @@ Rails.application.routes.draw do resources :foods resources :recipes + resources :recipe_foods devise_for :users # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html From cfabbdf584a0d20f2eea13388a4fca23c2a2059b Mon Sep 17 00:00:00 2001 From: demesameneshoa Date: Thu, 21 Dec 2023 02:16:07 +0300 Subject: [PATCH 04/13] feat: add recipe foods controller to create and remove recipe food --- app/controllers/recipe_foods_controller.rb | 50 ++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 app/controllers/recipe_foods_controller.rb diff --git a/app/controllers/recipe_foods_controller.rb b/app/controllers/recipe_foods_controller.rb new file mode 100644 index 0000000..63f902f --- /dev/null +++ b/app/controllers/recipe_foods_controller.rb @@ -0,0 +1,50 @@ +class RecipeFoodsController < ApplicationController + +def index + #match the recipe id with the recipe_foods recipe_id + @recipe_foods = RecipeFood.where(recipe_id: params[:recipe_id]) + @recipe = Recipe.find(params[:recipe_id]) + @food = Food.where(food_id: params[:food_id]) +end + +def new + @recipe_food = RecipeFood.new + @recipe_id = params[:recipe_id] + @foods = Food.all +end + +def create + @recipe_food = RecipeFood.new(recipe_food_params) + if @recipe_food.save + flash[:success] = 'Ingredient was successfully added.' + redirect_to recipe_path(id: @recipe_food.recipe_id) + else + render :new, alert: 'Failed to add ingredient' + end +end + +def update + respond_to do |format| + if @recipe_food.update(recipe_food_params) + flash[:success] = 'Recipe was successfully updated.' + format.html { redirect_to recipe_food_path(@recipe_food) } + else + flash[:danger] = 'Recipe was not updated.' + format.html { render :edit, status: :unprocessable_entity } + end + end +end + +def destroy + @recipe_id = params[:recipe_id] + @recipe_food = RecipeFood.find(params[:id]) + return unless @recipe_food.destroy + flash[:success] = 'Ingredient was successfully deleted.' + redirect_to recipe_path(id: @recipe_food.recipe_id) +end +private + +def recipe_food_params + params.require(:recipe_food).permit(:quantity, :recipe_id, :food_id) +end +end \ No newline at end of file From 86addd21efb81e3cac483b5877da6cfa754766b4 Mon Sep 17 00:00:00 2001 From: demesameneshoa Date: Thu, 21 Dec 2023 02:16:43 +0300 Subject: [PATCH 05/13] feat: add recipe food view to add food items to the recipe as ingredients --- app/views/recipe_foods/new.html.erb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 app/views/recipe_foods/new.html.erb diff --git a/app/views/recipe_foods/new.html.erb b/app/views/recipe_foods/new.html.erb new file mode 100644 index 0000000..0ba9671 --- /dev/null +++ b/app/views/recipe_foods/new.html.erb @@ -0,0 +1,20 @@ +

Add new Food to Recipe

+<%= form_with(model: [@recipe_food], url: recipe_foods_path) do |form| %> +
+
+ <%= form.hidden_field :recipe_id, value: @recipe_id %> +
+
+ <%= form.label :name %>
+ <%= form.collection_select(:food_id, Food.all, :id, :name, { prompt: "Select a Food" }, { class: "form-select" }) %> +
+ +
+ <%= form.label :quantity %>
+ <%= form.number_field :quantity, autofocus: true, autocomplete: "quantity", placeholder: "Quantity" %> +
+
+ <%= form.submit 'Add Food to Recipe', class: "btn btn-primary mt-3"%> +
+
+<% end %> \ No newline at end of file From 204aad28790729f35c9714957a88bbe00c7ca482 Mon Sep 17 00:00:00 2001 From: demesameneshoa Date: Thu, 21 Dec 2023 02:22:32 +0300 Subject: [PATCH 06/13] fix: resolve rubocop conflicts --- app/controllers/recipe_foods_controller.rb | 31 +++++++++++----------- app/controllers/recipes_controller.rb | 6 ++--- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/app/controllers/recipe_foods_controller.rb b/app/controllers/recipe_foods_controller.rb index 63f902f..44dbcfb 100644 --- a/app/controllers/recipe_foods_controller.rb +++ b/app/controllers/recipe_foods_controller.rb @@ -1,19 +1,18 @@ class RecipeFoodsController < ApplicationController - -def index - #match the recipe id with the recipe_foods recipe_id + def index + # match the recipe id with the recipe_foods recipe_id @recipe_foods = RecipeFood.where(recipe_id: params[:recipe_id]) @recipe = Recipe.find(params[:recipe_id]) @food = Food.where(food_id: params[:food_id]) -end + end -def new + def new @recipe_food = RecipeFood.new @recipe_id = params[:recipe_id] @foods = Food.all -end + end -def create + def create @recipe_food = RecipeFood.new(recipe_food_params) if @recipe_food.save flash[:success] = 'Ingredient was successfully added.' @@ -21,9 +20,9 @@ def create else render :new, alert: 'Failed to add ingredient' end -end + end -def update + def update respond_to do |format| if @recipe_food.update(recipe_food_params) flash[:success] = 'Recipe was successfully updated.' @@ -33,18 +32,20 @@ def update format.html { render :edit, status: :unprocessable_entity } end end -end + end -def destroy + def destroy @recipe_id = params[:recipe_id] @recipe_food = RecipeFood.find(params[:id]) return unless @recipe_food.destroy + flash[:success] = 'Ingredient was successfully deleted.' redirect_to recipe_path(id: @recipe_food.recipe_id) -end -private + end + + private -def recipe_food_params + def recipe_food_params params.require(:recipe_food).permit(:quantity, :recipe_id, :food_id) + end end -end \ No newline at end of file diff --git a/app/controllers/recipes_controller.rb b/app/controllers/recipes_controller.rb index 865181e..5e9d543 100644 --- a/app/controllers/recipes_controller.rb +++ b/app/controllers/recipes_controller.rb @@ -1,7 +1,7 @@ class RecipesController < ApplicationController - # uncomment the below line to enable authentication on the public recipes page - # public pages will be only visible across loggd in user and not to the public - # before_action :authenticate_user! + # uncomment the below line to enable authentication on the public recipes page + # public pages will be only visible across loggd in user and not to the public + # before_action :authenticate_user! before_action :set_recipe, only: %i[show edit update destroy] # GET /recipes or /recipes.json From 499dcb6cf429478be40a1b980312891fcd422d07 Mon Sep 17 00:00:00 2001 From: demesameneshoa Date: Thu, 21 Dec 2023 10:40:53 +0300 Subject: [PATCH 07/13] test: created unit test for 'recipe_food' model --- app/models/recipe_food.rb | 5 ++++ spec/factories/recipe_foods.rb | 7 +++++ spec/models/recipe_foods_spec.rb | 46 ++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 spec/factories/recipe_foods.rb create mode 100644 spec/models/recipe_foods_spec.rb diff --git a/app/models/recipe_food.rb b/app/models/recipe_food.rb index de4c992..4e9da1c 100644 --- a/app/models/recipe_food.rb +++ b/app/models/recipe_food.rb @@ -1,4 +1,9 @@ class RecipeFood < ApplicationRecord belongs_to :recipe belongs_to :food + + validates :quantity, presence: true, numericality: { greater_than: 0 } + validates :recipe_id, presence: true + validates :food_id, presence: true + end diff --git a/spec/factories/recipe_foods.rb b/spec/factories/recipe_foods.rb new file mode 100644 index 0000000..611e178 --- /dev/null +++ b/spec/factories/recipe_foods.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :recipe_food do + food { create(:food) } + recipe { create(:recipe) } + quantity { 1 } + end +end diff --git a/spec/models/recipe_foods_spec.rb b/spec/models/recipe_foods_spec.rb new file mode 100644 index 0000000..d2c5ef2 --- /dev/null +++ b/spec/models/recipe_foods_spec.rb @@ -0,0 +1,46 @@ +require 'rails_helper' +RSpec.describe RecipeFood, type: :model do + let(:recipe_food) { build(:recipe_food) } # ! non persisting object. It's not saved in the database. + + describe 'validations' do + context 'object itself' do + it 'should be valid' do + expect(recipe_food).to be_valid + end + end + + context 'quantity validations' do + it 'should be valid' do + expect(recipe_food.quantity).to be_present + expect(recipe_food.quantity).to be > 0 + end + + it 'should be invalid' do + recipe_food.quantity = nil + expect(recipe_food).to_not be_valid + end + end + + context 'recipe_id validations' do + it 'should be valid' do + expect(recipe_food.recipe_id).to be_present + end + + it 'should be invalid' do + recipe_food.recipe_id = nil + expect(recipe_food).to_not be_valid + end + end + + context 'food_id validations' do + it 'should be valid' do + expect(recipe_food.food_id).to be_present + end + + it 'should be invalid' do + recipe_food.food_id = nil + expect(recipe_food).to_not be_valid + end + end + end +end \ No newline at end of file From 027a3f032f19d44d8652ced03c30402a5c0e6fba Mon Sep 17 00:00:00 2001 From: demesameneshoa Date: Thu, 21 Dec 2023 10:45:42 +0300 Subject: [PATCH 08/13] test: add recipe foods controller --- .../recipe_foods_controller_spec.rb | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 spec/controllers/recipe_foods_controller_spec.rb diff --git a/spec/controllers/recipe_foods_controller_spec.rb b/spec/controllers/recipe_foods_controller_spec.rb new file mode 100644 index 0000000..55ff506 --- /dev/null +++ b/spec/controllers/recipe_foods_controller_spec.rb @@ -0,0 +1,45 @@ +require 'rails_helper' + +RSpec.describe RecipeFoodsController, type: :controller do + include Devise::Test::ControllerHelpers + let(:user) { create(:user) } + let(:recipe) { create(:recipe, user: user) } + let(:food) { create(:food) } + let(:valid_attributes) { attributes_for(:recipe_food, recipe_id: recipe.id, food_id: food.id) } + let(:invalid_attributes) { attributes_for(:recipe_food, quantity: nil, recipe_id: recipe.id, food_id: food.id) } + + before do + sign_in user + end + + describe 'GET #new' do + it 'returns a success response' do + recipe_food = RecipeFood.create! valid_attributes + get :new, params: { recipe_id: recipe_food.recipe_id } + expect(response).to be_successful + end + end + + describe 'POST #create' do + context 'with valid params' do + it 'creates a new RecipeFood' do + expect do + post :create, params: { recipe_food: valid_attributes } + end.to change(RecipeFood, :count).by(1) + end + + it 'redirects to the recipe show page' do + post :create, params: { recipe_food: valid_attributes } + expect(response).to redirect_to(recipe_path(id: valid_attributes[:recipe_id])) + end + end + + context 'with invalid params' do + it 'does not create a new RecipeFood' do + expect do + post :create, params: { recipe_food: invalid_attributes } + end.to change(RecipeFood, :count).by(0) + end + end +end +end From f386ac86e8460eb5a9e8c8fe130cc88238751b85 Mon Sep 17 00:00:00 2001 From: demesameneshoa Date: Thu, 21 Dec 2023 10:48:07 +0300 Subject: [PATCH 09/13] test: add integraiton testing for recipe details view --- app/views/recipes/_recipe.html.erb | 3 +- app/views/recipes/show.html.erb | 17 ++++++---- spec/views/recipes/show.html.erb_spec.rb | 43 +++++++++++++----------- 3 files changed, 37 insertions(+), 26 deletions(-) diff --git a/app/views/recipes/_recipe.html.erb b/app/views/recipes/_recipe.html.erb index 6803e35..a91dcef 100644 --- a/app/views/recipes/_recipe.html.erb +++ b/app/views/recipes/_recipe.html.erb @@ -24,6 +24,7 @@
+ <% if recipe.user == current_user %> <%= form_with(model: recipe) do |form| %>
<%= form.check_box :public, class: "custom-control-input", @@ -31,7 +32,7 @@
<% end %> - + <% end %>