diff --git a/app/controllers/foods_controller.rb b/app/controllers/foods_controller.rb index 2c6a3ac..09b26f0 100644 --- a/app/controllers/foods_controller.rb +++ b/app/controllers/foods_controller.rb @@ -1,9 +1,10 @@ class FoodsController < ApplicationController + before_action :authenticate_user! before_action :set_food, only: %i[show edit update destroy] # GET /foods or /foods.json def index - @foods = Food.all + @foods = Food.where(user_id: current_user.id) end # GET /foods/1 or /foods/1.json @@ -20,10 +21,12 @@ def edit; end # POST /foods or /foods.json def create @food = Food.new(food_params) + @food.user = current_user respond_to do |format| if @food.save - format.html { redirect_to food_url(@food), notice: 'Food was successfully created.' } + flash[:success] = 'Food was successfully created.' + format.html { redirect_to foods_path } format.json { render :show, status: :created, location: @food } else format.html { render :new, status: :unprocessable_entity } @@ -50,7 +53,8 @@ def destroy @food.destroy! respond_to do |format| - format.html { redirect_to foods_url, notice: 'Food was successfully destroyed.' } + flash[:success] = 'Food was successfully destroyed.' + format.html { redirect_to foods_url } format.json { head :no_content } end end @@ -64,6 +68,6 @@ def set_food # Only allow a list of trusted parameters through. def food_params - params.require(:food).permit(:name, :measurement_unit, :price, :quantity, :user_id) + params.require(:food).permit(:name, :measurement_unit, :price, :quantity) end end diff --git a/app/models/food.rb b/app/models/food.rb index 9108308..283d309 100644 --- a/app/models/food.rb +++ b/app/models/food.rb @@ -2,4 +2,13 @@ class Food < ApplicationRecord belongs_to :user has_many :recipe_foods has_many :recipes, through: :recipe_foods + + validates :name, presence: true + validates :price, presence: true, numericality: { greater_than: 0 } + validates :measurement_unit, presence: true, inclusion: { in: %w[mg g kg l ml] } + validates :quantity, presence: true, numericality: { greater_than: 0 } + + def total_price + (price * quantity).round(2) + end end diff --git a/app/views/foods/_food.html.erb b/app/views/foods/_food.html.erb index 7f7d637..a2fc7e8 100644 --- a/app/views/foods/_food.html.erb +++ b/app/views/foods/_food.html.erb @@ -1,27 +1,9 @@ -
-

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

- -

- Measurement unit: - <%= food.measurement_unit %> -

- -

- Price: - <%= food.price %> -

- -

- Quantity: - <%= food.quantity %> -

- -

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

- -
+ + <%= food.name %> + <%= food.measurement_unit %> + <%= "$#{food.price}" %> + <%= "#{food.quantity}#{food.measurement_unit}" %> + <%= button_to "Delete", food, + method: :delete, + class: "btn btn-outline-danger px-2 py-1" %> + \ No newline at end of file diff --git a/app/views/foods/_form.html.erb b/app/views/foods/_form.html.erb index c59e73b..6533093 100644 --- a/app/views/foods/_form.html.erb +++ b/app/views/foods/_form.html.erb @@ -11,32 +11,40 @@ <% end %> -
- <%= form.label :name, style: "display: block" %> - <%= form.text_field :name %> +
+ <%= form.text_field :name, + placeholder: "Name", + aria: { label: "Food Name" }, + class: "form-control mb-2" %>
-
- <%= form.label :measurement_unit, style: "display: block" %> - <%= form.text_field :measurement_unit %> +
+ <%= form.select :measurement_unit, + options_for_select(["mg", "g", "kg", "l", "ml"]), + { prompt: "Select Measurement Unit" }, + aria: { label: "Measurement unit" }, + class: "form-control mb-2" %>
-
- <%= form.label :price, style: "display: block" %> - <%= form.text_field :price %> +
+ <%= form.text_field :price, + placeholder: "Unit price", + aria: { label: "Unit price" }, + class: "form-control mb-2" %>
-
- <%= form.label :quantity, style: "display: block" %> - <%= form.number_field :quantity %> +
+ <%= form.number_field :quantity, + placeholder: "Quantity", + aria: { label: "Quantity" }, + class: "form-control mb-2" %>
- <%= form.label :user_id, style: "display: block" %> - <%= form.text_field :user_id %> + <%= form.hidden_field :user_id, value: current_user.id %>
-
- <%= form.submit %> +
+ <%= form.submit "Save", class: "btn btn-success w-100" %>
<% end %> diff --git a/app/views/foods/index.html.erb b/app/views/foods/index.html.erb index c028cee..18f5c86 100644 --- a/app/views/foods/index.html.erb +++ b/app/views/foods/index.html.erb @@ -1,14 +1,32 @@ -

<%= notice %>

+
-

Foods

+
+
+ <%= link_to "New food", new_food_path, class: "btn btn-outline-primary m-1" %> +
+ <% if @foods.empty? %> +
+

There are no foods in your inventory.

+
+ <% else %> + + + + + + + + + + -
- <% @foods.each do |food| %> - <%= render food %> -

- <%= link_to "Show this food", food %> -

- <% end %> -
+ + <% @foods.each do |food| %> + <%= render food %> + <% end %> + +
FoodMeasurement unitUnit PriceQuantityActions
+ <% end %> +
+
-<%= link_to "New food", new_food_path %> diff --git a/app/views/foods/new.html.erb b/app/views/foods/new.html.erb index 84bc27f..7370375 100644 --- a/app/views/foods/new.html.erb +++ b/app/views/foods/new.html.erb @@ -1,9 +1,11 @@ -

New food

+

Add a new item to inventory

+
-<%= render "form", food: @food %> + <%= render "form", food: @food %> -
+
-
- <%= link_to "Back to foods", foods_path %> -
+
+ <%= link_to "Back to food list", foods_path, class: "btn btn-secondary px-2 py-1" %> +
+
\ No newline at end of file diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index e01706b..9ae51f7 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -19,23 +19,30 @@ -
- <% if user_signed_in? %> - <%= button_to("Sign Out", destroy_user_session_path, - method: :delete, - class: "btn btn-outline-success px-2 py-1", - style: "font-size: 0.9rem;") %> - <% else %> - <%= button_to("Sign in", new_user_session_path, - method: :get, - class: "btn btn-outline-success px-2 py-1", - style: "font-size: 0.9rem;") %> - <% end %> -
+
+ <% if user_signed_in? %> + <%= button_to("Sign Out", destroy_user_session_path, + method: :delete, + class: "btn btn-outline-success px-2 py-1", + style: "font-size: 0.9rem;") %> + <% else %> + <%= button_to("Sign in", new_user_session_path, + method: :get, + class: "btn btn-outline-success px-2 py-1", + style: "font-size: 0.9rem;") %> + <% end %> +
-

<%= notice %>

-

<%= alert %>

+
+ <% flash.each do |key, message| %> +
+ <%= message %> +
+ <% end %> +
- <%= yield %> +
+ <%= yield %> +
diff --git a/spec/factories/foods.rb b/spec/factories/foods.rb index dcc2383..904207a 100644 --- a/spec/factories/foods.rb +++ b/spec/factories/foods.rb @@ -1,9 +1,11 @@ +require 'faker' + FactoryBot.define do factory :food do - name { 'MyString' } - measurement_unit { 'MyString' } - price { '9.99' } + name { Faker::Food.ingredient } + measurement_unit { 'kg' } + price { 1.5 } quantity { 1 } - user { nil } + user { create(:user) } end end diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 13e6a8c..b9ec52b 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -1,8 +1,11 @@ +require 'faker' + FactoryBot.define do factory :user do name { Faker::Name.name } email { Faker::Internet.email } password { '123456' } password_confirmation { '123456' } + confirmed_at { Time.zone.now } end end diff --git a/spec/models/food_spec.rb b/spec/models/food_spec.rb index 7c6ea23..ec73643 100644 --- a/spec/models/food_spec.rb +++ b/spec/models/food_spec.rb @@ -1,5 +1,70 @@ require 'rails_helper' RSpec.describe Food, type: :model do - pending "add some examples to (or delete) #{__FILE__}" + let(:food) { build(:food) } # ! non persisting object. It's not saved in the database. + + describe 'validations' do + context 'object itself' do + it 'should be valid' do + expect(food).to be_valid + end + end + + context 'name validations' do + it 'should be valid' do + expect(food.name).to be_present + end + + it 'should be invalid' do + food.name = nil + expect(food).to_not be_valid + end + end + + context 'measurement_unit validations' do + it 'should be valid' do + expect(food.measurement_unit).to be_present + expect(food.measurement_unit).to be_in(%w[mg g kg l ml]) + end + + it 'should be invalid' do + food.measurement_unit = nil + expect(food).to_not be_valid + end + end + + context 'price validations' do + it 'should be valid' do + expect(food.price).to be_present + expect(food.price).to be_a(BigDecimal) + expect(food.price).to be > 0 + end + + it 'should be invalid' do + food.price = nil + expect(food).to_not be_valid + end + end + + context 'quantity validations' do + it 'should be valid' do + expect(food.quantity).to be_present + expect(food.quantity).to be_a(Integer) + expect(food.quantity).to be > 0 + end + + it 'should be invalid' do + food.quantity = nil + expect(food).to_not be_valid + end + end + + context 'total_price validations' do + it 'should be valid' do + expect(food.total_price).to be_present + expect(food.total_price).to be_a(BigDecimal) + expect(food.total_price).to be > 0 + end + end + end end diff --git a/spec/views/foods/index.html.erb_spec.rb b/spec/views/foods/index.html.erb_spec.rb index 6a0206a..f1f5ad8 100644 --- a/spec/views/foods/index.html.erb_spec.rb +++ b/spec/views/foods/index.html.erb_spec.rb @@ -1,32 +1,40 @@ require 'rails_helper' -RSpec.describe 'foods/index', type: :view do - before(:each) do - assign(:foods, [ - Food.create!( - name: 'Name', - measurement_unit: 'Measurement Unit', - price: '9.99', - quantity: 2, - user: nil - ), - Food.create!( - name: 'Name', - measurement_unit: 'Measurement Unit', - price: '9.99', - quantity: 2, - user: nil - ) - ]) - end +RSpec.describe 'Foods', type: :system do + it 'displays a list of foods or a message if empty' do + new_user = create(:user) + + visit new_user_session_path + + fill_in 'Email', with: new_user.email + fill_in 'Password', with: new_user.password + + click_button 'Log in' + + create(:food, name: 'Pizza', measurement_unit: 'kg', price: 10.99, quantity: 5, user: new_user) + create(:food, name: 'Salad', measurement_unit: 'g', price: 5.5, quantity: 10, user: new_user) + + visit foods_path + + within('#foods', wait: 5) do + expect(page).to have_link('New food', href: new_food_path) + + expect(page).to have_selector('.p-2 a.btn', text: 'New food') + + if Food.all.empty? + expect(page).to have_selector('.alert.alert-info', text: 'There are no foods in your inventory.') + else + expect(page).to have_selector('table') + expect(page).to have_selector('thead th', text: 'Food') + expect(page).to have_selector('thead th', text: 'Measurement unit') + expect(page).to have_selector('thead th', text: 'Unit Price') + expect(page).to have_selector('thead th', text: 'Quantity') + expect(page).to have_selector('thead th', text: 'Actions') - it 'renders a list of foods' do - render - cell_selector = Rails::VERSION::STRING >= '7' ? 'div>p' : 'tr>td' - assert_select cell_selector, text: Regexp.new('Name'.to_s), count: 2 - assert_select cell_selector, text: Regexp.new('Measurement Unit'.to_s), count: 2 - assert_select cell_selector, text: Regexp.new('9.99'.to_s), count: 2 - assert_select cell_selector, text: Regexp.new(2.to_s), count: 2 - assert_select cell_selector, text: Regexp.new(nil.to_s), count: 2 + expect(page).to have_selector('tbody tr', count: 2) # Assuming there are two food items created + expect(page).to have_content('Pizza') + expect(page).to have_content('Salad') + end + end end end