How to create Policies for API-Controller's using Pundit gem?
Api controller path: /app/controllers/api/posts_controller.rb
#posts_controller.rb
class Api::PostsController < ApplicationController
def create
......
end
def update
......
end
def delete
......
end
end
I have Controller for the same and the corresponding Model
controller path: /controllers/posts_controller.rb
#posts_controller.rb
class PostsController < ApplicationController
def create
......
end
def update
......
end
def delete
......
end
end
I have created the policies for posts controller. How to create the same for API's Controller
Pundit is resource-based, not controller-based. When you call authorize and pass it a resource, Pundit cares about the action name and the resource type, but it does not care about the controller name.
Regardless of whether you call from the Api::PostsController:
# /app/controllers/api/posts_controller.rb
class Api::PostsController < ApplicationController
def create
#post = Post.find(params[:post_id])
authorize #post
end
end
or from your original PostsController:
# /app/controllers/posts_controller.rb
class PostsController < ApplicationController
def create
#post = Post.find(params[:post_id])
authorize #post
end
end
So long as #post is a member of the Post class, you can call authorize #post from the controller of a parent or child or a completely unrelated controller, it doesn't matter. In all cases Pundit will go and look for a method called create? within app/policies/post_policy:
# app/policies/post_policy.rb
class PostPolicy < ApplicationPolicy
attr_reader :user, :post
def initialize(user, post)
#user = user
#post = post
end
def create?
user.present?
end
end
Related
I have created a user controller with login and logout.
After login user should be able to give some comment in text box input and it should be saved in db.
How to associate the comment to the user. My users controller is
class UsersController < ApplicationController
def new
end
def create
user = User.new(user_params)
if user.save
session[:user_id] = user.id
redirect_to '/url'
else
redirect_to '/signup'
end
end
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
end
my urls controller is
class UrlsController < ApplicationController
def new
end
def create
url = Url.new(url_params)
url.save
redirect_to #url
end
def url_params
params.require(:url).permit(:url)
end
end
I am getting error in url_params. How it should be for a text field?
For example you need to create Comment model
class Comment < ApplicationRecord
belongs_to :user
end
class User < ApplicationRecord
has_many :comments
end
and create CommentsController
class CommentsController < ApplicationController
before_action :set_user
def create
#user.comments.create(comment_params)
redirect_to root_url
end
private
def set_user
#user = User.find(session[:user_id])
end
def comment_params
params.require(:comment).permit(:text)
end
end
I'm using Devise for student authentication and I have other actions called show_profile and edit_profile so a student can see and edit his profile.
The problem is the controller I made over writes the Devise controllers so the sign in/up stops working. How can I make my controller an extension for the Devise controllers?
If I put those two:
class Students::RegistrationsController < Devise::RegistrationsController
class StudentsController < ApplicationController
and comment this when logging in class StudentsController < ApplicationController and this class Students::RegistrationsController < Devise::RegistrationsController after login it works.
class Students::RegistrationsController < Devise::RegistrationsController
#class StudentsController < ApplicationController
private
def secure_params
params.require(:student).permit(:name, :father_name, :grand_father_name)
end
public
before_action :authenticate_student!
def show_profile
#student = current_student
end
def edit_profile
#student = current_student
end
def update_profile
#student = current_student
if #student.update_attributes(secure_params)
redirect_to(:action => 'show_profile',:id => #student.id )
flash[:notice] = "student edited successfully"
else
render('edit_profile')
end
end
end
You have to tell Devise that you're using a custom setup in your routes.rb
Something like this, tweak to your needs.
devise_for :users,
controllers: {
registrations: 'student/registrations'
}
Use devise_for :students obviously if that's what your model is called.
I have 2 controller, 1 for user and 1 for admin.
controllers/articles_controller.rb
class ArticlesController < ActionController::Base
...
def show
#article = Article.find(parmas[:id])
authorize #article
end
...
end
controllers/admin/articles_controller.rb
class Admin::ArticlesController < AdminController
...
def show
#article = Article.find(parmas[:id])
authorize #article
end
...
end
And i have 2 file policy
policies/article_policy.rb
class ArticlePolicy
extend ActiveSupport::Autoload
autoload :Admin
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def show?
# allow show for every user.
true
end
end
And one file policies/admin/article_policy.rb
class Admin::ArticlePolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def show?
# only show if use have role manager
user.manager?
end
end
but when i use a account user to show articles at /admin/articles/1/. It show normaly, Should is "Access denied".
How to fix this? (I use gem pundit 1.10).
Use the authorize method to pass the namespace as a parameter.
class ArticlesController < ActionController::Base
...
def show
#article = Article.find(parmas[:id])
authorize [:admin, #article]
end
...
end
How can I access a controller's instance variable inside of a model?
In this example, I want to access the instance variable #user inside of the model.
Post Controller
class PostController < ApplicationController
def destroy
#user = User.find(params[:user_id])
post = Post.find(params[:id])
end
end
Post Model
class Post < ActiveRecord::Base
belongs_to :user
before_destroy :check_if_owned_by_user
if self.user != #user
return false
end
end
end
you cannot access #user in the post model. the callbacks do not accept parameters as they triggered automatically. you have to explicitly call the method from the controller. like this.
class PostController < ApplicationController
def destroy
#user = User.find(params[:user_id])
post = Post.find(params[:id])
if post.check_if_owned_by_user(#user)
#delete it
end
end
end
class Post < ActiveRecord::Base
belongs_to :user
def check_if_owned_by_user(user)
self.user == user
end
end
Checking whether the current user is allowed to delete an object is an authorization concern, and should be handled within your authorization method(eg. cancan).
If you must handle it manually, consider using a service object to achieve the same. Callbacks are messy things once they accumulate.
class DestroyPost
def initialize post, user
#post = post
#user = user
end
def call
return false unless #post.user = #user
#post.destroy
end
end
You'd call DestroyPost.new(#post, #user).call in the controller instead of #post.destroy
So, I'm trying to use the gem pundit. I'm just trying to figure out how to have an index view for users and admins. I want to render all results for an admin and only related posts for a user. I've googled and searched on github, but I'm not find any luck. What do I have to put in my policy and controller?
original code
class PostsPolicy
attr_reader :current_user, :model
def initialize(current_user, model)
#current_user = current_user
#post = model
end
def index?
#current_user.admin?
end
end
controller
class PostsController < ApplicationController
before_filter :load_user
before_filter :authenticate_user!
after_action :verify_authorized
def index
#posts = Post.order('title').page(params[:page]).per(25)
authorize User
end
private
def load_user
#user = User.find_by_id(params[:user_id])
end
end
second update
class PostsPolicy
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
if user.admin?
scope.all
else
scope.where(user: user)
end
end
end
end
controller
class PostsController < ApplicationController
before_filter :load_user
before_filter :authenticate_user!
after_action :verify_authorized
def index
#posts = policy_scope(Post).order('title').page(params[:page]).per(25)
authorize User
end
private
def load_user
#user = User.find_by_id(params[:user_id])
end
end
third update
class PostPolicy
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
if user.admin?
scope.all
else
scope.where(user: user)
end
end
end
def index?
user.admin? || user.posts.count > 0
end
end
controller
class PostsController < ApplicationController
before_filter :load_user
before_filter :authenticate_user!
after_action :verify_authorized
def index
#posts = policy_scope(Post).order('title').page(params[:page]).per(25)
authorize User
end
private
def load_user
#user = User.find_by_id(params[:user_id])
end
end
** final update with working code **
class PostPolicy
attr_reader :user, :model
def initialize(user, model)
#user = user
#post = model
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
if user.admin?
scope.all
else
scope.where(user: user)
end
end
end
def index?
user.admin? || user.posts.count
end
end
controller
class PostsController < ApplicationController
before_filter :load_user
before_filter :authenticate_user!
after_action :verify_authorized
def index
#posts = policy_scope(Post).order('title').page(params[:page]).per(25)
authorize Post
end
private
def load_user
#user = User.find_by_id(params[:user_id])
end
end
What you're looking for is a Scope:
class PostsPolicy
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
if user.admin?
scope.all
else
scope.where(user: user)
end
end
end
end
Then in your controller
def index
#posts = policy_scope(Post).order('title').page(params[:page]).per(25)
authorize User
end
Edit
As a side note, authorize User will probably not serve you well in the long run. You're essentially creating an index policy that would need to serve every index. If you want to to authorize visibility to the index page you can still do something like this in your policy:
def index?
user.admin? || user.posts.count > 0
end
Assuming that relationship is set up, then you would call authorize Post in your index controller before your policy_scope.