Skip to content

Commit

Permalink
Merge pull request #34 from RileyManda/feature/recipe-tests-authoriza…
Browse files Browse the repository at this point in the history
…tion

Feature/recipe tests authorization
  • Loading branch information
RileyManda authored Oct 19, 2023
2 parents e191eb7 + c883450 commit 9b5ee3d
Show file tree
Hide file tree
Showing 14 changed files with 230 additions and 40 deletions.
14 changes: 6 additions & 8 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,14 @@ source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '3.2.2'
gem 'bootstrap', '~> 5.3'
gem 'cancancan'
gem 'devise'
gem 'rspec-rails'
gem 'rubocop', '>= 1.0', '< 2.0'
# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem 'rails', '~> 7.0.8'

# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
gem 'sprockets-rails'

# Use postgresql as the database for Active Record
gem 'pg', '~> 1.1'
gem 'pg', '~> 1.5', '>= 1.5.4'

# Use the Puma web server [https://github.com/puma/puma]
gem 'puma', '~> 5.0'
Expand Down Expand Up @@ -46,6 +41,7 @@ gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby]
# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', require: false

gem 'rubocop', '>= 1.0', '< 2.0'
# Use Sass to process CSS
# gem "sassc-rails"

Expand All @@ -60,7 +56,6 @@ end
group :development do
# Use console on exceptions pages [https://github.com/rails/web-console]
gem 'web-console'

# Add speed badges [https://github.com/MiniProfiler/rack-mini-profiler]
# gem "rack-mini-profiler"

Expand All @@ -74,4 +69,7 @@ group :test do
gem 'selenium-webdriver'
end

gem 'cssbundling-rails', '~> 1.3'
gem 'cancancan'
gem 'devise', '~> 4.9'
gem 'factory_bot_rails'
gem 'rspec-rails'
32 changes: 8 additions & 24 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,11 @@ GEM
addressable (2.8.5)
public_suffix (>= 2.0.2, < 6.0)
ast (2.4.2)
autoprefixer-rails (10.4.15.0)
execjs (~> 2)
base64 (0.1.1)
bcrypt (3.1.19)
bindex (0.8.1)
bootsnap (1.16.0)
msgpack (~> 1.2)
bootstrap (5.3.0)
autoprefixer-rails (>= 9.1.0)
popper_js (>= 2.11.7, < 3)
sassc-rails (>= 2.0.0)
builder (3.2.4)
cancancan (3.5.0)
capybara (3.39.2)
Expand All @@ -93,8 +87,6 @@ GEM
xpath (~> 3.2)
concurrent-ruby (1.2.2)
crass (1.0.6)
cssbundling-rails (1.3.3)
railties (>= 6.0.0)
date (3.3.3)
debug (1.8.0)
irb (>= 1.5.0)
Expand All @@ -107,8 +99,11 @@ GEM
warden (~> 1.2.3)
diff-lcs (1.5.0)
erubi (1.12.0)
execjs (2.9.1)
ffi (1.16.3)
factory_bot (6.2.1)
activesupport (>= 5.0.0)
factory_bot_rails (6.2.0)
factory_bot (~> 6.2.0)
railties (>= 5.0.0)
globalid (1.2.1)
activesupport (>= 6.1)
i18n (1.14.1)
Expand Down Expand Up @@ -157,7 +152,6 @@ GEM
ast (~> 2.4.1)
racc
pg (1.5.4)
popper_js (2.11.8)
psych (5.1.1)
stringio
public_suffix (5.0.3)
Expand Down Expand Up @@ -239,14 +233,6 @@ GEM
parser (>= 3.2.1.0)
ruby-progressbar (1.13.0)
rubyzip (2.3.2)
sassc (2.4.0)
ffi (~> 1.9)
sassc-rails (2.1.2)
railties (>= 4.0.0)
sassc (>= 2.0)
sprockets (> 3.0)
sprockets-rails
tilt
selenium-webdriver (4.14.0)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
Expand All @@ -262,7 +248,6 @@ GEM
railties (>= 6.0.0)
stringio (3.0.8)
thor (1.2.2)
tilt (2.3.0)
timeout (0.4.0)
turbo-rails (1.5.0)
actionpack (>= 6.0.0)
Expand Down Expand Up @@ -292,15 +277,14 @@ PLATFORMS

DEPENDENCIES
bootsnap
bootstrap (~> 5.3)
cancancan
capybara
cssbundling-rails (~> 1.3)
debug
devise
devise (~> 4.9)
factory_bot_rails
importmap-rails
jbuilder
pg (~> 1.1)
pg (~> 1.5, >= 1.5.4)
puma (~> 5.0)
rails (~> 7.0.8)
rspec-rails
Expand Down
7 changes: 6 additions & 1 deletion app/controllers/recipe_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
class RecipeController < ApplicationController
load_and_authorize_resource
def index
@recipes = Recipe.all
@recipes = if user_signed_in?
Recipe.where(public: true).or(Recipe.where(user_id: current_user.id))
else
Recipe.where(public: true)
end
end

def new
Expand Down
32 changes: 32 additions & 0 deletions app/models/ability.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
class Ability
include CanCan::Ability

def initialize(user)
user ||= User.new
can :manage, Recipe, user_id: user.id
# Define abilities for the user here. For example:
#
# return unless user.present?
# can :read, :all
# return unless user.admin?
# can :manage, :all
#
# The first argument to `can` is the action you are giving the user
# permission to do.
# If you pass :manage it will apply to every action. Other common actions
# here are :read, :create, :update and :destroy.
#
# The second argument is the resource the user can perform the action on.
# If you pass :all it will apply to every resource. Otherwise pass a Ruby
# class of the resource.
#
# The third argument is an optional hash of conditions to further filter the
# objects.
# For example, here the user can only update published articles.
#
# can :update, Article, published: true
#
# See the wiki for details:
# https://github.com/CanCanCommunity/cancancan/blob/develop/docs/define_check_abilities.md
end
end
10 changes: 6 additions & 4 deletions app/models/recipe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ class Recipe < ApplicationRecord
has_many :recipe_foods
has_many :foods, through: :recipe_foods

validates :name, presence: true
validates :preparation_time, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
validates :cooking_time, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
validates :description, presence: true
validates :name, presence: { message: 'Cannot be empty' }
validates :preparation_time, presence: { message: 'Cannot be empty' },
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
validates :cooking_time, presence: { message: 'Cannot be empty' },
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
validates :description, presence: { message: 'Cannot be empty' }
validates :public, inclusion: { in: [true, false] }
end
5 changes: 4 additions & 1 deletion app/views/recipe/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
<li class="list-group-item d-flex justify-content-between align-items-center">
<div>
<h3><%= link_to recipe.name, recipe_path(recipe) %></h3>
<%= button_to "Remove", recipe, method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-danger" %>
<% if can? :manage, recipe %>
<%= button_to "Remove", recipe, method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-danger" %>
<% end %>

</div>
<p class="text-muted"><%= recipe.description %></p>

Expand Down
6 changes: 5 additions & 1 deletion app/views/recipe/new.html.erb
Original file line number Diff line number Diff line change
@@ -1,28 +1,32 @@
<h1>Create a New Recipe</h1>
<%= form_for @recipe, url: new_recipe_path, method: :post do |f| %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.object.errors.full_messages_for(:name).join(', ') if f.object.errors.include?(:name) %>
</div>

<div class="field">
<%= f.label :preparation_time %>
<%= f.number_field :preparation_time %>
<%= f.object.errors.full_messages_for(:preparation_time).join(', ') if f.object.errors.include?(:preparation_time) %>
</div>

<div class="field">
<%= f.label :cooking_time %>
<%= f.number_field :cooking_time %>
<%= f.object.errors.full_messages_for(:cooking_time).join(', ') if f.object.errors.include?(:cooking_time) %>
</div>

<div class="field">
<%= f.label :description %>
<%= f.text_area :description %>
<%= f.object.errors.full_messages_for(:description).join(', ') if f.object.errors.include?(:description) %>
</div>

<div class="field">
<%= f.label :public %>
<%= f.check_box :public %>
<%= f.object.errors.full_messages_for(:public).join(', ') if f.object.errors.include?(:public) %>
</div>

<div class="actions">
Expand Down
1 change: 0 additions & 1 deletion app/views/recipe_foods/index.html.erb

This file was deleted.

Empty file.
11 changes: 11 additions & 0 deletions spec/features/public_recipes_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require 'rails_helper'

RSpec.feature 'Public Recipes Page', type: :feature do
scenario 'all users can see public recipes' do
user = create(:user)
login_as(user, scope: :user)
recipe = create(:recipe, user:)
visit public_recipes_path
expect(page).to have_content(recipe.name)
end
end
33 changes: 33 additions & 0 deletions spec/features/recipe_index_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
require 'rails_helper'

RSpec.feature 'Recipe#index Page', type: :feature do
scenario 'authorized user can add a new recipe' do
user = create(:user)
login_as(user, scope: :user)
visit recipe_index_path
click_button('Add Recipe')
expect(page).to have_current_path(new_recipe_path)
end

scenario 'user can remove their recipe' do
user = create(:user)
login_as(user, scope: :user)
visit recipe_index_path
expect(page).not_to have_button('Remove', exact: true)
end

scenario 'user can add a new recipe' do
user = create(:user)
login_as(user, scope: :user)
visit recipe_index_path
expect(page).to have_button('Add Recipe', exact: true)
end

scenario 'user can view their own private and public recipes' do
user = create(:user)
login_as(user, scope: :user)
recipe = create(:recipe, user:)
visit recipe_index_path
expect(page).to have_content(recipe.name)
end
end
58 changes: 58 additions & 0 deletions spec/models/recipe_spec.rb
Original file line number Diff line number Diff line change
@@ -1 +1,59 @@
require 'rails_helper'

RSpec.describe Recipe, type: :model do
describe Recipe, type: :model do
it 'is valid with valid attributes' do
user = User.create(name: 'johndoes', email: 'john@email.com', password: '123456')
recipe = Recipe.new(
name: 'Sample Recipe',
preparation_time: 30,
cooking_time: 60,
description: 'Recipe description',
public: true,
user:
)
expect(recipe).to be_valid
end

it 'is not valid without a name' do
recipe = Recipe.new(name: nil)
expect(recipe).not_to be_valid
end

it 'is not valid without a user' do
recipe = Recipe.new(
name: 'MyRecipe',
preparation_time: 30,
cooking_time: 60,
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
public: true
)
expect(recipe).not_to be_valid
end
it 'is not valid with a negative preparation time' do
user = User.create(name: 'johndoes', email: 'john@email.com', password: '123456')
recipe = Recipe.new(
name: 'Sample Recipe',
preparation_time: -5,
cooking_time: 60,
description: 'Recipe description',
public: true,
user:
)
expect(recipe).not_to be_valid
end

it 'is not valid with a negative cooking time' do
user = User.create(name: 'johndoes', email: 'john@email.com', password: '123456')
recipe = Recipe.new(
name: 'Sample Recipe',
preparation_time: 30,
cooking_time: -10,
description: 'Recipe description',
public: true,
user:
)
expect(recipe).not_to be_valid
end
end
end
16 changes: 16 additions & 0 deletions spec/rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@
require_relative '../config/environment'
# Prevent database truncation if the environment is production
abort('The Rails environment is running in production mode!') if Rails.env.production?
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
require 'rspec/rails'
require 'capybara/rails'
require 'capybara/rspec'
require 'factory_bot_rails'
require 'devise'

# Add additional requires below this line. Rails is not loaded until this point!

# Requires supporting ruby files with custom matchers and macros, etc, in
Expand All @@ -30,8 +36,16 @@
abort e.to_s.strip
end
RSpec.configure do |config|
config.before(:each, type: :feature) do
default_url_options[:host] = 'http://127.0.0.1:3000/' # Replace with your application's host
end

# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
config.fixture_path = "#{Rails.root}/spec/fixtures"
config.include FactoryBot::Syntax::Methods
config.include Devise::Test::IntegrationHelpers, type: :request
config.include Devise::Test::IntegrationHelpers, type: :feature
config.include Devise::Test::ControllerHelpers, type: :controller

# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
Expand Down Expand Up @@ -61,3 +75,5 @@
# arbitrary gems may also be filtered via:
# config.filter_gems_from_backtrace("gem name")
end

Capybara.javascript_driver = :selenium_chrome_headless
Loading

0 comments on commit 9b5ee3d

Please sign in to comment.