I currently have a view simplified below...
<% if can? :manage, #task %>
Admin
<% elsif can? :update, #task %>
Moderator
<% elsif current_user %>
User
<% else %>
Not logged in
<% end %>
This view has a large number of fields that are wrapped in similar conditionals for each user and I currently have to log in and out of test accounts to check formatting.
I want to be logged in as an admin but have a dropdown to select whether the page is rendered as Admin or Moderator or User or Not logged in
I have some rough ideas of solutioning, but don't know which to follow...
Bake into cancan with an extra column on user that I can set from a navbar dropdown form
Create user specific methods
Allow a url parameter to request session view and render accordingly
Is there a best practice around this?
How about if you implemented something like the real/effective user dichotomy of Unix?
You're existing infrastructure is the 'real' user based on whatever authentication has occurred but you also associate an 'effective user' with each session.
The effective user starts out the same as the real user but can be changed to something else. Changing the effective user should be conditioned on the real user having admin rights.
Condition all your layout on the effective user and not on the real user.
Condition your 'change effective user' drop down on the real user.
Related
I'm creating a bloglike application which includes features based around different tiers of admins.
I have a main admin who I want to give the ability to turn a user into a subadmin, which will be a type of admin who has access to some admin features but not all.
Currently I have this _users partial which I use in a view to display all users to the admin:
<li>
<%= link_to user.name, user %>
| <%= pluralize(user.how_many_new_posts?, "unsubmitted daily post") %>
<% if current_user.admin? && !current_user?(user) %>
| <%= link_to "delete account", user, method: :delete,
data: { confirm: "You sure?" } %>
<% end %>
</li>
This will render the name of each user, how many unsubmitted posts they have, and the option to delete the user.
I want to include another option here, a way of clicking a button to make the specified user a subadmin. Once this button is clicked it should give the admin the ability to assign other users to this subadmin, so that they can monitor them in the same way as the above partial. I assume this will require some new kind of partial, but I'm not totally sure how it would work. Does anyone have any experience with this?
You will need to create a model say Role which has_many users and User belongs_to Role or whatever relationship you prefer.
You may have difderent roles in there..
You can then manage there accessibilities/abilities based on their role designing your own or there are gems at your disposal e.g. cancancan
Then, in your view you can provide a dropdown list for available roles and change the role_id on submit.
How should I write a if condition in the view using Slim? I would like to show content if the current_user is a subscriber. I have a subscriptions table with a user_id and cancelled column. User has access to the website if their id can be found in the subscriptions table under user_id and the cancelled column is NULL. If cancelled has a 1 value then the user no longer has access.
If you have your relationship set up, something like this as an instance method in the user model should work.
def subscribed?
subscriptions.where(cancelled: nil).exists?
end
Then in the view, you can do something like:
<% if user.subscribed? %>
Here ya go
<% else %>
Go away
<% end %>
You could obviously just put the logic in the subscribed? method in the view, but this is a little cleaner, maybe.
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.
I've got an application where the user can set up a folder to keep notes in. What I had previously was a hidden form field to store the id of the person who created it, i.e.:
<%=f.hidden_field 'user_id', :value => current_user.id %>
However, I now need to add a 'keyholder', who has read-only access to this folder. I have a list of links, which only appear if the user has added a folder, or the keyholder can set one up for them.
The keyholder is a regular user themselves, so the above code would only set up a folder with their own id, not that of the person whose account they are accessing. The keyholder has an 'access_id' that matches the user id of the the person whose account they can access.
How do I set it up so that the form is capturing the right user id?
What I'm trying to acheive is the following (this doesn't work, but might give a better idea of what I mean):
<% if current_user.access.folder.nil? %>
<li><%= link_to 'Create a Folder',
new_folder_path(:user_id => current_user.access_id) %></li>
<% end %>
And what would I need to change in the folder form partial to get it to accept this user id? Thanks!
You'd better use an authorization gem such like cancan
I'm not sure i completely understand what you're trying to do, but rather than storing the user id in a hidden field. Just use <%= current_user.id %> on any page as it seems your doing.
Then depending on how your models are setup, just create a helper method to check access of the page that the user is on. I'm assuming your helper will check the params[:id] or params[:user_id] to get the current page.
I have the following code in my home.html.erb file;
<!-- if seeker is logged in show button "Grab it" -->
<% if user_signed_in? %>
<div class="grabit alignright">
<small>want to work on this?</small>
<!-- if seeker is not logged in, show him the output text of "Sign in to work on this job" -->
<% else %>
<small>are you a guru? want to work on this? Sign up.</small>
<% end %>
</div>
Now as you can see I'm trying to have Seeker as a user role. Depending if that type of user with that type of role is signed in or not, different content is shown.
I have Devise + CanCan installed. Now very new to this and I've looked all over to see how to do this. I will have "Users" with normal roles of users and "Seekers" with seeker role. Using the above code only shows when a user is signed in, but not a seeker.
Is it as simple as seekers_signed_in? that I should be using? compare to user_signed_in? I've generated the Devise views for both Users and Seekers and Admins. Admins will be able to delete, update, etc. for users and seekers. Users post items and Seekers grab them.
Can anyone help?
You don't have to create Users& Seekers (two devise models), instead you can create only one model as common and call it User, then add as many roles as you need.
I recommend using this gem for easy roles configuration,
Then in your home.html.erb you simply do the following:
<!-- if seeker is logged in show button "Grab it" -->
<% if user_signed_in? && current_user.is_seeker? %>
<div class="grabit alignright">
<small>want to work on this?</small>
<!-- if seeker is not logged in, show him the output text of "Sign in to work on this job" -->
<% else if !user_signed_in?%>
<small>are you a guru? want to work on this? Sign up.</small>
<% else%>
<small>You are not a seeker, you need to be a seeker!</small>
<% end %>
</div>
CanCan is used at Controller level, so the above approach is easy and direct for your case.
I hope this will help.