Undefined method 'visible_to' - Simple Rails website - ruby-on-rails

I'm in the process of creating a very simple wiki based website where users can upgrade and downgrade their account. One of the perks of being a 'premium' member is the ability to create private wikis.
The issue I'm having is to do with this piece of code in my wikis_controller.rb:
def index
#wiki = Wiki.visible_to(current_user)
#wikis = policy_scope(Wiki)
end
When the code is written like that, I get an error message saying 'undefined method 'visible_to'. But, if I comment out the first line in that method so that it looks like this:
def index
##wiki = Wiki.visible_to(current_user)
#wikis = policy_scope(Wiki)
end
the user can see the wiki index, but they cannot see their private wikis.
Any ideas where I am going wrong? For reference, here is my wiki_policy.rb:
class WikiPolicy < ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def update?
user.present?
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
wikis = []
if user.role == 'admin'
wikis = scope.all # if the user is an admin, show them all the wikis
elsif user.role == 'premium'
all_wikis = scope.all
all_wikis.each do |wiki|
if !wiki.private? || wiki.user == user || wiki.users.include?(user)
wikis << wiki # if the user is premium, only show them public wikis, or that private wikis they created, or private wikis they are a collaborator on
end
end
else # this is the lowly standard user
all_wikis = scope.all
wikis = []
all_wikis.each do |wiki|
if !wiki.private? || wiki.users.include?(user)
wikis << wiki # only show standard users public wikis and private wikis they are a collaborator on
end
end
end
wikis # return the wikis array we've built up
end
end
end
and here is my application_policy.rb:
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def index?
end
def show?
if user.standard?
true
else
false
end
end
def downgrade?
if user.premium?
true
else
false
end
end
def create?
if user.present?
true
else
false
end
end
def new?
create?
end
def update?
user.present?
end
def edit?
update?
end
def destroy?
if user.present? && user.admin? #|| record.user == user)
true
else
false
end
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
Many Thanks.

But even when I do add a 'visible_to' method to Wiki controller, I still pick up the same error message.
Without seeing your code I can't be certain but I am 95% sure your problem is that you're defining an instance method when you mean to define a class method.
You are probably doing this:
class Wiki
# instance method
def visible_to test
puts test
end
end
Which would allow you to call the function like instance method:
irb(main):018:0> Wiki.new.visible_to "hello world"
# hello world
irb(main):007:0> Wiki.visible_to "hello world"
# NoMethodError: undefined method `visible_to' for Wiki:Class
But since you are actually calling the function like a class method:
Wiki.visible_to(current_user)
You need to define your method like this:
class Wiki
# class method
def self.visible_to test
puts test
end
end
Now you can call the class method:
irb(main):024:0> Wiki.visible_to "hello world"
# hello world
irb(main):007:0> Wiki.new.visible_to "hello world"
# NoMethodError: undefined method `visible_to' for #<Wiki:0x00000001d16a68>
Read more here!

Related

Pundit policy for personalized routes

I'm working on a rails app where I wrote a personalized route called "all_designs"; with the corresponding method on the controller and the view, before I add pundit to my project it was working fine.
Now I'm having this error:
Pundit::AuthorizationNotPerformedError in DesignsController#all_designs
I understand that I'm missing a policy for this action, but the way I'm trying is not working.
How can I add a policy for this method?
Controller:
class DesignsController < ApplicationController
before_action :set_design, only: [:show,:edit,:update,:destroy]
def index
#designs = policy_scope(Design.where(user: current_user, status: 'activo'))
#user = current_user
end
def all_designs
#designs = Design.where(user: current_user)
#user = current_user
end
...
end
Policy:
class DesignPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.all
end
end
def create?
true
end
def show?
true
end
def destroy?
user == record.user
end
def update?
# If the user is the owner of the design
user == record.user
end
def all_designs?
true
end
end
I would consider separate controller and policy for this as what you're doing is really just a nested route (designs belonging to a singleton resource).
scope 'user', module: :users do
resources :designs, only: :index
end
module Users
class DesignsPolicy
class Scope < Scope
def resolve
#user.designs # make sure user has a `has_many :designs` assocation.
end
end
end
def index?
true
end
end
# Represents designs belonging to the current user
module Users
class DesignsController < ApplicationController
# GET /user/designs
def index
#designs = policy_scope
end
end
end
This lets you separate the logic of displaying the the current users designs from /designs which would display everything in a clean way.
Every method on the controller which needs to be authorized, needs to contains an explicit declaration like this:
def all_designs
#designs = Design.where(user: current_user)
#user = current_user
authorize #designs
end
The reason it wasn't working was: I missed the authorize line

Rails_admin and rails_admin_pundit error after upgrade to 5.2.1

NoMethodError at /
protected method `policy' called for #<RailsAdmin::MainController:0x007ff5e5d1a528>
Did you mean? policies
Here's the first thing it looks at in the error page (this is in the gemfile code)
# This method is called to find authorization policy
def policy(record)
begin
#controller.policy(record)
rescue ::Pundit::NotDefinedError
::ApplicationPolicy.new(#controller.send(:pundit_user), record)
end
end
private :policy
Getting this error when I try to visit /admin - nothing changed, was working fine in 5.1.6.. I didn't change the policy.rb file, i didn't change any controller code, nothing was changed at all apart from a gemfile update to go to rails 5.2.1
My application policy..
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?
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
def rails_admin?(action)
case action
when :dashboard
#user.admin?
when :index
#user.admin?
when :show
#user.admin?
when :new
#user.admin?
when :edit
#user.admin?
when :destroy
#user.admin?
when :export
#user.admin?
when :history
#user.admin?
when :show_in_app
#user.admin?
else
raise ::Pundit::NotDefinedError, "unable to find policy #{action} for #{record}."
end
end
end
I don't know what caused this to happen.. can't find anything on Google and the gem rails admin pundit was last updated a year ago.
Make sure you have
config.parent_controller = "::ApplicationController"
on your config/initializers/rails_admin.rb, to indicate who's the parent controller. This solved it for me.

How to have a guest user after log in retain his data with Devise + Rails 5

As by the Devise docs that are so often referred to (Found here), I made this application_controller.rb for an e-commerce platform:
class ApplicationController < ActionController::Base
# ...
# The order that can be used in the shop up untill the point of payment.
def current_order
if current_user
current_order = Order.where(customer_id: current_user.id, status: :open).first_or_create(status: :open)
else
current_order = Order.where(customer_id: guest_user.id).first_or_create(status: :open)
end
end
##### From Devise documentation: guest user #####
def current_or_guest_user
if current_user
if session[:guest_user_id] && session[:guest_user_id] != current_user.id
logging_in
guest_user(with_retry = false).reload.try(:destroy)
session[:guest_user_id] = nil
end
current_user
else
guest_user
end
end
def guest_user(with_retry = true)
#cached_guest_user ||= User.find(session[:guest_user_id] ||= create_guest_user.id)
rescue ActiveRecord::RecordNotFound
session[:guest_user_id] = nil
guest_user if with_retry
end
private
# Custom logging in
def logging_in
guest_order = current_order
guest_order.customer.id = current_user.id
guest_order.save!
end
def create_guest_user
u = User.create(:first_name => "guest", :email => "guest_#{Time.now.to_i}#{rand(100)}#example.com")
u.save!(:validate => false)
session[:guest_user_id] = u.id
u
end
def store_current_location
store_location_for(:user, request.url)
end
# ...
end
Then in my registrations_controller.rb:
class RegistrationsController < Devise::RegistrationsController
def create
super
current_or_guest_user
end
end
And in my sessions_controller.rb I have something similar.
In theory this should work.
What happens is that the not-logged-in-user / guest_user goes to check out and sees:
his order
a form to register
a form to log in
Filling in either one of the forms gives me (after a complete rerender)
his order - still on the guest account!!!
his profile information
(and a new form for an address which after filling will make a confirm button show up for proceeding in the payment process.)
Now everything got jammed because the address should be stored as a child on an order that can not be found...
The whole procedure of paying does work when already logged in. The order in the check out view simply already has a customer_id of the right user.
Thing is: I want it to work for a user visiting the shop as a guest. I want:
guest_order = current_order
guest_order.customer.id = current_user.id
guest_order.save!
but somehow this code in my logging_in doesn't get executed

Pundit not defined error for a nested controller

I'm adding a csv upload feature to my site. I want to be able to add startups to categories that I've created.
I'm getting this error:
Startup_policy.rb file
class StartupPolicy < CategoryPolicy
def create?
user.present?
end
def import?
user.present?
end
end
Startup_controller.rb
class StartupsController < ApplicationController
before_action :authenticate_user!
def create
#startup = #startupable.startups.new startup_params
#startup.user = current_user
#startup.save
redirect_to #startupable, notice: "Your startup was added succesfully."
authorize #startup
end
def import
count = Startup.import params[:file]
redirect_to category_startups_path, notice: "Imported #{count} startups"
authorize #startups
end
private
def set_category
#startup = Startup.find(params[:id])
authorize #startup
end
def startup_params
params.require(:startup).permit(:company, :url)
end
end
Startup.rb
class Startup < ActiveRecord::Base
belongs_to :startupable, polymorphic: true
belongs_to :user
def self.import(file)
counter = 0
CSV.foreach(file.path, headers: true, header_converters: :symbol) do |row|
startup = Startup.assign_from_row(row)
if startup.save
counter += 1
else
puts "#{startup.company} - #{startup.errors.full_messages.join(",")}"
end
end
counter
end
def self.assign_from_row(row)
startup = Startup.where(company: row[:company]).first_or_initialize
startup.assign_attributes row.to_hash.slice(:url)
startup
end
end
EDIT: added Category_policy.rb
class CategoryPolicy < ApplicationPolicy
def index?
true
end
def create?
user.present?
end
def update?
return true if user.present? && (user.admin? || category.user)
end
def show?
true
end
def destroy?
user.present? && user.admin?
end
private
def category
record
end
end
What am I missing here? Pundit is pretty easy to use but for some reason I'm stumped on this one.
I don't see that you've set #startups anywhere. So it's nil, which explains the error message. I assume you meant to set it to the resulting list you get after importing the CSV. Or, since all you are checking in the policy is whether you have a user, you could just authorize :startup which will get you to the right policy, and not care which startups you have.

Authorise user to edit a particular field using Pundit in Rails

I'm running Pundit in my Rails app for authorisation. I seem to be getting the hang of it all but want to know how to restrict the edit or update actions to a certain field.
For example, a user can edit their user.first_name, user.mobile or user.birthday etc but can't edit their user.role. Essentially my logic is, let the user edit anything that's cosmetic but not if it is functional.
These fields should only be able to be edited by a user who has a 'super_admin' role (which I have setup on the user.rb with methods such as the below).
def super_admin?
role == "super admin"
end
def account?
role == "account"
end
def segment?
role == "segment"
end
def sales?
role == "sale"
end
def regional?
role == "regional"
end
def national?
role == "national"
end
def global?
role == "global"
end
I pretty much have a clean slate user_policy.rb file where the update and edit actions are the default
def update?
false
end
def edit?
update?
end
Maybe I am thinking entirely wrong about this and should just wrap a user.super_admin? if statement around the role field on the user show page but this feels wrong if I am only using that tactic for security.
Use Pundit's permitted_attributes helper which is described on the gem's README page: https://github.com/elabs/pundit
# app/policies/post_policy.rb
class PostPolicy < ApplicationPolicy
def permitted_attributes
if user.admin? || user.owner_of?(post)
[:title, :body, :tag_list]
else
[:tag_list]
end
end
end
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def update
#post = Post.find(params[:id])
if #post.update_attributes(post_params)
redirect_to #post
else
render :edit
end
end
private
def post_params
params.require(:post).permit(policy(#post).permitted_attributes)
end
end
In your views, you can limit what users can see based on their role.
User View
- if current_user.super_admin?
= f.select(:role, User.roles.keys.map {|role| [role.titleize.role]})
- else
= user.role
And in the policy you can call the role of the user to make sure they are able to edit.
class UserPolicy
attr_reader :current_user, :model
def initialize(current_user, model)
#current_user = current_user
#user = model
end
def edit?
#current_user.super_admin || #current_user == #user
end
end

Resources