Rails - CanCan - Set & Assign Roles? - cancan

How do I setup the roles for CanCan, and how do I assign those roles to users? I'd like for now at least to have a dropdown menu upon registration for a user to select their role. I'm not quite sure with all of the documentation I keep seeming to miss this, but any help is greatly appreciated!

Here's a solid Rails-appropriate method for managing roles. I use it. I like it. Essentially, you create a has-and-belongs-to-many table to associate users and roles and define a single function, User#has_role? to check for the association.
# file: app/models/user.rb
class User < ActiveRecord::Base
has_many :user_roles, :dependent => :destroy
has_many :roles, :through => :user_roles
def has_role?(name)
roles.pluck(:name).member?(name.to_s)
end
end
# file: app/models/role.rb
class Role < ActiveRecord::Base
has_many :user_roles, :dependent => :destroy
has_many :users, :through => :user_roles
end
# file: app/models/user_role.rb
class UserRole < ActiveRecord::Base
belongs_to :user
belongs_to :role
end
The migration / schema files:
# file: db/migrate/xxx_create_users.rb
class CreateUsers < ActiveRecord::Migration
def change
create_table(:users) do |t|
t.string :email, :null => false, :default => ""
# ... any other fields you want ...
t.timestamps
end
end
end
# file: db/migrate/xxx_create_roles.rb
class CreateRoles < ActiveRecord::Migration
def change
create_table :roles do |t|
t.string :name
t.timestamps
end
end
end
# file: db/migrate/xxx_create_user_roles.rb
class CreateUserRoles < ActiveRecord::Migration
def change
create_table :user_roles do |t|
t.references :user
t.references :role
t.timestamps
end
add_index :user_roles, [:user_id, :role_id], :unique => true
end
end
So now, your Ability file will look something like this:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # support guest user
if user.has_role?(:admin)
can :manage, :all
elsif user.has_role?(:nsa)
can :read, :all
elsif user.has_role?(:editor)
# ... add other abilities here
end
end
end
To be clear, this doesn't answer the OP's question about how you create roles in the first place. But since users, roles, and user_roles are now full Rails models, you can use the standard CRUD tools for managing them. But 90% of the time, you'll simply be assigning admin privileges to one or two users, which is easily done in a seed file like this:
# file: db/seeds.rb
# create a role named "admin"
admin_role = Role.create!(:name => "admin")
# create an admin user
admin_user = User.create!(:email => "admin#admin.com")
# assign the admin role to the admin user. (This bit of rails
# magic creates a user_role record in the database.)
admin_user.roles << admin_role

I figured this out for the most part.
For setting up roles, they are actually set when you define them in the ability class.
For instance:
if user.has_role? :admin
can :manage, :all
else
can :read, :all
end
This is basically "setting up" the admin role. For some reason I thought you would have to initialize that role elsewhere as well.
To assign the roles, following most tutorials I've seen, you need to go into the rails console and use something similar to:
user = User.find(1)
user.add_role :admin # sets a global role
user.has_role? :admin
=> true
The first line finds the user, and the second adds the role. The third is used to check if the user is assigned to that role. There are also ways to add this upon registration, and a few others as well. I'll try to list them here as I run across them, but hopefully this clears up any confusion for someone in the future. :)

I handled this by putting a roles field in the user table, with the roles comma separated in the string field. I let an administrator manage the user roles, with programmatic check for valid against a configuration file (using functions such as such as valid_role?, has_role?. I then wrote some functions in the user model that CanCan could call such as user_can_see_...
Not sure how that would work for a self administered role system, but if you are interested, my code can be found at: tws_auth on github

Related

belong_to association and ActiveRecord::InvalidForeignKey in Rails 5

My application has two models: Service and User. A service may have and assigned driver or not. I have implemented this as:
class User < ApplicationRecord
end
and:
class Service < ApplicationRecord
has_and_belongs_to_many :users
belongs_to :driver, class_name: "User", optional: true
end
Note that since a particular service may or may not have a driver, I have marked the association as optional. And I don't have any pointer from the User model to Service.
I have the following migration implementing this association:
class AddOPtionalDriverToService < ActiveRecord::Migration[5.0]
def change
add_reference :services, :driver, references: :users, index: true
add_foreign_key :services, :users, column: :driver_id
end
end
The relevant part of my schema when I run the migration is:
create_table "services", force: :cascade do |t|
t.string "destination"
....
t.text "comments"
t.index ["driver_id"], name: "index_tdy_requests_on_driver_id", using: :btree
end
...
add_foreign_key "tdy_requests", "users", column: "driver_id"
end
My problem is that when I try to create a new service without a driver my params contains a value of "0" for the driver:
Parameters: {"utf8"=>"✓",
"authenticity_token"=>"kVC53huZYZxuF4akSiqkGkSvoo5p2f4dQ==",
"service"=>{
"destination"=>"Some where", ... ,
"driver_id"=>"0",
"comments"=>""},
"commit"=>"Create Service"}
But since driver_id is "0" I get the following exception:
PG::ForeignKeyViolation: ERROR: insert or update on
table "services" violates foreign key constraint
"fk_rails_15497e1c36" DETAIL: Key (driver_id)=(0) is
not present in table "users"
This makes lots of sense but the funny thing is that I have discovered this when I move from SQLite to PostgreSQL because it was working fine with SQLite. At least the application was doing what I wanted to do. I'm using Rail 5.0.2.
I would like to know how can modify my models or migrations to avoid this exception. Any ideas?
Thank you very much in advance.
I'm sorry. I have just realized the solution. Something that I didn't say was that I enter the driver using a select element. Since the driver is optional I was adding and "empty" using:
<option value="0"></option>
changing to:
<option value=""></option>
solves the problem.
I'd prefer to remove the question since doesn't offer any value.

Ruby on Rails - PostgreSQL Grouping error when doing joins & group

In my application I have models Visit & Post.
class Post < ActiveRecord::Base
has_many :visits
class Visit < ActiveRecord::Base
belongs_to :post, :counter_cache => true
Im trying to get all visits a post has in visits table. I did:
- a = Visit.joins(:post).group(:post_id).select(:post_id, :title, 'count(visits.id) as total_views').where(user: current_user)
- a.each do |a|
%tr
%td= a.title
%td= a.total_views
This works find in my development env/localhost (I think since I use sqlite3), butI am using PostgreSql in my production and I got this error:
PG::GroupingError: ERROR: column "posts.title" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: ...ECT count(visits.id) as total_views, "visits"."post_id", "title", c...
What am I doing wrong and how to fix it?
As the answer says "posts.title" must appear in the GROUP BY. In group add :title along with :post_id
Visit.joins(:post).group([:post_id, :title]).select('sum(cpc_bid) as earnings', :post_id, :title, 'count(visits.id) as total_views').where(influencer: current_user)

Rails 4 pg_search - multisearchable Issue

I'm using the pg_search gem for Rails (Ruby 2.1.1, Rails 4.1.4) and trying to implement a global multisearch for some of my models but I keep getting
PG::UndefinedFunction: ERROR: operator does not exist: text % unknown
when I run the multisearch command.
Here are my models:
# name :text
class Animal < ActiveRecord::Base
has_one :expression
multisearchable against: :name
end
# type :text
class Expression < ActiveRecord::Base
belongs_to :animal
multisearchable against: :type
end
My search query is then something like Happy Tiger
When I run PgSearch.multisearch("Happy Tiger") though, I get that above error.
I also have an initializer that tells PgSearch to use trigram
Any ideas???
I realized I needed to install trigram in postgres :P

Mongoid: If possible, how to assign a parent to pre-existing child?

In a model definition can you have more than one belongs_to statement? If the answer is no, read no further. I am trying to create three 1-n referenced relationships with mongoid in a sinatra app.
models
class SkillTrack
include Mongoid::Document
belongs_to :student
belongs_to :grading_period
belongs_to :teacher
end
class Student
include Mongoid::Document
field :name
field :nickname
field :dob, type: Date
has_many :skill_tracks
end
class GradingPeriod
include Mongoid::Document
field :school_year
field :period_name
field :signing_date, type: Date
has_many :skill_tracks
end
class Teacher
include Mongoid::Document
field :name
has_many :skill_tracks
end
routes
post "/skill_track/new" do
form = params[:formdata] # using sinatra form helpers gem
student = Student.find("#{formdata["student_mongo_id"]}")
working = (student.skill_tracks.create).id
??? what do I do with working to make it a child of a teacher and of a grading_period?
end
what I have tried
The thing that looked the most promising to me from the mongoid docs was:
band.member_ids = [ id ] #Set the related document ids.
I mucked about in irb and tried lots of variations in my models but I could not set a parent teacher or grading period for the newly created skilltracking object. The student foreign key was set properly on creation.
I am hoping I have a simple syntax ignorance, but I tried so many variations I wonder if I can do this at all.
UPDATE:
I just needed to add working.save to David Troyer answer and boom working.
post "/skill_track/new" do
form = params[:formdata]
student = Student.find("#{formdata["student_mongo_id"]}")
working = student.skill_tracks.create
working.teacher = Teacher.create # or find
working.grading_period = GradingPeriod.create # or find
working.save
end
I believe so. If I understand your question correctly, try using some setters on the child SkillTrack document.
post "/skill_track/new" do
form = params[:formdata]
student = Student.find("#{formdata["student_mongo_id"]}")
working = student.skill_tracks.create
working.teacher = Teacher.create # or find
working.grading_period = GradingPeriod.create # or find
end
Dig a little bit further into the Operations section of the mongoid docs you referenced

In CREATE, init with params does fill all attributes

I am using RoR 3.1 + Postgres on MacOSX
In my create function I have this:
def create
#power_plant_substrate = PowerPlantSubstrate.new(params[:power_plant_substrate]) <= 1
#trade = params[:power_plant_substrate][:trade]
respond_to do |format|
if #power_plant_substrate.save
...
end
(1) should instantiate a new object with params[:power_plant_substrate]
THE PROBLEM:
Right after the creation of my new object #power_plant_substrate some of the attribute are available.
if I check params[:power_plant_substrate] value (trace to console) I can verify that all fields were passed correctly:
{"power_plant_id"=>"161", "substrate_id"=>"213", "quantity"=>"1", "periodicity"=>"yearly", "trade"=>"wanted", "price_per_unit"=>"0.00", "total_price"=>"0.00", "currency"=>"USD", "address"=>", Reserved", "transport"=>"pickup_only", "description"=>"afewrqe", "latitude"=>"", "longitude"=>""}
However I checked my object right after saving (#power_plant_substrate.save). "trade" attribute is not assigned anymore.
I tried accessing the same attributes in the model in a method that I call after_create, and same problem.
However, the record is available with all fields correctly assigned in the database.
WHY #power_plant_substrate object doesn't appear "fully" assigned after saving?
Hope you can help.
My guess is that there is a list of attr_accessible in PowerPlantSubstrate model. check if currency and trade are added in that list. If not, then add those.
attr_accessible :currency, :trade, . . .
Making a few attributes alone as attr_accessible will make it impossible to mass assign other variables. That is why individual assignment worked
If there is not list, try adding
attr_accessible nil
and see if that works