Rails 5, Pundit with namespaced resources - ruby-on-rails

I'm trying to figure out how to use pundit with my namespaced resources.
I've read lots of SO posts from others saying they have problems with this, but those predate discussions on the pundit gem issue tracker. The issue tracker isn't clear about what the solution is - so I'm stuck.
I have folder called Stance, which is a namespace for nested resources, one of which is called overview.
The model.rb file is called:
class Stance::Overview < ApplicationRecord
The controller is called:
class Stance::OverviewsController < ApplicationController
The table in the db is called:
create_table "stance_overviews", force: :cascade do |t|
In my app/policies folder, I've tried various ways of making a policy, with various attempts at then referencing the policy in the views. I can't find a way that works.
I've tried making a folder called Stance with a file called overview_policy.rb, with:
class Stance::OverviewPolicy < ApplicationPolicy
In my organisation view (which has one stance::overview), i define a local variable):
<% if current_user.organisation_id != #organisation.id %>
<%= render 'stance/overviews/internal', overview: #organisation.overview %>
<% else %>
<%= render 'stance/overviews/external', overview: #organisation.overview %>
<% end %>
Then in the view, I'm trying:
<% if policy(overview).show? %>
<p><%= overview.explanation %></p>
<% end %>
I can't find a variation on this that works. Everything I've tried is a guess based on the SO posts, most of which predate the conversations on the pundit issue tracker.
I don't get an error message, but the content doesnt render when it should.
Can anyone see what I need to do to be able to use pundit with namespaced resources?

when I meet the namespace problem, I use Pundit.policy().
For your class, I think should be use Pundit.policy(current_user,[:stance, :overview]).show? in the view and use authorize [:stance, #overview] in the controller.
the Pundit.policy(current_user,[:stance, :overview]).show? will find show? in app/policies/stance/overview_policy.rb.
if you write authorize [:stance, #overview] on show in your controller, it will find show? in app/policies/stance/overview_policy.rb.

Related

Rails 4 - Helper Method - use in a view

I'm trying to figure out how to use helper methods in Rails 4.
I have two models, organisation and preferences.
The associations are:
Organisation has_one :preference
Preference belongs_to :organisation
In my preference table, I have an attribute called :prior_notice_required
In my organisation view, I'm trying to display the organisation's preferences. In my organisation view folder, I have a partial called preferences.
In my OrganisationsHelper.rb, I've tried this:
module OrganisationsHelper
def publicity_notice_required
if #organisation.preference.prior_notice_required == true
'Prior notice of publicity is required'
else
'Prior notice of publicity is not required'
end
end
In my organisation preferences partial, I then try each of these:
<%= #organisation.preference.prior_notice_required(publicity_notice_required) %>
<%= publicity_notice_required(#organisation.preference) %>
<%= publicity_notice_required(#organisation.preference.prior_notice_required) %>
I can't figure out how to get this working. Does anyone have any experience with helpers to see where I'm going wrong?
It is as simple as calling <%= publicity_notice_required %> in the view.
Rails Helpers are modules which are included across your application. This makes life easy but also has some drawbacks when it comes to Encapsulation & a good separation on concerns.
I'm not sure if this is right- but it seems to be working.
I change my view to:
<%= publicity_notice_required(#organisation.preference) %>
and my helper to:
def publicity_notice_required(organisation)
if #organisation.preference.prior_notice_required == true
'Prior notice of publicity is required'
else
'Prior notice of publicity is not required'
end
end
I'm not sure why the (organisation) needs to be in brackets or if its actually meant to include the preference as well. Hope this helps someone.

Manage user permission with an instance variable or in a helper

I'm looking for the cleanest way to handle a user permission. It would be used to define if a menu option can be displayed (menu is present in all views) and the access to a page.
So I was wondering which is the cleanest way to do it.
Set an instance variable in each action from the controller validating if the user had access
Add a method in the application helper validating each time it is call if the current user have access
You can define method in your ApplicationController who will check current user permissions. And you can use that method in before_action callback for those actions you need it.
I would recommend to look at cancancan gem (it's community driven support of cancan gem)
Using it it's easy to authorize actions and check abilities to decide show menu item or not.
You can also check out RailsCast about that subject to get understanding of whole idea.
Are you trying to implement an administrator or something similar? I think the cleanest way would be to just make a new column in the users table, which is initialized to false for most, but to true if the user is an admin (or something else). Then you can just make two partials to handle the two cases.
In that case, in your menu view (in your layout or whatnot) you would have this code or something similar:
<% if current_user.admin? %>
<%= render 'admin_page' %>
<% else %>
<%= render 'user_page' %>
<% end %>
Where I assume you define #current_user in your controller, or if you are using Devise, this is handled automatically.
Edit: Yes I endorse the earlier answer, CanCan is a good gem to handle these things also, you should consider using it. In such a case your code would look something like:
<% if can? :update, #user %>
# Edit something
<%= link_to edit_profile_path(#user), class: 'user' do %>
Edit your profile
<% end %>
<% end %>

Rails category (or filter) links in same controller?

Having trouble understanding how using links_to filter content within the same controller in the rails view works. My code is below:
# index.html.erb (link nav area)
<nav>
<%= link_to 'Daily Monitoring', root_path(:category => "dailymonitoring") %>
<%= link_to 'Smoke Tests', root_path(:category => "smoketests") %>
</nav>
# index.html.erb (cont.)
<ul id="results">
<% if #reportlinks == "dailymonitoring" %>
# dailymonitoring content
<% elsif #reportlinks == "smoketests" %>
# smoketests content
<% end %> <!-- end conditional -->
</ul>
# reports_controller.rb
if params[:category]
#reportlinks = Report.where(:category => params[:category])
else
#reportlinks = Report.all
end
# model (report.rb)
class Report
include ActiveModel::Model
attr_accessor :reports, :smokereports
belongs_to :reports, :smokereports, :reportlinks
end
The error I'm getting is undefined method `belongs_to' for Report:Class and a < top (required) > error. There's no database involved. I'm just trying to make it known that I want to click on any of the links and that filters only the block of content within that if/else statement.
Do I need to create a new controller for the if/else statement to work? Please let me know if more code is needed to get a better understanding. Thanks.
belongs_to is defined in ActiveRecord::Associations, which is part of ActiveRecord. You are manually including ActiveModel::Model which doesn't offer any association-related capabilities.
Includes the required interface for an object to interact with ActionPack, using different ActiveModel modules. It includes model name introspections, conversions, translations and validations. Besides that, it allows you to initialize the object with a hash of attributes, pretty much like ActiveRecord does.
Assuming you don't need the whole ActiveRecord database persistence capabilities but you need the association style, then you'll need to deal with that manually.
Therefore, you will have to define your own methods to append/remove associated records and keep track of them.
The association feature of ActiveRecord is strictly tied with the database persistence.

How to hide parts of the view given a user role on Rails 4

I'm trying to hide parts of my views depending on the User role.
So let's say I want only admins to be able to destroy Products. Besides the code in the controller for preventing regular users from destroying records, I would do the following in the view:
<% if current_user.admin? %>
<%= link_to 'Delete', product, method: :delete %>
<% end %>
The previous code works, but it's prone to errors of omission, which may cause regular users to see links to actions they are not allowed to execute.
Also, if I decide later on that a new role (e.g. "moderator") can delete Products, I would have to find the views that display a delete link and add the logic allowing moderators to see it.
And if there are many models that can be deleted only by admin users (e.g. Promotion, User) maitenance of all the ifs would be pretty challenging.
Is there a better way of doing it? Maybe using helpers, or something similar? I'm looking for something maybe like this:
<%= destroy_link 'Delete', product %> # Only admins can see it
<%= edit_link 'Edit', promotion %> # Again, only admins see this link
<%= show_link 'Show', comment %> # Everyone sees this one
I found these two questions that are similar to mine, but none of them answered my question:
Show and hide based on user role in rails
Ruby on Rails (3) hiding parts of the view
I strongly recommend pundit.
It allows you to create "policies" for each model. For your Product model you might have a ProductPolicy that looks something like this
class ProductPolicy < ApplicationPolicy
def delete?
user.admin?
end
end
In your view you can do something like this
<% if policy(#post).delete? %>
<%= link_to 'Delete', product, method: :delete %>
<% end %>
If later on you want to add a moderator role, just modify the policy method
class ProductPolicy < ApplicationPolicy
def delete?
user.admin? || user.moderator?
end
end
So I kind of figured a way to move the IFs out of the view. First, I override the link_to helper in my application_helper.rb:
def link_to(text, path, options={})
super(text, path, options) unless options[:admin] and !current_user.admin?
end
Then on my views I use it as:
<%= link_to 'Edit Product', product, admin: true, ... %>
This prevents regular users from seeing admin links, but for other html tags with content inside, such as divs, tables etc., an if would still be needed.
CanCan is another gem that lets you define "Abilities" per user role.
In views you can use something like if can? :delete, #post to check if the
user may delete that specific post.
Using the CanCan and Role gems, what is still needed is a way to Check The Route and see if "current_user" has permissions to access that Route based on their role(s) - then show/hide based on that.
This saves the user clicking on things and getting told they cannot see it - or us having to write per-item "if" logic specifying what roles can see what list-items (which the customer will change periodically, as roles are changed/refined) around every single link in one's menu (consider a bootstrap menu with 50+ items nested in groups with html formatting, etc), which is insane.
If we must put if-logic around each menu-item, let's use the exact same logic for every item by checking the role/permissions we already defined in the Ability file.
But in our menu-list, we have route-helpers - not "controller/method" info, so how to test the user's ability to hit the controller-action specified for the "path" in each link?
To get the controller and method (action) of a path (my examples use the 'users_path' route-helper) ...
Rails.application.routes.recognize_path(app.users_path)
=> {:controller=>"users", :action=>"index"}
Get just the controller-name
Rails.application.routes.recognize_path(app.users_path)[:controller]
=> "users"
Ability uses the Model for its breakdown, so convert from controller name to it's model (assuming default naming used) ...
Rails.application.routes.recognize_path(app.users_path)[:controller].classify
=> "User"
Get just the action-name
Rails.application.routes.recognize_path(app.users_path)[:action]
=> "index"
And since the "can?" method needs a Symbol for the action, and Constant for the model, for each menu-item we get this:
path_hash = Rails.application.routes.recognize_path(app.users_path)
model = path_hash[:controller].classify.constantize
action = path_hash[:action].to_sym
Then use our existing Abilty system to check if the current_user can access it, we have to pass the action as a symbol and the Model as a constant, so ...
<% if can? action model %>
<%= link_to "Users List", users_path %>
<% end %>
Now we can change who can see this resource and link from the Ability file, without ever messing with the menu, again. But to make this a bit cleaner, I extracted out the lookup for each menu-item with this in the app-controller:
def get_path_parts(path)
path_hash = Rails.application.routes.recognize_path(path)
model_name = path_hash[:controller].classify.constantize
action_name = path_hash[:action].to_sym
return [model_name, action_name]
end
helper_method :get_path_parts
... so I could do this in the view (I took out all the html-formatting from the links for simplicity, here):
<% path_parts = get_path_parts(users_path); if can?(path_parts[1], path_parts[0]) %>
<%= link_to "Users Listing", users_path %>
<% end %>
... and to make this not take all day typing these per-menu-item if-wraps, I used regex find/replace with capture and wildcards to wrap this around every list-item in the menu-item listing in one pass.
It's far from ideal, and I could do a lot more to make it much better, but I don't have spare-time to write the rest of this missing-piece of the Role/CanCan system. I hope this part helps someone out.

Is Property in any way reserved for rails model naming? (Rails pluralisation is killing me.)

I am trying to add a model called Properties in Rails 3.1 So I used created it using ryan bates nifty generator, although the model itself is very basic for now and only includes.
class Property < ActiveRecord::Base
belongs_to :user
end
in my resources I have"
resources :properties
In one of my views I am simply trying to do the following:
<% for property in Property.all %>
<p>description etc</p>
<% end %>
but it gives me the following error?!
undefined method `all' for Property:Module
Now it works if I replace Property.all with User.all or House.all but for some reason Property doesn't work. I'm kinda new to rails and think it has something to do with pluralization but I can't figure it out and its killing me. If anyone could please help that would be epic! Cheers
You can use Inflections to extend default dictionary ( peoperty word is not included by default). The official API can help you with it
In your model definition you need to tell it the real name of your table, eg.
class Property < ActiveRecord::Base
set_table_name "properties"
end
Also you may want to adapt the code slightly for showing your data, it's better to use your controller to grab the data, and the view to display it
In your controller
def index
#properties = Property.all
end
In your view
<% #properties.each do |property| %>
<%= property.description %>
<% end %>

Resources