I'm just learning ruby on rails and I have a table of user roles (Owner, Admin, and User). There are going to be places in the code where I need to check the user's role and show different options. Does anyone know how to do this without resorting to magic numbers or other ugly methods?
In ASP.Net web apps I've worked on I've seen this done through the use of enumerated types:
public enum UserRole { Owner = 1, Admin = 2, User = 3 }
// ...
if (user.Role == UserRole.Admin)
// Show special admin options
Each different role in the database is reflected as an enumerated type with a value set to the ID of that role in the database. That doesn't seem like a very good solution because it depends on knowledge of database that may change. Even if it is the proper way to handle something like this, I don't know how to use enumerated types in rails.
I would appreciate any insight into this matter.
Ruby itself does not have an enumerated type, but this site shows a method http://www.rubyfleebie.com/enumerations-and-ruby/
You could make something like this in your User model:
#constants
OWNER = 1
ADMIN = 2
USER = 3
def is_owner?
self.role == OWNER
end
def is_admin?
self.role == ADMIN
end
def is_user?
self.role == USER
end
Could the functionality added in Rails 4.1, be what you are looking for ?
http://coherence.io/blog/2013/12/17/whats-new-in-rails-4-1.html
Copy from blog post:
class Bug < ActiveRecord::Base
# Relevant schema change looks like this:
#
# create_table :bugs do |t|
# t.column :status, :integer, default: 0 # defaults to the first value (i.e. :unverified)
# end
enum status: [ :unverified, :confirmed, :assigned, :in_progress, :resolved, :rejected, :reopened ]
...
Bug.resolved # => a scope to find all resolved bugs
bug.resolved? # => check if bug has the status resolved
bug.resolved! # => update! the bug with status set to resolved
bug.status # => a string describing the bug's status
bug.status = :resolved # => set the bug's status to resolved
This seems like a really good case for using my classy_enum gem. It essentially allows you to define a fixed set of options, where each one is a class with behavior and properties specific to it. It helps cut down on all the conditional logic that tends to get scattered throughout the application.
For example, If you are doing things like this:
class User < ActiveRecord::Base
def options
if user.is_admin?
[...admin options...]
else
[...non admin options...]
end
end
end
Then calling as: user.options somewhere else...
classy_enum allows you to move that logic to a separate place and have the same functionality with no conditional logic:
class User < ActiveRecord::Base
classy_enum_attr :role
delegate :options, :to => :role
end
The README has a working example and describes the gem in detail.
I prefer to use the aptly named Authorization plugin for situations like this.
This will let you
permit "role"
to restrict access to roles, and
permit? "role"
to simply test for access. Both of these delegate to User#has_role?(role).
Don't feel like you have to use their ObjectRoles implementation. You can use the Hardwired roles and then implement your own User#has_role?(role) method to use your existing schema.
Just starting to learn Rails (from C#), and had this exact same question. It seems that Rails doesn't really have enums because the philosophy is different. I would use tons of enums to try to organize all the details in a C# project, but maybe since Rails handles so much for you they aren't that important. It's not really an answer, just an observation.
There's a enum plugin on rubyforge so you do:
t.column :severity, :enum, :limit => [:low, :medium, :high, :critical]
It's pretty ugly to use :limit attribute to pass parameters, but it's a more standardized way.
To install just do:
script/plugin install svn://rubyforge.org/var/svn/enum-column/plugins/enum-column
it currenctly works with Rails 2.2.2 or later.
Rubyforge link: www.rubyforge.org/projects/enum-column/
Related
I want create roles in my project. Each user can be: admin, registered or demo. Each role see different things.
How can I do that? What is the best gem to do roles?
This is a example in 'bad programming" of what I want:
def index
if current_user.role[:name] == 'admin'
#installations = Installation.all
elsif current_user.role[:name] == 'registered'
#installations = current_user.installations
elsif current_user.role[:name] == 'demo'
#installations = current_user.installations.first
else
end
end
Some gems that might be interesting for you :
rolify
role_model
If you decide to implement it yourself, then within some page you might want to change the content, for that you might want to do something like this :
Add a role to the user model using a migration :
class AddRoleToUsers < ActiveRecord::Migration
def change
add_column :users, :role, :string, default: :demo
end
end
Then in your app you can use it as follows:
def index
case current_user.role
when :admin
#installations = Installation.all
when :registered
#installations = current_user.installations
else
#installations = current_user.installations.first
end
end
You can also simply create a boolean admin for instance.
What you might want to do also is create some methods in your model so that you can call current_user.admin? or current_user.registered? . You can do that by doing (if you chose to use a string to store the role):
class User < ActiveRecord::Base
def admin?
self.role == "admin"
end
def registered?
self.role == "registered"
end
end
One advantage I see of having a role stored in a string is that if you have 5 roles for instance then you do not have 4 booleans (as when you store admin in a boolean) but only one string. On the long run you might want to store actually a role_id instead of a string and have a separate role model.
An excellent alternative pointed out by Jorge de Los Santos (another answer) is to use enum :
class User < ActiveRecord::Base
enum role: [:demo, :admin, :registered]
end
It is an excellent alternative because it will automagically add the methods described above such as current_user.admin? without hard coding them.
With your roles, you might want to do some authorization (admins can have access to specific pages, demo users are restricted to only a subset of pages, etc.). For this, you can use the gem called cancancan. You can look at this railscast to learn more about it. Also, you can have some infos here : How to use cancancan? .
There are plenty of solutions available to you.
Starting by gems:
https://github.com/RolifyCommunity/rolify
https://github.com/martinrehfeld/role_model
By using Devise architecture (in case you use it):
https://github.com/plataformatec/devise/wiki/How-To:-Add-a-default-role-to-a-User
By using enums in rails 4:
class AddRolesToUser < ActiveRecord::Migration
#add_column 'role', :integer, default: 0 to the users table
end
class User < ActiveRecord::Base
enum role: [:demo, :admin, :registered]
end
That will enable role methods.
user = User.find(1)
user.role #:demo
user.admin? #false
user.registered? #false
And consequently:
if user.admin?
#somethig
elsif user.registered?
#another something
else
#another another something.
And last but not least, what you are searching is not the manage roles solution, is the manage permissions solutions:
https://github.com/ryanb/cancan
Add a boolean, :admin to your User model.
class AddAdminToUsers < ActiveRecord::Migration
def change
add_column :users, :admin, :boolean, deafult: false
end
end
Create a method for a registered user to separate them from demo users, such as verifying their email, providing a home address and phone number, filling out a profile, etc. This is up to you though, first you need to decide how a registered and demo user should be different.
The CanCan gem adds authorization to your project, and is especially useful if you want to implement multiple roles with differing abilities. When used with an authentication system like devise, you get a full suite of capability for your site.
You're in full control of what roles you want to define and what abilities they have. CanCan manages tracking, assignment, and querying of roles, and then gets out of your way to let you build what you need.
You can find the CanCan gem in Github: https://github.com/ryanb/cancan
It's simple to use, and the documentation is straightforward and easy to follow.
I know this is a really simple question but I guess my brain and google-fu isn't working so well today.
Let's say I have an Event, with Registrants, and they can pay for the event using one or more payments.
I'm trying to create a payment linked to a registrant (who is linked to an event).
So my payment should have both registrant_id and event_id.
My URL looks something like this: (nested routes)
http://mysite.com/events/1/registrants/1/payments/new
My controller looks something like:
def create
#event = Event.find(params[:event_id])
#registrant = Registrant.find(:first, conditions: {id: params[:registrant_id], event_id: params[:event_id]} )
#payment = Payment.new params[:payment]
end
I know there is a much better way to do it, but I'm having trouble with the wording to properly google it :)
What syntax should I be using to make the .new automatically aware of the event_id and registrant_id?
Based on the discussion in the comments, there are several ways that the question can be addressed: the direct way and the Rails way.
The direct approach to creating objects that are related is to create the object using new_object = ClassName.new as suggested in the question. Then take the id of the created object and set that on an existing object (directly with existing_object.id = new_object.id or through some other method if additional logic is required). Or set the id on a new object by defining a custom initializer, such as:
class Payment
def initializer id_of_registrant
#registrant_id = id_of_registrant
end
...
end
The advantage of this approach is that it allows you to assign registrant IDs that may come from a range of objects with different classes, without having to deal with unnecessary or perhaps incorrect (for your solution) inheritance and polymorphism.
The Rails way, if you always have a direct relationship (1 to 1) between a Registrant and a 'mandatory' Payment is to use a has_many or belongs_to association, as described in the Rails guide: http://guides.rubyonrails.org/association_basics.html
For the example classes from the question:
class Registrant < ActiveRecord::Base
has_one :payment
end
class Payment < ActiveRecord::Base
belongs_to :registrant
end
You will want to use the appropriate migration to create the database tables and foreign keys that go with this. For example:
class CreateRegistrants < ActiveRecord::Migration
def change
create_table :registrants do |t|
t.string :name
t.timestamps
end
create_table :payments do |t|
t.integer :registrant_id
t.string :account_number
t.timestamps
end
end
end
Of course, if you registrants only optionally make a payment, or make multiple payments, then you will need to look at using the has_many association.
With the has and belongs associations, you can then do nice things like:
#payment.registrant = #registrant
if you have instantiated the objects by hand, or
#payment.new(payment_amount)
#registrant = #payment.build_registrant(:registrant_number => 123,
:registrant_name => "John Doe")
if you would like the associations populated automatically.
The Rails Guide has plenty of examples, though in my experience only trying the most appropriate one for your actual use case will show if there are restrictions that could not be anticipated. The Rails approach will make future queries and object building much easier, but if you have a very loose relationship model for your objects you may find it becomes restrictive or unnatural and the equivalent associations are better coded by hand with your additional business rules.
It's not great practice to set id attributes directly, as the id might not refer to an actual database row. The normal thing to do here would be to use CanCan (https://github.com/ryanb/cancan), which seems like it would solve all your problems.
EDIT:
If you're not using authentication of any kind then I'd either put the load methods in before_filters to keep things clean:
before_filter :load_event
def load_event
#event = Event.find params[:event_id]
end
or define some funky generic loader (unnecessarily meta and complex and not recommended):
_load_resource :event
def self._load_resource resource_type
before_filter do
resource = resource_type.constantize.find params[:"#{ resource_type }_id]
instance_variable_set :"##{ resource_type }", resource
end
end
I am trying to implement specific object (row) authorisation using cancan, I want it to work in a way that a user can only make a change(update/edit) to a Record if he/she has the role for that specific Record. after consulting the cancan docs I tried doing the following:
class Ability
include CanCan::Ability
def initialize(user)
can :manage, Record do |record|
user.can_edit(record)
end
end
end
class User
has_many :assignments
has_many :roles_as_editor, :through => :assignments, :class_name => "Role", :source => :role, :conditions => {:edit => true}
def rec_as_editor
self.roles_as_editor.collect{ |x| Record.where(:cp_secondary_id => x.record_id) }.flatten.uniq
end
def can_edit(rec)
rec_as_editor.include?(rec)
end
end
The can_edit method takes in a Record object and ensures that a User has the role necessary to make a change to it by returning true or false. this method is tested and works correctly so the problem seems to be with the CanCan code because when i try editing a Record that the user dosent hold the role for it still allows me to make changes to the Record, anyone know why this wont work?
If you require any further information please let me know through a comment.
Thank You
Are you authorizing the resource in the controller?
you should have load_and_authorize_resource in your controller
or
def edit
#critical_process = CriticalProcess.find(params[:id])
#this here is what you use
authorize! :edit, #critical_process
end
in your edit method inside the critical process controller.
I personally prefer to keep this logic completely separate from models so that I don't have to dig into model code to find authorization issues. In other words, user.can_edit checks for authorization which is what the ability file is supposed to be in charge of. Shouldn't matter though... in this case I think you might have a problem inside the can_edit method. I have used code that looks nearly identical to yours without problems many times like this:
can :manage, Record do |record|
user.has_role?(:record_manager)
end
I suggest including your code for can_edit or use the debugger to see what value gets returned from can_edit.
I think the problem comes from the way you query for the records that are supposed to have the user as an editor.
I copy/pasted your code and built the other associations from scratch.
And testing it in the console it works as expected when I use it:
>> u = User.last
>> a = Ability.new(u)
>> a.can :edit, Role.last
false
The only thing I changed is the query for the records: it seemed to look for a record that owns the role (your Role has a record_id) but then looks for the same key under cp_secondary_id.
I think something is wrong in your query, but what depends on your schema and associations:
roles_as_editor.collect{ |x| Record.where(:cp_secondary_id => x.record_id) }.flatten.uniq
as I understood your code we are traversing associations like this:
User=>Assignment<=Role(boolean edit flag)<=Record
One of mine rails projects has many models with the same flag: approved.
I don't like to manage the flag 'approved' for so many models, and I am seeking a DRY solution.
I have found some plugin like flag_shih_tzu or can_flag, but I think they work only with a model.
Do you know some plugin to flag many models at once ?
I think that I a good solution (without plugin) should use the polymorphic associations, do you agree ?
many thanks,
Alessandro
If all you're looking for is a way to store all the functions in a single place, but have them accessible from all your flagable models, I'd recommend writing a mixin for them. For example, in lib/approved.rb, you could have the module:
module Approved
# Any approval functions/constants that don't belong in a model go here...
module Mixin
def self.included(klass)
klass.class_eval do
# Class-levell model macros can be run here
named_scope :approved, {:conditions => {:approved => true}}
named_scope :unapproved, {:conditions => {:approved => false}}
end
end
def approved?
return (self.approved == true)
end
# Other shared model functions go here...
end
end
And then it's just a matter of including the mixin in all the models that need those functions:
class Approvable < ActiveRecord::Base
include Approved::Mixin
# etc.
end
Hope that helps!
I have a similar problem in my application, we have 10 or so models that all require approval and didn't want to copy the code everywhere. In our case we are using transitions as our workflow gem, so instead of having a flag approved we have a string column state.
A model that requires approval looks like this:
class Comment < A:RB
include ApprovalWorkflow
end
Then we have a workflow that looks like this:
# /app/workflows/approval_workflow.rb
module ApprovalWorkflow
def self.included(klass)
klass.class_eval do
state_machine do
.. workflow junk goes here ..
end
end
end
end
So what's going on here is that we've created a module, which you can think of like an anonymous piece of code which does't belong anywhere (read more about modules to understand why this is an awful description), which we then include in our classes which mixes in the functionality. Now our comment class has the approval workflow!
In you case, assuming you were to keep the approval flag, you might add default validations, some methods like approve!(user) or scopes for querying.
I hope this helps.
Using polymorphic associations is not the solution unless they are all the same base object. Keep in mind that with inheritance the parent should have an is-a relationship with the child.
What you could do is create an Approval model and have a one-to-one relationship with the approvable models.
I'm wondering what the best way is to store user settings? For a web 2.0 app I want users to be able to select certain settings. At the moment is it only when to receive email notifications.
The easiest way would be to just create a Model "Settings" and have a column for every setting and then have a 1-1 relationship with users.
But is there a pattern to solve this better? Is it maybe better to store the info in the user table itself? Or should I use a table with "settings_name" and "settings_value" to be completely open about the type of settings stored there (without having to run any migrations when adding options)?
What is your opinion?
Thanks
If you use PostgreSQL, the best solution is to use https://github.com/diogob/activerecord-postgres-hstore/. It's a simple, fast and reliable way to store hashes in the database. Since it's not just a serialised text field, you can search on it also, and you don't need to create a new table, as in HasEasy.
def User
serialize :preferences, ActiveRecord::Coders::Hstore
end
user = User.create preferences: { theme: "navy" }
user.preferences['theme']
The "open" table approach makes it difficult to model with AR, since you have to worry about the types of the data (boolean, int, string, etc). I've always added prefs as columns on the users table, and then move them out to a user_preferences table if there are "too many" of them. It's simple, and it's easy to work with.
If the user settings are not meant to be findable (via a User.find_by_x_preference, e.g.) you could also store them in a serialized column as a hash. This is the use case described in the rails docs (http://www.railsbrain.com/api/rails-2.3.2/doc/index.html?a=M002334&name=serialize#), actually.
class User < ActiveRecord::Base
serialize :preferences
end
u = User.new
u.preferences = {:favorite_color => "green", :favorite_book => "Moby Dick"}
We use the helpful plugin called HasEasy. It stores the data in a vertical table, but allows you to add validations, pre/post storage processing, types, etc.
Rails 6 supports ActiveRecordStore that can be used to solve this problem. The gem that improves ActiveRecordStore, by adding type definition, is activerecord-typedstore.
Define attributes in your model:
class User < ApplicationRecord
typed_store :settings do |s|
s.boolean :public, default: false, null: false
s.string :email
s.datetime :publish_at
s.integer :age, null: false
end
end
And use can use it:
shop = Shop.new(email: 'george#cyclim.se')
shop.public? # => false
shop.email # => 'george#cyclim.se'
shop.published_at # => nil