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.
Related
I'm writing some tests for my Rails project. I've come to writing some tests around a polymorphic association, for which I have defined a fixture. However, when I try to access the polymorphically associated field on that fixture, it returns nil.
The strange part is that while the field itself is nil, the _id and _type fields that describe it are not.
This is the model:
class Comment < ActiveRecord::Base
default_scope { where(is_deleted: false) }
belongs_to :post, :polymorphic => true
belongs_to :user
end
(In case it wasn't obvious, the post association is the polymorphic.)
This is the action in the controller that I'm trying to test:
class CommentsController < ApplicationController
# ...
def update
if #comment.update comment_params
puts #comment.post.nil?
puts #comment.post_id.nil?
puts #comment.post_type.nil?
if #comment.post_type == 'Question'
redirect_to url_for(:controller => :questions, :action => :show, :id => #comment.post.id)
else
redirect_to url_for(:controller => :questions, :action => :show, :id => #comment.post.question.id)
end
else
flash[:error] = "Comment failed to update."
if #comment.post_type == 'Question'
redirect_to url_for(:controller => :questions, :action => :show, :id => #comment.post.id)
else
redirect_to url_for(:controller => :questions, :action => :show, :id => #comment.post.question.id)
end
end
end
private
def comment_params
params.require(:comment).permit(:content, :post_type, :post_id)
end
# ...
end
The test itself looks like this:
test "should update existing comment" do
sign_in users(:standard_user)
patch :update, :id => comments(:one).id, :comment => { :content => "ABCDEF GHIJKL MNOPQR STUVWX YZ" }
assert_not_nil assigns(:comment)
assert_not_nil assigns(:comment).post
assert_response(302)
end
And, finally, the fixture for that existing comment (comments(:one)) is:
one:
user: standard_user
post: one (Answer)
content: ABCDEF GHIJKL MNOPQR
Both the standard_user User fixture and the one Answer fixture are correctly defined.
Running the test gives me this error:
2) Error:
CommentsControllerTest#test_should_update_existing_comment:
NoMethodError: undefined method `question' for nil:NilClass
app/controllers/comments_controller.rb:50:in `update'
test/controllers/comments_controller_test.rb:16:in `block in <class:CommentsControllerTest>'
The three log lines near the top of the update action output true \n false \n false - i.e. that the post field is nil, but that post_id and post_type are not.
Why is this? What can I do to fix it? I have no ID fields defined in my fixtures YAML, and I'd prefer not to have to specify the IDs (unless it can be done with ERB).
This is the entirety of the Answers fixture that the Comments depend on:
one:
body: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
score: 0
question: one
user: standard_user
two:
body: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
score: 0
question: one
user: editor
That in turn depends on this Question fixture:
one:
title: ABCDEF GHIJKL MNOPQR STUVWX YZ
body: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
tags:
- ABCDEF
- GHIJKL
- MNOPQR
score: 0
user: standard_user
It seems that this issue is caused by there being no Answer fixtures in the database when the comments tests happen (so the call to #comment.post returns nil, and #comment.post.question or #comment.post.id raises a NoMethodError). However, the test helper file calls fixtures :all, so I don't see why those fixtures aren't loaded.
This happens when ActiveRecord couldn't find the polymorphic association.
Even though the post_id and post_type have values, but Answer with id by comment.post_id could not be found, it will just return nil when you call comment.post.
Could you paste your answers.yml fixture? I think you might have put an id in it accidentally.
EDIT:
I found the bug in your project. You didn't add default value for is_deleted in answers and comments tables, so you won't find the correct record since their is_deleted is NULL actually. My advice would be: never use default_scope, you can always have another better solution than that.
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)
The merit ruby gem is not adding the points to the user with the "user.passion.present" added. It works without it. I set everything up with working with Devise and Merit gems. Everything seems to work except this.
module Merit
class PointRules
include Merit::PointRulesMethods
def initialize
score 50, :on => 'user/registrations#create', model_name: 'User'
score 10, :on => 'user/registrations#update', model_name: 'User' do |user|
user.passion.present?
end
#
# score 15, :on => 'reviews#create', :to => [:reviewer, :reviewed]
#
# score 20, :on => [
# 'comments#create',
# 'photos#create'
# ]
score 20, on: 'lyrics#create', to: :user, description: 'Plus 20 points'
score (-20), on: 'lyrics#destroy', to: :user
end
end
end
Did you add the instance variable to the update overridden action?
See wiki page: https://github.com/tute/merit/wiki/How-to-grant-badges-on-user-registration-using-Devise.
Trying to test a controller in Rspec. (Rails 2.3.8, Ruby 1.8.7, Rspec 1.3.1, Rspec-Rails 1.3.3)
I'm trying to post a create but I get this error message:
ActiveRecord::AssociationTypeMismatch in 'ProjectsController with appropriate parameters while logged in: should create project'
User(#2171994580) expected, got TrueClass(#2148251900)
My test code is as follows:
def mock_user(stubs = {})
#user = mock_model(User, stubs)
end
def mock_project(stubs = {})
#project = mock_model(Project, stubs)
end
def mock_lifecycletype(stubs = {})
#lifecycletype = mock_model(Lifecycletype, stubs)
end
it "should create project" do
post :create, :project => { :name => "Mock Project",
:description => "Mock Description",
:owner => #user,
:lifecycletype => mock_lifecycletype({ :name => "Mock Lifecycle" }) }
assigns[:project].should == mock_project({ :name => "Mock Project",
:description => "Mock Description",
:owner => mock_user,
:lifecycletype => mock_lifecycletype({ :name => "Mock Lifecycle" })})
flash[:notice].should == "Project was successfully created."
end
The trouble comes when I try to do :owner => #user in the code above. For some reason, it thinks that my #user is TrueClass instead of a User class object. Funny thing is, if I comment out the post :create code, and I do a simple #user.class.should == User, it works, meaning that #user is indeed a User class object.
I've also tried
:owner => mock_user
:owner => mock_user({ :name => "User",
:email => "user#email.com",
:password => "password",
:password_confirmation => "password })
:owner => #current_user
Note #current_user is also mocked out as a user, which I tested (the same way, #current_user.class.should == User) and also returns a TrueClass when I try to set :owner.
Anybody have any clue why this is happening?
Thank you!
From what I can see, you are not creating your instance variable, #user before referencing it in the post statement. You would do well to create the instance variables prior to the post so the preconditions are immediately obvious. That way you could know whether #user had been set.
I know some people prefer the one-line-of-code-is-better-because-i'm-smart method of writing stuff like this, but I've found being explicit and even repetitive is a really good idea, particularly in tests.
I'm adding the following code that I believe may express your intent better that what you have. In my code, I use mock expectations to "expect" a Project is created with a particular set of parameters. I believe your code assumes that you can do an equality comparison between a newly-created mock Project and a different one created during execution of your controller. That may not be true because they are distinctly different objects.
In my code, if you have a problem with something evaluating to TrueClass or the like, you can use a line of code like user.should be_a(User) to the example to make sure stuff is wired up correctly.
def mock_user(stubs = {})
mock_model(User, stubs)
end
def mock_project(stubs = {})
mock_model(Project, stubs)
end
def mock_lifecycletype(stubs = {})
mock_model(Lifecycletype, stubs)
end
it "should create project" do
user = mock_user
owner = user
lifecycletype = mock_lifecycletype({ :name => "Mock Lifecycle" })
# Not certain what your params to create are, but the argument to with
# is what the params are expected to be
Project.should_receive(:create).once.with({:user => user, :owner => owner, :lifecycletype => lifecycletype})
post :create, :project => { :name => "Mock Project",
:description => "Mock Description",
:owner => #user,
:lifecycletype => lifecycletype }
flash[:notice].should == "Project was successfully created."
end
I'm trying to Factory a Post associated with a Vote. So that Post.votes would generate the Vote's that are associated with it.
Factory.define :voted_post, :parent => :post, :class => Post do |p|
p.association :votes, :factory => :vote
end
And my rspec2 is relatively straightforward :
describe "vote scores" do
it "should show me the total vote score" do
#post = Factory(:voted_post)
#post.vote_score.should == 1
end
end
So why would it return this error :
Failures:
1) Post vote scores should show me the total vote score
Failure/Error: #post = Factory(:voted_post)
undefined method `each' for #<Vote:0x105819948>
ruby 1.8.7 (2009-06-12 patchlevel 174) [universal-darwin10.0]
Rails 3.0.0
Factory.define :voted_post, :parent => :post, :class => Post do |p|
p.association :votes, :factory => :vote
end
Is the same as trying to go
some_voted_post.votes = Factory(:vote)
Basically you're attempting to assign a single vote as an array.
EDIT
You can have an array containing a single vote, but you can't just have a single vote.
It's the difference between:
some_voted_post.votes = Factory(:vote)
and
some_voted_post.votes = [Factory(:vote)]
The former is not an array, and therefore does not work, the latter is an array.
If you want to assign has_many association which expects array and not a single value, you should use the long form:
Factory.define :voted_post, :parent => :post, :class => Post do |p|
p.votes { |vote| [vote.association(:vote)] }
end
And encapsulate the creation of the association with [] to ensure that array would be returned