How to use scopes, defined in the model, in my Pundit policy?
In my model I have a scope:
scope :published, ->{ where.not(published_at: nil )}
And in my Pundit policy I have
class CompanyPolicy < ApplicationPolicy
def index?
true
end
def create?
user.present?
end
def new?
true
end
def show?
true
end
def update?
user.present? && user == record.user
end
end
How can I use my scope in the Pundit policy? I would like to show it only if it's "published", something like this, which doesn't work at the moment:
class CompanyPolicy < ApplicationPolicy
def show
record.published?
end
end
Scopes are Class methods, you can't call them on instances.
You have to define a published? instance method too:
def published?
published_at.present?
end
You could use the scope if you ask if the record exists on the given scope with:
User.published.exists?(user.id)
And it will return true if the scope includes the user id, but I wouldn't recommend that, it requires an extra query to the database to get something that you can know from the user instance you already have.
Related
I am using Pundit for authorization for my User model.
My goal is to extend this to use my AdminUser model, specifically for my admin namespace.
By default, Pundit checks for a "user" or "current_user". How can I change this to check for a "admin_user" or "current_admin_user", based on Devise?
policies/admin/admin_policy.rb (Closed system, currently looks for User instead of AdminUser)
class Admin::AdminPolicy
attr_reader :user, :record
def initialize(user, record)
# Must be logged in
raise Pundit::NotAuthorizedError, "You must be logged in to perform this action" unless user
#user = user
#record = record
end
def index?
false
end
def show?
false
end
def create?
false
end
def new?
create?
end
def update?
false
end
def edit?
update?
end
def destroy?
false
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
raise Pundit::NotAuthorizedError, "You must be logged in to perform this action" unless user
#user = user
#scope = scope
end
def resolve
scope.all
end
end
end
policies/admin/home_policy.rb (Example sub-policy of the Admin namespace)
class Admin::HomePolicy < Admin::AdminPolicy
def index?
user.present?
end
end
I think you need to define the method pundit_user on your controllers to customize it https://github.com/varvet/pundit#customize-pundit-user
def pundit_user
current_admin_user
end
I've had a long break from my 2 years of effort in trying to learn how to use pundit in my rails app. I'm back and trying to learn how to use pundit.
I've made a completely new rails 5 app and installed pundit.
I have a user resource, an application policy and a user policy. Each has:
Users controller:
def index
# #users = User.all
#users = policy_scope(User)
end
Application Policy
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def index?
true
end
def show?
scope.where(:id => record.id).exists?
end
def create?
false
end
def new?
create?
end
def update?
false
end
def edit?
update?
end
def destroy?
false
end
def scope
Pundit.policy_scope!(user, record.class)
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
scope
end
end
end
User policy
class UserPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.where(user: user)
end
end
end
Then in my user index, I'm trying to follow the instructions in the pundit gem docs, by doing:
<% policy_scope(#users).each do |user| %>
I get this error:
PG::UndefinedColumn: ERROR: column users.user does not exist
LINE 1: SELECT "users".* FROM "users" WHERE "users"."user" = '566119...
^
: SELECT "users".* FROM "users" WHERE "users"."user" = '566119d2-54d8-4ab2-b7c5-f17c80b517f3' AND "users"."user" = '566119d2-54d8-4ab2-b7c5-f17c80b517f3'
Can anyone see how I'm getting off to the wrong start? I haven't even tried to define my scope in the way I want to yet, but it isn't working at this point.
class UserPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.where(user: user)
end
end
end
scope.where means the User class (user.where), because it's inside the user policy.
The user value is the current user, inherited by the application scope.
The scope example that you posted i'm guessing that you want to show only current user records. Then you can do the following as the comment above:
scope.where(id: user.try(:id))
Also because you have defined a scope in the controller
def index
# #users = User.all
#users = policy_scope(User)
end
You don't need to define another one in the view.
<% policy_scope(#users).each do |user| %>
It's one or the other. In the view you can simply do users.each do....
I'm working on Authorization for my school assignment, which is a Reddit clone. I was just introduced to the Pundit Gem for Authorization on user roles, ie, Admin, Moderator, Member and Guest.
I have to make it so:
Admins and Moderators should see all posts, members should only see their own posts, and guests should see no posts.
Sign in as a normal user, and you should only see the posts you've created.
application_policy.rb
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def index?
false
end
def show?
scope.where(:id => record.id).exists?
end
def create?
# Checks if user exists and is logged in
user.present?
end
def new?
create?
end
def update?
# Checks if user is logged in, the owner or admin
user.present? && (record.user == user || user.admin?)
end
def edit?
update?
end
def destroy?
update?
end
def scope
record.class
end
end
Here is what I am working on:
This will check if a user is present, and if the user is a moderator or administrator and only grant them access to view posts. Works just like the instructions state.
post_policy.rb
class PostPolicy < ApplicationPolicy
def index?
user.present? && (user.moderator? || user.admin?)
end
end
Now if I look back at my application_policy.rb I can see this line here, "Checks if the user is logged in, the owner, or admin":
user.preset? && (record.user == user || user.admin?)
If I try to add this into my authorization of index? I will keep getting a
"NoMethodError in PostsController#index"
class PostPolicy < ApplicationPolicy
def index?
user.present? && (user.moderator? || user.admin? || record.user == user)
end
end
Thank you.
Use scopes: https://github.com/elabs/pundit#scopes
In your case PostPolicy.rb should look like this:
class PostPolicy < ApplicationPolicy
def index?
true
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
if user.admin? || user.moderator?
scope.all
else
scope.where(user: user)
end
end
end
end
In one of my project I started to using pundit gem and I have a very simply policy that looks like this:
class CompanyPolicy < ApplicationPolicy
def index?
true if user.is_a? Administrator
end
def new?
true if user.is_a? Administrator
end
def create?
new?
end
def edit?
true if user.is_a? Administrator
end
def update?
edit?
end
end
And the question is how can I avoid repeating this:
true if user.is_a? Administrator
I do trick which looks like this:
class ApplicationPolicy
private
def self.permit_owner_to(*actions)
actions.each do |action|
define_method("#{action}?") do
owner?
end
end
end
def owner?
# owner logic
end
end
And used it in other policies
class ItemPolicy < ApplicationPolicy
permit_owner_to :show, :update, :destroy, :confirm
end
I don't actually think you need to remove this. By repeating this you are explicitly saying that this user must be an administrator to access this method. If you did want to though, you could just create a private method.
class CompanyPolicy < ApplicationPolicy
def index?
admin?
end
def new?
admin?
end
def create?
new?
end
def edit?
admin?
end
def update?
edit?
end
private
def admin?
user.is_a? Administrator
end
end
Guess this is a matter of personal preference.
You could use alias_method.
class CompanyPolicy < ApplicationPolicy
def index?
user.is_a? Administrator
end
alias_method :create?, :index?
alias_method :update?, :index?
end
You have a base class ApplicationPolicy which probably already contains:
def new?
create?
end
def edit?
update?
end
so you don't need to repeat these methods in your subclass.
.is_a? returns true or false so no need to explicitly return true if true.
That's a lot more succinct eh? :)
I combined answers from above and came up with the following:
class ApplicationPolicy
attr_reader :user
def initialize(user)
#user = user
end
def self.permit(roles, options)
return if options[:to].none?
options[:to].each do |action|
define_method("#{action}?") do
return #user.roles? Array.wrap(roles) if options[:when].blank?
send(options[:when]) and #user.roles? Array.wrap(roles)
end
end
end
end
which allows one to use it like this:
class CommentPolicy < ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#record = record
super(user)
end
permit %i[admin member], to: %i[show edit destroy update], when: :created_by_user
def created_by_user
#record.user == #user
end
end
and
permit :admin, to: %i[index update edit]
works as well
my roles method from user model looks like:
def roles?(user_roles)
user_roles.each do |role|
return true if role?(role)
end
false
end
def role?(role)
roles.any? { |r| r.name.underscore.to_sym == role }
end
I have Post model with published? field and some authorization system which defines admin? method inside ApplicationController.
I want to restrict access to unpublished posts and show them only to administrator.
I tried to define a scope accessible to return only published posts to users, but all posts for administrator.
scope :published, where(:published => true)
def self.accessible
admin? ? all : published
end
The problem is that admin? method can't be accessed inside the model. What is the best way to implement what I want?
# option 1
class Post < ActiveRecord::Base
def self.accessible_to user
user.admin? ? all : published
end
end
class PostsController < ApplicationController
def index
#posts = post.accessible_to current_user
end
end
# option 2
class Post < ActiveRecord::Base
def self.accessible is_admin
is_admin ? all : published
end
end
class PostsController < ApplicationController
def index
#posts = post.accessible admin?
end
end
One way, but not so abstract.
def self.published_unless(condition)
condition ? all : published
end
Post.published_unless(admin?)