so I do the association of the categories and posts through categorizations. And i'm trying to restrict the access with pundit gem and testing with RSpec for to show by a category the posts. Because with this association I have something like: c = Category.first c.posts
and i'm showing the posts by a category.
And i need some help.
my categories_controller.rb
class CategoriesController < ApplicationController
before_action :set_category, only: [:show]
def show
authorize @category, :show?
end
private
def set_category
@category = Category.find(params[:id])
end
end
category.rb
class Category < ActiveRecord::Base
has_many :categorizations, dependent: :destroy
has_many :posts, through: :categorizations
validates :name,
presence: true,
length: { minimum: 3, maximum: 30 },
uniqueness: true
end
app/views/categories/show.html.slim
= title("Category - #{@category.name}")
#category.text-center
h1 Category: <small>#{@category.name}</small>
- if @category.posts
#category.posts
- @category.posts.each do |post|
.row.col-md-12
.caption
h2 = link_to post.title, post
p = link_to post.subtitle, post
small = "by : #{post.author}"
hr
routes.rb
Rails.application.routes.draw do
namespace :admin do
root 'application#index'
resources :posts, only: [:new, :create, :destroy]
resources :categories
resources :users do
member do
patch :archive
end
end
end
devise_for :users
root "posts#index"
resources :posts, only: [:index, :show, :edit, :update]
resources :categories, only: [:show]
end
category_policy.rb
class CategoryPolicy < ApplicationPolicy
class Scope < Scope
def resolve
resolve
end
end
def show?
user.try(:admin?) || record.post.has_member?(user)
end
end
category_policy_spec.rb
require 'rails_helper'
RSpec.describe CategoryPolicy do
context "permissions" do
subject { CategoryPolicy.new(user, category) }
let(:user) { create(:user) }
let(:post) { create(:post) }
let(:category) { create(:category) }
context "for anonymous users" do
let(:user) { nil }
it { should_not permit_action :show }
end
context "for viewers of the post" do
before { assign_role!(user, :viewer, post) }
it { should permit_action :show }
end
context "for editors of the post" do
before { assign_role!(user, :editor, post) }
it { should permit_action :show }
end
context "for managers of the post" do
before { assign_role!(user, :manager, post) }
it { should permit_action :show }
end
context "for managers of other posts" do
before do
assign_role!(user, :manager, create(:post))
end
it { should_not permit_action :show }
end
context "for administrators" do
let(:user) { create(:user, :admin) }
it { should permit_action :show }
end
end
end
I'm using this matcher. pundit_matcher.rb
RSpec::Matchers.define :permit_action do |action|
match do |policy|
policy.public_send("#{action}?")
end
failure_message do |policy|
"#{policy.class} does not allow #{policy.user || "nil"} to " +
"perform :#{action}? on #{policy.record}."
end
failure_message_when_negated do |policy|
"#{policy.class} does not forbid #{policy.user || "nil"} from " +
"performing :#{action}? on #{policy.record}."
end
end
post.rb
class Post < ActiveRecord::Base
belongs_to :author, class_name: "User"
has_many :roles, dependent: :delete_all
has_many :categorizations, dependent: :destroy
has_many :categories, through: :categorizations
mount_base64_uploader :attachment, AttachmentUploader
validates :title,
presence: true,
length: { minimum: 10, maximum: 100 },
uniqueness: true
validates :subtitle,
length: { maximum: 100 }
validates :content,
presence: true,
length: { minimum: 30 }
validates :attachment, file_size: { less_than: 1.megabytes }
def has_member?(user)
roles.exists?(user_id: user)
end
[:manager, :editor, :viewer].each do |role|
define_method "has_#{role}?" do |user|
roles.exists?(user_id: user, role: role)
end
end
end
roler.rb
class Role < ActiveRecord::Base
belongs_to :user
belongs_to :post
def self.available_roles
%w(manager editor viewer)
end
end
When I try to run the:
rspec spec/policies/category_policy_spec.rb
I get this error:
Failure/Error: user.try(:admin?) || record.post.has_member?(user)
NoMethodError:
undefined method `post' for #<Category:0x007fc61bfdbf40>
Did you mean? posts
It's ok show this posts by category on show, or I need to switch for index?
As the error states, your Category model doesn't have a post
method, it only has posts
. This is because Category has_many posts. Because of that record.post.has_member?(user)
in your Category policy doesn't make any sense (record
is a Category instance). Since Category doesn't appear to have a direct relationship to users, and your roles are associated with users and posts.
It's unclear to me what the purpose of your Category policy actually is, unless it's to only allow a user to see categories if they already have a post that uses that category? If that's the case then you need to change the policy to actually check if any of those exist.