I'm using cancan as my authorization engine.
I already have the roles in user:
ROLES = %w[admin normal author corp]
I also have methods to add and check roles:
#cancan
def roles=(roles)
self.roles_mask = (roles & ROLES).map { |r| 2**ROLES.index(r) }.sum
end
def roles
ROLES.reject do |r|
((roles_mask || 0) & 2**ROLES.index(r)).zero?
end
end
def is?(role)
roles.include?(role.to_s)
end
And I have # roles_mask :integer in the User model.
However, I want to have a after_save :add_normal_role which assigns the normal role to the user.
Basically, I'm not being able (don't know) how to assign roles to each user.
This is what I have, which doesn't work:
private
def add_normal_role
self.roles=(ROLES[1])
end
Thanks
You should try using a before_create callback ensuring the user has a normal role.
The problem with your current callback is since it's an after_save, your modifications aren't saved by default. (Saving in an after_save callback is a bad idea leading to infinite loops...) You could also use a before_save callback (with the same code you already have), which will also work.
However, since you really only need to add a normal role when the object is created (and not on every update), a before_create is better suited.
Related
I would like to control the number of invitations per user
I know that I can make some configurations to devise_invitable in my config/initializers/devise.rb file, and limit how many users I want to invite for example:
# initializer/devise.rb
# Number of invitations users can send.
...
config.invitation_limit = 5
But I want is that depending on the type of user I can invite more or less people.
If I have a role type administrator then it would be something like config.invitation_limit = 10
If I have a common user role then I can invite only 3 people
and so respectively
The documentation states:
You can change invitation_limit column for some users so they can send more or less invitations...
You could set the invitation_limit on create.
class User < ApplicationModel
before_create :set_invitation_limit
def initial_invitation_limit
if is_admin?
10
else
3
end
end
def set_invitation_limit
self.invitation_limit = initial_invitation_limit
end
end
If you allow changes to the role after creation you may use before_save.
Or you could override the invitation_limit method:
class User < ApplicationModel
def initial_invitation_limit
if is_admin?
10
else
3
end
end
def invitation_limit
self[:invitation_limit] || initial_invitation_limit
end
end
In my Rails 6 app I've got User model which has first_name and last_name fields. Both of these fields are required to create a new record. Now I want to trigger the IdentityCheck.new(self).call service each time when user updates his first_name or last_name.
Naturally I've tried something like below:
class User < ApplicationRecord
after_update :restart_identity_check, if: :first_name_changed? || :last_name_changed?
private
def restart_identity_check
IdentityCheck.new(self).call
end
end
But I don't know why it won't worked. Is there a way to do this?
That's not the correct syntax for if.
after_update :restart_identity_check, if: Proc.new { |user| user.first_name_changed? || user.last_name_changed? }
You may wish to create an alternate method, like this:
after_update :restart_identity_check, if: :must_restart_identity_check?
# I'd make this private
private
def must_restart_identity_check?
first_name_changed? || last_name_changed?
end
Remember that after_update runs before record is commited to the database. So, if something happens in between, you are going to restart_identity_check even if your User changes is not commited to the database. I'd use after_update_commit just in case.
I have the following two classes:
class Account < ActiveRecord::Base
# columns
# ...
# features :jsonb
has_one :plan
end
class Plan < ActiveRecord::Base
# columns
# ...
# features :jsonb
end
And will be calling features like this:
account.features # being account is an instance of Account
# or
account.features[:feature_key]
The thing is I want account to look for features or features[:feature_key] within itself, and if that is nil or empty it should pick the value from the associated Plan object.
Something like:
features.present? ? features : plan.features
# and
features[:feature_key].present ? features[:feature_key] : plan.features[:feature_key]
But in a proper method within Account class
Well, it's not best practice, but you can override the getter with a custom method. In your case:
def features
return features[:feature_key] if features[:feature_key].present?
return plan&.features[:feature_key] if plan&.features[:feature_key].present?
end
Not sure that's the logic you need, but that'll work for an override if you put it in the Account model. The preferred way to do it would be to name the method something else and call that method so you can still access the unmodified attribute if need be.
Not sure I completely understand but given your comment under the other answer I am assuming you are looking for something like:
class Account < ActiveRecord::Base
def feature_info(key)
return plan.features[key] unless features.present? && features[key].present?
features[key]
end
end
Then called as
account = Account.first
account.feature_info(:feature_key)
This might be cleaner though
class Account < ActiveRecord::Base
def features
read_attribute(:features) || {}
end
def feature_info(key)
return plan.features[key] unless features[key].present?
features[key]
end
end
I have a User model, my application on Rails 5 and I'm trying to make a custom callback that allows you to assign the role of admin to the first registered user, but unfortunately, when saving data to the database, the role is not assigned... Tell me please, what's wrong?
Database
t.boolean "admin", default: false
Model
class User < ApplicationRecord
before_save: update_admin
private
def update_admin
User.count(1).update_attributes (admin: 'true')
end
end
You want a before_create callback that will set admin to true (but not save! it would trigger the before_save callback again and so on => Stack level too deep).
You can do the following:
before_create :set_admin
private
def set_admin
self.admin = true unless User.exists? # faster than a count
end
Or the alternative version, with an after_save callback:
after_create :update_admin
private
def update_admin
self.update_attributes!(admin: true) if User.count == 1
end
The downside of both solutions is that these callbacks will trigger a SQL count each time you create a User.
try using
user = User.find(1)
user.admin = true
user.save
I wouldn't recommend using Callback for updating 1st registered user as admin. You can use seeds as it needs to be run only one time.
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.