From 7abfc0def6daeadbf1f26c5fed9d61f274571e93 Mon Sep 17 00:00:00 2001 From: Chris Alley Date: Fri, 25 Oct 2024 20:48:02 +1300 Subject: [PATCH] Add Blog Policy --- Gemfile | 1 + Gemfile.lock | 6 ++++ app/policies/blog_policy.rb | 52 +++++++++++++++++++++++++++++++ spec/factories/blogs.rb | 9 ++++++ spec/factories/posts.rb | 9 ++++++ spec/factories/users.rb | 13 ++++++++ spec/policies/blog_policy_spec.rb | 51 ++++++++++++++++++++++++++++++ spec/rails_helper.rb | 1 + 8 files changed, 142 insertions(+) create mode 100644 app/policies/blog_policy.rb create mode 100644 spec/factories/blogs.rb create mode 100644 spec/factories/posts.rb create mode 100644 spec/factories/users.rb create mode 100644 spec/policies/blog_policy_spec.rb diff --git a/Gemfile b/Gemfile index a10f7e0..af80831 100644 --- a/Gemfile +++ b/Gemfile @@ -50,6 +50,7 @@ group :development, :test do end group :test do + gem "factory_bot_rails", "~> 6.4" gem "shoulda-matchers", "~> 6.0" gem "pundit-matchers", "~> 3.1" end diff --git a/Gemfile.lock b/Gemfile.lock index 5b3c92a..3f79692 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -97,6 +97,11 @@ GEM diff-lcs (1.5.1) drb (2.2.1) erubi (1.13.0) + factory_bot (6.5.0) + activesupport (>= 5.0.0) + factory_bot_rails (6.4.3) + factory_bot (~> 6.4) + railties (>= 5.0.0) globalid (1.2.1) activesupport (>= 6.1) i18n (1.14.6) @@ -325,6 +330,7 @@ DEPENDENCIES brakeman debug devise (~> 4.9) + factory_bot_rails (~> 6.4) importmap-rails jbuilder puma (>= 5.0) diff --git a/app/policies/blog_policy.rb b/app/policies/blog_policy.rb new file mode 100644 index 0000000..e1302a9 --- /dev/null +++ b/app/policies/blog_policy.rb @@ -0,0 +1,52 @@ +class BlogPolicy < ApplicationPolicy + class Scope + attr_reader :user, :scope + + def initialize(user, scope) + @user = user + @scope = scope + end + + def resolve + scope + end + end + + def index? + true + end + + def show? + true + end + + def new? + create? + end + + def create? + is_admin? + end + + def edit? + update? + end + + def update? + is_admin? || is_owner? + end + + def destroy? + update? && record.posts.count == 0 + end + + private + + def is_admin? + user&.administrator + end + + def is_owner? + user&.id == record.user.id + end +end diff --git a/spec/factories/blogs.rb b/spec/factories/blogs.rb new file mode 100644 index 0000000..65f11af --- /dev/null +++ b/spec/factories/blogs.rb @@ -0,0 +1,9 @@ +FactoryBot.define do + factory :blog do + factory :valid_blog do + sequence(:name) { |n| "Blog #{n}" } + user { FactoryBot.create(:registered_user) } + posts { [] } + end + end +end diff --git a/spec/factories/posts.rb b/spec/factories/posts.rb new file mode 100644 index 0000000..9dfcaed --- /dev/null +++ b/spec/factories/posts.rb @@ -0,0 +1,9 @@ +FactoryBot.define do + factory :post do + factory :valid_post do + sequence(:name) { |n| "Post #{n}" } + user { FactoryBot.create(:registered_user) } + blog { FactoryBot.create(:valid_blog) } + end + end +end diff --git a/spec/factories/users.rb b/spec/factories/users.rb new file mode 100644 index 0000000..9d8901c --- /dev/null +++ b/spec/factories/users.rb @@ -0,0 +1,13 @@ +FactoryBot.define do + factory :user do + factory :registered_user do + sequence(:email) { |n| "testuser#{n}@example.com" } + password { '1234567A' } + password_confirmation { '1234567A' } + + factory :administrator do + administrator { true } + end + end + end +end diff --git a/spec/policies/blog_policy_spec.rb b/spec/policies/blog_policy_spec.rb new file mode 100644 index 0000000..04ff5d6 --- /dev/null +++ b/spec/policies/blog_policy_spec.rb @@ -0,0 +1,51 @@ +require 'rails_helper' + +describe BlogPolicy do + subject { described_class.new(user, blog) } + + let(:resolved_scope) { described_class::Scope.new(user, Blog.all).resolve } + let(:blog) { FactoryBot.create(:valid_blog) } + + context 'with visitors' do + let(:user) { nil } + + it { expect(resolved_scope).to include(blog) } + it { is_expected.to permit_only_actions(%i[index show]) } + it { is_expected.to forbid_actions(%i[new create edit update destroy]) } + end + + context 'with registered users' do + let(:user) { FactoryBot.create(:registered_user) } + + it { expect(resolved_scope).to include(blog) } + it { is_expected.to permit_only_actions(%i[index show]) } + it { is_expected.to forbid_actions(%i[new create edit update destroy]) } + + context 'when registered user is the blog owner' do + before { blog.user = user } + + it { is_expected.to permit_only_actions(%i[index show edit update destroy]) } + it { is_expected.to forbid_new_and_create_actions } + + context 'blog has a post' do + before { blog.posts << FactoryBot.create(:valid_post) } + + it { is_expected.to forbid_action(:destroy) } + end + end + end + + context 'with administrators' do + let(:user) { FactoryBot.create(:administrator) } + let(:blog) { FactoryBot.create(:valid_blog) } + + it { expect(resolved_scope).to include(blog) } + it { is_expected.to permit_all_actions } + + context 'blog has a post' do + before { blog.posts << FactoryBot.create(:valid_post) } + + it { is_expected.to forbid_action(:destroy) } + end + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 4892cb3..f44e70a 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -9,6 +9,7 @@ # return unless Rails.env.test? require 'rspec/rails' # Add additional requires below this line. Rails is not loaded until this point! +require 'factory_bot' # Requires supporting ruby files with custom matchers and macros, etc, in # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are