Adding an admin role to a Rails app - ruby-on-rails

I recently created a small rails app using Rails Composer with devise.
I then followed the guide to an admin attribute to the users table using this guide: https://github.com/plataformatec/devise/wiki/How-To:-Add-an-Admin-Role.
I'm struggling with this one thing:
in app/views/layouts/_nav_links_for_auth.html.erb, I have:
<% if user_signed_in? %>
<% if current_user.try(:admin?) %>
<li> <%= link_to 'Users', users_path %></li>
<% end %>
<% end %>
I have set the attribute to true for the user I'm logged in as - however, the statement if current_user.try(:admin?) above doesn't return true. I've doubled checked in the sqlite development database that the attribute is actually set to true, and it is.
So, I'm stuck. As best I can figure out, it doesn't know what actually is an "admin". In my user model, I have this (which was there by default):
class User < ActiveRecord::Base
enum role: [:user, :vip, :admin]
after_initialize :set_default_role, :if => :new_record?
...
end
I'd love some help with getting to this work. What do I need to do?

Why do you have an enum if you have an explicit admin attribute for your User model?
If you're using enum, you'd have a role attribute, which will give you methods such as admin?, or you need to have an explicit admin attribute - which you should have if you have fired the migration that you posted.
To clear up any potential confusion, enum is used if you have strict criteria for a column, and only wish your users to have certain values assigned to it. Roles is a great example:
#app/models/user.rb
class User < ActiveRecord::Base
enum role: [:admin, :user] #-> requires "role" column which will store either :admin (0) or :user (1) values
end
In your case, you have several issues.
Firstly, boolean columns create appropriate methods in your model for that column...
Having admin as a boolean column will give you #user.admin? method.
<%= content_tag :li, link_to('Users', users_path) if user_signed_in? && current_user.admin? %>
This is what you should use if you have the migration as you stated from the Devise link (IE you have an admin boolean column)
--
The second problem you have is that you're getting confused in your model.
IF you wanted to have a default role, and have potential roles defined with an enum, you'd want to use the following:
#app/models/user.rb
class User < ActiveRecord::Base
enum role: [:user, :vip, :admin]
# remove after_init callback
end
You'd set a default role by setting the role default value in your db:
$ rails g migration SetDefaultRole
#db/migrate/set_default_role_______.rb
class SetDefaultRole < ActiveRecord::Migration
def change
add_column :users, :role, :integer, default: 1
end
end
$ rails db:migrate #-> if you're using Rails 5
$ rake db:migrate #-> Rails 4+

You could use either the role enum or the admin attribute but not both.
The problem in your case is that the admin? method being called is the one that is defined by enum (which checks the role) rather than admin? method provided by activerecord for boolean columns.
So though your admin column is true, your role is not being set to admin (for reasons we can not investigate because your set_default_role implementation is not available).
I recommend adopting a role based system, investigating why the correct role is not being set and then getting rid of the admin column.

Related

Rails ActiveAdmin modify resource object

I've currently got a user object but to avoid redundancy, I'd like to wrap it into a presenter object called MerchantUser/ProviderUser. However, with ActiveAdmin, I'm a little confused on how to do this. I've tried using before_create to change the user into the corresponding presenters but in index...do, I'm still seeing that user.class is equal to User and not the wrapper classes that I've defined.
I've looked into scoping_collection but unfortunately that only works on collections and not individual objects?
ActiveAdmin.register User, as: "Companies" do # rubocop:disable Metrics/BlockLength
before_create do |user|
if user.merchant?
user = MerchantUser.new(user)
else
user = ProviderUser.new(user)
end
end
actions :all, except: [:destroy]
permit_params :name, :email, contract_attributes: [:id, :flat_rate, :percentage]
filter :role, as: :select
index do # rubocop:disable Metrics/BlockLength
column :name do |user|
user.name <---I want it so I can just do this without the if/else blocks like below.
end
column :role
column :contact_phone
column :email
column :website do |user|
if user.merchant?
user.company.website
else
user.provider.website
end
end
column :flat_rate do |user|
money_without_cents_and_with_symbol(user.contract.flat_rate)
end
column :percentage do |user|
number_to_percentage(user.contract.percentage, precision: 0)
end
actions
end
Have you looked into Active Admin's support for decorators? This page is quite comprehensive. The best way to implement them depends on how your decorator/presenter object is implemented.
Link summary: use decorate_with or look into using this gem for PORO support
Are you sure you want/need a presenter here? You can register the same Rails model multiple times as ActiveAdmin resources with different names and customizations (filters, index page, forms, etc). You can also use Rails STI or just subclass Rails models, perhaps with different Rails default_scope and then register the subclasses.

Rails 4 | Assign Role to User in Form using Bitmask | RoleModel

I got stuck on the following issue - I suspect there is a simple solution to it, but I just can't figure it out.
I am using Role Model Gem with Rails 4.
All works well, and the assigned roles are stored perfectly well in the roles_mask attribute (integer) of the user model as internal bitmask.
Now, I would like that Admins can assign as well as remove Roles from users via a FORM (view). I am not a Rails Ninja, so there might be a trick to do this.
According to the Doc, I can do the following:
# role assignment
>> u.roles = [:admin] # ['admin'] works as well
=> [:admin]
# adding roles (remove via delete or re-assign)
>> u.roles << :manager
=> [:admin, :manager]
So that is understood.
And my approach was to query for all valid roles in the form:
# get all valid roles that have been declared
>> User.valid_roles
=> [:admin, :manager, :author]
Then list them as checkbox. Once the form gets submitted I assign / remove roles.
The question:
Is that the right approach, does this even work, and if so how?
Since I had the problem not once I figured out a way - here is the solution:
More details here.
Many roles per user
I assume in this answer that you have roles in form of symbols (below I modified this solution in a way that it works with symbols) - so to work with Pundit in case you are using it.
# in models/user.rb
ROLES = [:admin, :manager, :general, :custom_role, :another_custome_role, :banned]
It is possible to assign multiple roles to a user and store it into a single integer column using a bitmask. First add a roles_mask integer column to your users table.
rails generate migration add_roles_mask_to_users roles_mask:integer
rake db:migrate
Next you'll need to add the following code to the User model for getting and setting the list of roles a user belongs to. This will perform the necessary bitwise operations to translate an array of roles into the integer field.
# in models/user.rb
def roles=(roles)
self.roles_mask = (roles & ROLES).map { |r| 2**ROLES.index(r) }.inject(0, :+)
end
def roles
ROLES.reject do |r|
((roles_mask.to_i || 0) & 2**ROLES.index(r)).zero?
end
end
Here a tiny modification to make it work with Rolify & Pundit
def roles=(roles)
self.roles_mask = (roles.map { |x| x.to_sym } & ROLES).map { |r| 2**ROLES.index(r) }.inject(0, :+)
end
If you're using devise without strong parameters, don't forget to add attr_accessible :roles to you user model.
If you're using devise with strong_parameters, either as a gem in a Rails 3 app, or as is built-in in Rails 4, dont forget to add the roles to the permitted list in the controller
class ApplicationController < ActionController::Base
before_filter :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) {|u| u.permit(:email, :password, :password_confirmation, roles: [])}
end
end
See the section on strong parameters in the Devise documentation.
You can use checkboxes in the view for setting these roles.
<% for role in User::ROLES %>
<%= check_box_tag "user[roles][#{role}]", role, #user.roles.include?(role), {:name => "user[roles][]"}%>
<%= label_tag "user_roles_#{role}", role.humanize %><br />
<% end %>
<%= hidden_field_tag "user[roles][]", "" %>
Finally, you can then add a convenient way to check the user's roles in the Ability class.
# in models/user.rb
def is?(role)
roles.include?(role.to_s)
end
# in models/ability.rb
can :manage, :all if user.is? :admin
See Custom Actions for a way to restrict which users can assign roles to other users.
This functionality has also been extracted into a little gem called role_model (code & howto).
If you do not like this bitmask solution, see Separate Role Model for an alternative way to handle this.

Can't mass-assign protected attributes for creating a has_many nested model with Devise

I've watched the RailsCast, another nested attributes video, lots of SO posts, and fought with this for a while, but I still can't figure it out. I hope it's something tiny.
I have two models, User (created by Devise), and Locker (aka, a product wishlist), and I'm trying to create a Locker for a User when they sign up. My login form has a field for the name of their new Locker (aptly called :name) that I'm trying to assign to the locker that gets created upon new user registration. All I'm ever greeted with is:
WARNING: Can't mass-assign protected attributes: locker
I've tried every combination of accepts_nested_attributes and attr_accesible in both of my models, yet still nothing works. I can see from the logs that it's being processed by the Devise#create method, and I know Devise isn't smart enough to create my models how I want :)
Here's the relevant bits of my two models:
# user.rb
class User < ActiveRecord::Base
attr_accessible :username, :email, :password, :password_confirmation, :remember_me, :locker_attributes
# Associations
has_many :lockers
has_many :lockups, :through => :lockers
# Model nesting access
accepts_nested_attributes_for :lockers
end
and
# locker.rb
class Locker < ActiveRecord::Base
belongs_to :user
has_many :lockups
has_many :products, :through => :lockups
attr_accessible :name, :description
end
# lockers_controller.rb (create)
#locker = current_user.lockers.build(params[:locker])
#locker.save
I'm assuming I need to override Devise's create method to somehow get this to work, but I'm quite new to rails and am getting used to the black box "magic" nature of it all.
If anyone can help me out, I'd be incredibly thankful. Already spent too much time on this as it is :)
EDIT: I realized I omitted something in my problem. My Locker model has three attributes - name, description (not mandatory), and user_id to link it back to the User. My signup form only requires the name, so I'm not looping through all the attributes in my nested form. Could that have something to do with my issue too?
EDIT 2: I also figured out how to override Devise's RegistrationsController#create method, I just don't know what to put there. Devise's whole resource thing doesn't make sense to me, and browsing their source code for the RegistrationsController didn't help me much either.
And for bonus points: When a user submits the login form with invalid data, the Locker field always comes back blank, while the regular Devise fields, username & email, are filled in. Could this also be fixed easily? If so, how?
first, you have a typo :
attr_accessible :locker_attributes
should be plural :
attr_accessible :lockers_attributes
then, the standard way to use nested_attributes is :
<%= form_for #user do |f| %>
<%# fields_for will iterate over all user.lockers and
build fields for each one of them using the block below,
with html name attributes like user[lockers_attributes][0][name].
it will also generate a hidden field user[lockers_attributes][0][id]
if the locker is already persisted, which allows nested_attributes
to know if the locker already exists of if it must create a new one
%>
<% f.fields_for :lockers do |locker_fields| %>
<%= locker_fields.label :name %>
<%= locker_fields.text_field :name %>
<% end %>
<% end %>
and in you controller :
def new
#user = User.new
#user.lockers.build
end
def create
# no need to use build here : params[:user] contains a
# :lockers_attributes key, which has an array of lockers attributes as value ;
# it gets assigned to the user using user.lockers_attributes=,
# a method created by nested_attributes
#user = User.new( params[:user] )
end
as a side note, you can avoid building a new locker for new users in controller in different ways:
create a factory method on User, or override new, or use an after_initialize callback to ensure every new user instantiated gets a locker builded automatically
pass a specific object to fields_for :
<% f.fields_for :lockers, f.object.lockers.new do |new_locker_fields| %>
Someone helped me figure out the solution in a more "Rails 4'y" way with strong attributes & how to override Devise's sign_up_params (to catch all the data coming from my signup form).
def sign_up_params
params.require(:user).permit(:username, :email, :password, :lockers_attributes)
end
Gemfile addition: gem 'strong_parameters'
Commenting out the attr_accessible statement in my user.rb file, since apparently strong parameters eliminate the need for attr_accessible declarations.
# attr_accessible :username, :email, :password, :password_confirmation, :lockers
And the/a correct way of building a Locker before submitting the form: at the beginning of the nested form:
<%= l.input :name, :required => true, label: "Locker name", :placeholder => "Name your first locker" %>
Thanks again for all your help. I know a question like this is difficult to answer without seeing the whole codebase.

Rails 3.1 attr_accessible verification receives an array of roles

I would like to use rails new dynamic attr_accessible feature. However each of my user has many roles (i am using declarative authorization). So i have the following in my model:
class Student < ActiveRecord::Base
attr_accessible :first_name, :as=> :admin
end
and i pass this in my controller:
#student.update_attributes(params[:student], :as => user_roles)
user_roles is an array of symbols:
user_roles = [:admin, :employee]
I would like my model to check if one of the symbols in the array matches with the declared attr_accessible. Therefore I avoid any duplication.
For example, given that user_roles =[:admin, :employee]. This works:
#student.update_attributes(params[:student], :as => user_roles.first)
but it is useless if I can only verify one role or symbol because all my users have many roles.
Any help would be greatly appreciated
***************UPDATE************************
You can download an example app here:
https://github.com/jalagrange/roles_test_app
There are 2 examples in this app: Students in which y cannot update any attributes, despite the fact that 'user_roles = [:admin, :student]'; And People in which I can change only the first name because i am using "user_roles.first" in the controller update action. Hope this helps. Im sure somebody else must have this issue.
You can monkey-patch ActiveModel's mass assignment module as follows:
# in config/initializers/mass_assignment_security.rb
module ActiveModel::MassAssignmentSecurity::ClassMethods
def accessible_attributes(roles = :default)
whitelist = ActiveModel::MassAssignmentSecurity::WhiteList.new
Array.wrap(roles).inject(whitelist) do |allowed_attrs, role|
allowed_attrs + accessible_attributes_configs[role].to_a
end
end
end
That way, you can pass an array as the :as option to update_attributes
Note that this probably breaks if accessible_attrs_configs contains a BlackList (from using attr_protected)

Rails: Using form fields that are unassociated with a model in validations

In a Ruby on Rails application I am trying to use information from fields that are not associated with the model in validation.
Here is part of the model as an example (the whole model has gotten kinda big):
class Scorecard < ActiveRecord::Base
belongs_to :course
belongs_to :user
validate :attributes_consistency
def attributes_consistency
# Executed for all scorecards. Checks if the user completed the hole attributes correctly
if ( params[:no_fairways] and any_fairways? and !only_nine? ) or ( params[:no_fairways] and !any_h1_to_h9_score_blank and any_h1_to_h9_fairway? and only_nine? ) or ( params[:no_fairways] and !any_h10_to_h18_score_blank and any_h10_to_h18_fairway? and only_nine? )
errors.add_to_base("You inidicated that you missed all the fairways, but you also marked one or more fairways in the scorecard. Either uncheck the fairways mistakenly marked or uncheck the 'No fairways' checkbox.")
end
if ( params[:no_girs] and any_girs? and !only_nine? ) or ( params[:no_girs] and !any_h1_to_h9_score_blank and any_h1_to_h9_gir? and only_nine? ) or ( params[:no_girs] and !any_h10_to_h18_score_blank and any_h10_to_h18_gir? and only_nine? )
errors.add_to_base("You inidicated that you missed all the greens, but you also marked one or more greens in the scorecard. Either uncheck the marked greens on the scorecard or uncheck the 'No GIRs' checkbox.")
end
end # attributes_consistency
def any_h1_to_h9_score_blank?
h1_score.blank? or h2_score.blank? or h3_score.blank? or h4_score.blank? or h5_score.blank? or h6_score.blank? or h7_score.blank? or h8_score.blank? or h9_score.blank?
end
def any_h10_to_h18_score_blank?
h10_score.blank? or h11_score.blank? or h12_score.blank? or h13_score.blank? or h14_score.blank? or h15_score.blank? or h16_score.blank? or h17_score.blank? or h18_score.blank?
end
def any_h1_to_h9_fairway?
h1_fairway? or h2_fairway? or h3_fairway? or h4_fairway? or h5_fairway? or h6_fairway? or h7_fairway? or h8_fairway? or h9_fairway?
end
def any_h10_to_h18_fairway?
h10_fairway? or h11_fairway? or h12_fairway? or h13_fairway? or h14_fairway? or h15_fairway? or h16_fairway? or h17_fairway? or h18_fairway?
end
def any_h1_to_h9_gir?
h1_gir? or h2_gir? or h3_gir? or h4_gir? or h5_gir? or h6_gir? or h7_gir? or h8_gir? or h9_gir?
end
def any_h10_to_h18_gir?
h10_gir? or h11_gir? or h12_gir? or h13_gir? or h14_gir? or h15_gir? or h16_gir? or h17_gir? or h18_gir?
end
So how can I access params from the model?
Don't let params sneak up to the model. There's no point of having a controller in that case. Instead, checkout this episode from Railscasts that talks about virtual attributes that do not go into the database but can still be used for validations.
You don't need a corresponding model attribute for the virtual attributes. Define attributes local to the class such as #no_fairways that hold the state.
class ScoreCard < ActiveRecord::Base
# define attributes and accessors for both fields
attr_accessor :no_fairways, :no_girs
..
end
Now inside you form, you could just write:
<% form_for #scorecard %>
<%= f.check_box :no_fairways %>
<% end %>
Found the solution, thanks for the lingo though, "virtual attribute" helped with the google searchin.
The cleanliest way to accomplish this is to create attributes that are not part of the database but still part of the model. In my case I put this into the model:
attr_accessor :no_fairways
attr_accessor :no_girs
That easy! Now #scorecard.no_fairways and #scorecard.no_girs act just like any other attribute but aren't part of the database.

Resources