Rails Devise Role Model & CanCanCan - defining abilities - ruby-on-rails

I'm trying to define abilities in CanCanCan.
I can't figure out the syntax to get started.
I use Role Model for my roles and the roles are defined in my Profile.rb. Profile.rb belongs to User.rb.
I am trying to check if the user has the role :student.
When I try:
if {user_signed_in?, user.profile.has_role? :student}
I get a syntax error that says:
syntax error, unexpected ',', expecting =>
if {user_signed_in?, user.profile.has_role? :student}
When I try:
if {user_signed_in? && user.profile.has_role? :student}
I get a syntax error that says:
syntax error, unexpected tSYMBEG, expecting =>
if {user_signed_in? && user.profile.has_role? :student}
I have also tried replacing the curly braces with regular brackets and removing them altogether.
When I try removing the devise part (user_signed_in) and using the suggestion in the comments below, I try:
if user.profile.has_role?(:student)
And I get this error:
undefined method `has_role?' for nil:NilClass
When I try:
if user.profile.has_role? :student
I get this error:
undefined method `has_role?' for nil:NilClass
When I try:
if user.profile.has_role?(student)
I get this error:
undefined local variable or method `student' for #<Ability:0x007fd034a78968>
I have the following roles defined in my profile.rb:
roles :admin, :manager, #
:student, :educator, :researcher, :ktp, :faculty_manager, :ip_asset_manager, # for universities
:sponsor, # for industry
:project_manager, :representative, # for both uni and industry
:grantor, :investor, :adviser, :innovation_consultant, :panel_member, # external
:participant, :guest # public
When I try:
can :crud, Profile, :user_id => user.id if user.profile.has_role? :student
I don't get any errors, but my problem with this approach is that a student can do many things (there are 10 lines of permissions, so I would need to add the if statement individually to each of the 10 'can' statements, unless there is a way that the if statement can be applied to all lines, before the next 'elsif' statement.
The first part of my ability.rb is pasted below (there are a lot of roles and a lot of abilities, so I haven't pasted the whole thing).
class Ability
include CanCan::Ability
def initialize(user)
alias_action :create, :read, :update, :destroy, :to => :crud
# Define abilities for the passed in user here. For example:
#
user ||= User.new # guest user (not logged in)
#users who are not signed in can create registration or login
# can read publicly available projects, programs and proposals
can :read, Project, {:active => true, :closed => false, :sweep => { :disclosure => { :allusers => true } } }
# {:active => true, :closed => false && :Project.sweep.disclosure.allusers => true}
# if user role is student
can :crud, Profile, :user_id => user.id if user.profile.has_role? :student #[for themselves]
can :read, ProjectInvitation, {:student_id => #current_user && :expiry_date_for_students_to_claim_project > Time.now}
# can read project invitations addressed to them
# can read projects to which they are invited whilst the invite is available to accept;
can :read, Project, {} #if invited to the project?
# and they can create responses to invitations to those projects
can :update, ProjectInvitation.student_accepted
# they can create questions on those projects before the invite expiry date;
can :read, ProjectQuestion, {} #if intvited
can [:create, :update, :destroy], ProjectQuestion #if they created the question
can :read, ProjectAnswer #if its on a project they can see
# they can update term sheets and template agreements for those projects
can [:read, :update], TermSheet #{where created for project in which they are participating}
can [:read, :update], ProjectAgreement #{where created for a project in which they are participating}
can [:read, :update], FinanceAgreement #{where created for a project in which they are participating}
can [:read, :update], Nda #{where created for a project in which they are participating}
can [:create, :update], Feedback #{where created for a project in which they are participating and the feedback is on other project team members and the project is completed}
elsif user.profile.has_role? :educator
When I try (the suggestion below):
if user.try(:profile).present? && user.profile.has_role? :student
I get this error:
syntax error, unexpected tSYMBEG, expecting keyword_then or ';' or '\n'
...nt? && user.profile.has_role? :student
Please can someone see what I'm doing wrong?

For following codes
if {user_signed_in?, user.profile.has_role? :student}
if {user_signed_in? && user.profile.has_role? :student}
if {user_signed_in? && user.profile.has_role? :student}
You can not user {} for an ruby statement, it expect a key value pair. You can rewrite your code as following
if user_signed_in? && user.profile.has_role? :student
But you are getting an null pointer errors, so you have fix it in you codes as following, first check if user.try(:profile).present? then you can call user.profile.has_role? :student because your profile getting nil.
if user.try(:profile).present? && user.profile.has_role?(:student)

Related

Allow member routes to have authorization equivalent to read action cancancan

module Abilities
class ProjectsAbility < Ability
def abilities
can :manage, Project, user_id: user.id
if user.is?(:super_admin)
can :read, Project
end
if user.is?(:district_admin)
can [:read, :manage_details, :download], Project, user: { district: { id: user_district_ids } }
end
if user.is?(:school_admin)
can [:read, :manage_details, :download], Project, user: { school_id: user_school_ids || user.district.school_ids }
end
if user.is?(:advisor)
can [:read, :manage_details, :download], Project, user: { advisors: { id: user.id } }
end
# alias_action :manage_details, :download, to: :read
end
end
end
As district_admin, school_admin and advisor has same type of abilities. It can be agreed upon that all the users that have read access, have access to manage_details and download functionality.
So I was thinking that maybe I could achieve something like
alias_action :manage_details, :download, to: :read
But upon doing this I receive
CanCan::Error
You can't specify target (read) as alias because it is real action name
The reason I decided to go with the above approach because I don't want to place
authorize! :read, #project in the controller action
However if I do
alias_action :read, :manage_details, :download, to: :view_manage_details
and give users access to view_manage_details it works fine. Is there something that can be done to achieve alias_action :manage_details, :download, to: :read or is it something that requires extra mile work?

Cancan: Multiple abilities with AND operator

I have the following statement in my user_ability.cs class
can :read, Bike, :product => {:created_by => user.id, :company_id => user.company_id }
But this pops up an error: undefined "table_name" from Nil:class due to following statement that is under "/cancan/lib/cancan/model_adapters/active_record_adapter.rb"
name = model_class.reflect_on_association(name).table_name.to_sym
Now if we change the ability code as follows, application runs but OR is applied to the executed query.
can :read, Bike, :product => {:created_by => user.id}
can :read, Bike, :product => {:company_id => user.company_id }
And we want AND condition to work between created_by and company_id
Ruby version: 2.5.5p157
Rails version: 4.2.11.1
Cancan gem
version: 1.6.10
You can pass a scope as the third argument to can:
can :read, Bike, Bike.joins(:product).where(products: { created_by: user, company_id: user.company_id })
See defining abilities.

User to User Messages in Rails

I've been building messaging in a rails app for users to be able to send each other messages. I've looked at a few gems such as mailboxer but ultimately decided to build my own.
I'm hoping someone can help me put these pieces together. I've been following a similar question's answer here.
I'm testing in the rails console and I keep getting the following error:
undefined method `send_message' for #
How can I fix this?
Controller
class MessagesController < ApplicationController
# create a comment and bind it to an article and a user
def create
#user = User.find(params[:id])
#sender = current_user
#message = Message.send_message(#sender, #user)
flash[:success] = "Message Sent."
flash[:failure] = "There was an error saving your comment (empty comment or comment way to long)"
end
end
Routes
resources :users, :except => [ :create, :new ] do
resources :store
resources :messages, :only => [:create, :destroy]
end
Messages Model
class Message < ActiveRecord::Base
belongs_to :user
scope :sent, where(:sent => true)
scope :received, where(:sent => false)
def send_message(from, recipients)
recipients.each do |recipient|
msg = self.clone
msg.sent = false
msg.user_id = recipient
msg.save
end
self.update_attributes :user_id => from.id, :sent => true
end
end
You are invoking the method on a class level: Message.send_message. For this to work, it would expect a declaration like this:
def self.send_message(from, recipients)
# ...
end
But, you got this instead:
def send_message(from, recipients)
# ...
end
So, either invoke the method on the instance you need it for, or refactor to make it work on a class level.

Rails Functional Test Failing Due to Association

I have an accounts model that holds some basic account info (account name, website, etc). I then have a user model that has the following in the app/models/user.rb
belongs_to :account
I also have the following in my routes.rb
map.resources :account, :has_many => [:users, :othermodel]
the problem I'm facing is that the following test is failing:
test "should create user" do
assert_difference('User.count') do
post :create, :user => { } #this is the line it's actually failing on
end
assert_redirected_to user_path(assigns(:user)) #it doesn't get here yet
end
The error it gives is "Can't find Account without ID" so I kind of understand WHY it's failing, because of the fact that it doesn't have the account object (or account_id as it were) to know under what account to create the user. I have tried variations of the following but I am completely lost:
post :create, :user => { accounts(:one) } #I have the 'fixtures :accounts' syntax at the top of the test class
post :create, [accounts(:one), :user] => { }
post :create, :user => { accounts(:one), #other params for :user }
and like I said, just about every variation I could think of. I can't find much documentation on doing this and this might be why people have moved to Factories for doing test data, but I want to understand things that come standard in Rails before moving onto other things.
Can anyone help me get this working?
UPDATE:
I managed to get the test to fail in a different location, I had to ensure that the test could actually get to the create action (have some authlogic stuff in my app)
it now says
undefined method 'users' for nil:Class
So now it's saying that it can't find a users collection on my #account object in the controller, basically because it still can't find the #account even though it doesn't actually fail to find the account in question. So my before_filter :find_account works to the extent that it doesn't break, but it seem to not be finding the account.
I tried the post :create, :id => #account.id, :user => { } but to no avail. Also tried post :create, :account => accounts(:one), :user => { } and :user => { :account => accounts(:one) }, again with the same result.
I think you got the association backwards. If that's the case then Account model should belong_to :user, then User model should has_one :account.
Otherwise, since you're creating user which belongs to some account, you should pass account's :id in params:
post :create, :id => some_test_account.id, :user => {...}
The typical controller for the belongs_to side of a has_many association would have a create action like this:
def create
#account = Account.find(params[:account_id])
#user = #account.users.build(params[:user])
if #account.save
# handle success
else
# handle failure
end
end
If your controller doesn't look like this you not be handling the parameters correctly, thus the test failure.
You can also check your routes. You should see something like:
POST /accounts/:account_id/users(.:format) {:controller=>"users", :action=>"create"}
That's another clue that Rails is setting params[:account_id] to the value for the requested account.

Nested Resource testing RSpec

I have two models:
class Solution < ActiveRecord::Base
belongs_to :owner, :class_name => "User", :foreign_key => :user_id
end
class User < ActiveRecord::Base
has_many :solutions
end
with the following routing:
map.resources :users, :has_many => :solutions
and here is the SolutionsController:
class SolutionsController < ApplicationController
before_filter :load_user
def index
#solutions = #user.solutions
end
private
def load_user
#user = User.find(params[:user_id]) unless params[:user_id].nil?
end
end
Can anybody help me with writing a test for the index action? So far I have tried the following but it doesn't work:
describe SolutionsController do
before(:each) do
#user = Factory.create(:user)
#solutions = 7.times{Factory.build(:solution, :owner => #user)}
#user.stub!(:solutions).and_return(#solutions)
end
it "should find all of the solutions owned by a user" do
#user.should_receive(:solutions)
get :index, :user_id => #user.id
end
end
And I get the following error:
Spec::Mocks::MockExpectationError in 'SolutionsController GET index, when the user owns the software he is viewing should find all of the solutions owned by a user'
#<User:0x000000041c53e0> expected :solutions with (any args) once, but received it 0 times
Thanks in advance for all the help.
Joe
EDIT:
Thanks for the answer, I accepted it since it got my so much farther, except I am getting another error, and I can't quite figure out what its trying to tell me:
Once I create the solutions instead of build them, and I add the stub of the User.find, I see the following error:
NoMethodError in 'SolutionsController GET index, when the user owns the software he is viewing should find all of the solutions owned by a user'
undefined method `find' for #<Class:0x000000027e3668>
It's because you build solution, not create. So there are not in your database.
Made
before(:each) do
#user = Factory.create(:user)
#solutions = 7.times{Factory.create(:solution, :owner => #user)}
#user.stub!(:solutions).and_return(#solutions)
end
And you mock an instance of user but there are another instance of User can be instanciate. You need add mock User.find too
before(:each) do
#user = Factory.create(:user)
#solutions = 7.times{Factory.create(:solution, :owner => #user)}
User.stub!(:find).with(#user.id).and_return(#user)
#user.stub!(:solutions).and_return(#solutions)
end
I figured out my edit, when a find is done from the params, they are strings as opposed to actual objects or integers, so instead of:
User.stub!(:find).with(#user.id).and_return(#user)
I needed
User.stub!(:find).with(#user.id.to_s).and_return(#user)
but thank you so much shingara you got me in the right direction!
Joe

Resources