rspec testing association - ruby-on-rails-3

I want to test that a staff member is associated with a company in my rspec controller tests.
I would like to end up with this in my create action of the staff controller:
staff.companies << current_company
Where current_company is collected from a session variable.
How do I write a test for this?
I've got these models
class Company < ActiveRecord::Base
has_many :employees
has_many :staff, :through => :employees
end
class Employee < ActiveRecord::Base
belongs_to :company
belongs_to :staff
end
class Staff < ActiveRecord::Base
has_many :employees
has_many :companies, :through => :employees
end
The following test is my attempt to spec the assocation and it fails when I enter in the association code:
it "should belong to the current_company" do
staff.should_receive(:companies)
post :create
end
If I enter the 'staff.companies << current_company' code in my controller I get this error when running that test:
Failure/Error: post :create
NoMethodError:
You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.<<
Staff controller create method:
def create
#staff = Staff.new(params[:staff])
if #staff.save
#staff.companies << current_company
redirect_to staff_index_path, :notice => "Staff created successfully!"
else
#company = #staff.firm || current_company
flash[:alert] = "Staff failed to create"
render "new"
end
end

I would use a different approach, since testing that the model should receive a certain message couples your tests too tightly to the implementation. Do you really care whether companies receives #<< or some other method?
Really, what you want to test is whether the user's company is recorded when they post to the page. It doesn't matter how it was recorded. So I'd do something like this:
it "should add the company to the user's list of companies" do
lambda do
post :create
end.should change(staff.companies, :count).from(0).to(1)
staff.companies.map(&:name).should include("Acme, Inc.")
end
This is testing behavior instead of implementation. The advantage is that your test wont fail when someone changes that << to the equivalent push. It also has the advantage of being clearer about your intention and therefore better documenting the code.

If you're in your controller spec, I would use stub_chain
staff.stub_chain(:company, :<<).once.and_return(true)
which will mock out the company call AND the << call AND expect it to be called once.
(At least, that .once should work with stub_chain...)

You can test it with :
staff.should have(1).company
Or if the staff already has other companies, get the count and test for have(count+1).companies.

The problem with the code is that once you stub out a method - it no longer exists on the model anymore.
You have stubbed out the "companies" method (when you set the expectation on it) and it now, no-longer calls the actual, real companies association on the model but the stub that you have created... which returns nil (because you didn't set a returns value on it).
Then, when you try to put a company into this new, null method using << it says it can't do that.
To get around it you can do what you did which is to set a returns value:
staff.should_receive(:companies).and_return([])
which will then make sure that:
#staff.companies << current_company
will not fail with the horrible nil error (because there's and actual, real array for the company to go into).
But really the best thing to do is as the previous people have suggested and test what you actually really need to test - which is that saving a staff with companies will cause a new company to get saved to the db.

Related

New to Rails 4 Testing - Need help getting started (rSpec and Devise)

I'm relatively new to testing and very new to Rails 4 and rSpec. I am trying to test a controller that uses Devise for authentication and I am stuck. All of the examples I can find are for Rails 3.
I'm using Rails 4.0.3, Devise 3.2.3, rSpec 2.14.1 and FactoryGirl 4.4.0.
class LessonPlansController < ApplicationController
before_action :authenticate_user!
# GET /lesson_plans
def index
#lesson_plans = current_user.lesson_plans.to_a
end
.
.
.
private
# Use callbacks to share common setup or constraints between actions.
def set_lesson_plan
#lesson_plan = LessonPlan.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def lesson_plan_params
params[:lesson_plan]
end
def lesson_plan_params
params.require(:lesson_plan).permit(:title, :synopsis)
end
end
Here are my factory definitions: (Maybe I don't need to define user_id in the lesson_plan factory?)
FactoryGirl.define do
factory :user do
sequence( :username ) { |n| "user#{n}" }
sequence( :email ) { |n| "foo#{n}#example.com" }
password 'foobarbaz'
password_confirmation 'foobarbaz'
created_at Time.now
updated_at Time.now
end
end
FactoryGirl.define do
factory :lesson_plan do
user_id 1
title "The French Revolution"
synopsis "Background and events leading up to the French Revolution"
end
end
And the test part is where I get stuck.
describe LessonPlansController do
let(:valid_attributes) { { } }
let(:valid_session) { {} }
# describe "GET index" do
it "assigns all lesson_plans as #lesson_plans" do
user=FactoryGirl.create(:user)
sign_in user
lesson_plan = LessonPlan.create! valid_attributes
get :index, {}, valid_session
assigns(:lesson_plans).should eq([lesson_plan])
end
end
I'm not sure what to put in valid_attributes and valid_session (or if I even need them). The test will get as far as signing in the user, but will fail on creation of the lesson_plan. Admittedly this is the default/generated test for rSpec, but I am not sure how to proceed.
Examples I have seen use a before block to set up the user. I haven't been able to find anything on the Devise wiki page covering how to write basic rSpec tests for a controller that requires the user to be logged in. Any pointers would be greatly appreciated!
"I'm not sure what to put in valid_attributes and valid_session (or if I even need them)."
Well that depends what you're testing for.. Say you're testing validations & want to ensure that a record not be created if x column is set to null... then you could try to specifically create a record with invalid attributes (e.g. column: nil) and expect the result to not return true; maybe you want to ensure that it IS created with valid attributes.
You can btw, use `attributes_for(:factory_name)`` since you're using FactoryGirl. And no you don't necessarily need to specify the user's id in your lesson plan factory; unless you always want it to reference user 1. You can simply reference user with no value. Check out http://everydayrails.com/2012/03/12/testing-series-intro.html and especially parts 3-5 for an introduction to testing with RSPec.. I found this a pretty easy to follow guide when I was getting started.

rails3 Pundit policy base on join table value

User has_many constructusers, the latter being a join table for a has_many :through relationship to Construct. For the application purposes, the boolean roles are defined in the join table (constructusers.manager, constructusers.operator, etc.), while admin is a user attribute.
So when it comes time to define the policies on the actions the following throws a no method error for 'manager', while a relationship is recognised ActiveRecord::Relation:0x000001035c4370
def show?
user.admin? or user.constructusers.manager?
end
if the relationship (I assume the proper one) is correct, why is there no recognition of the boolean attribute?
As per comment below, for the simple reason that is plural. Thus filtering requires:
Constructuser.where(['construct_id = ? and user_id = ?', params[:id], current_user]).first
...which is running in the controller and impacts the view. Nonetheless, for proper Pundit handling, this needs to be factored out... still de application_controller in a before filter to set that attribute. However a before_filter :set_constructuser_manager with that find condition, with nil case handling, still has no impact when stating the policy
def show?
set_constructuser_manager?
end
Update: as per comment below. Pundit class private method
def contractorconstruct
#contructs = Construct.where(['constructusers.user_id = ?', user]).joins(:users).all
#contractorconstruct ||= Contractor.where(['construct_id IN (?)', #contructs]).first
end
and action rule
|| contractorconstruct?
returns no method error.
manager? will be a method on an instance of Constructuser, not on the relation. Think about what you are asking, "Is this constructusers a manager?" - it makes no sense. How would the computer know what constructuser you are talking about?
If a user has_many constructusers, in order to use manager? you need to find the instance you are concerned about. If this is in the ConstructPolicy, then you need to find the specific constructuser that links user to the construct that you are authorizing, then check if that single constructuser is manager?.
If you are in the Construct controller, you'll have something like
class ConstructsController
before_action :set_construct
def show
authorize #construct
# ...
end
# ...
end
In your policy then, user will be the current user and record will be #construct.
class ConstructPolicy
def show?
user.admin? || constructuser.manage?
end
private
def constructuser
#constructuser ||= Constructuser.find_by(user_id: user, construct_id: record)
end
end

Rails: How do I transactionally add a has_many association to an existing model?

Let's imagine I run an imaginary art store with a couple models (and by models I'm referring to the Rails term not the arts term as in nude models) that looks something like this:
class Artwork < ActiveRecord::Base
belongs_to :purchase
belongs_to :artist
end
class Purchase < ActiveRecord::Base
has_many :artworks
belongs_to :customer
end
The Artwork is created and sometime later it is included in a Purchase. In my create or update controller method for Purchase I would like to associate the new Purchase with the existing Artwork.
If the Artwork did not exist I could do #purchase.artworks.build or #purchase.artworks.create, but these both assume that I'm creating a new Artwork which I am not. I could add the existing artwork with something like this:
params[:artwork_ids].each do |artwork|
#purchase.artworks << Artwork.find(artwork)
end
However, this isn't transactional. The database is updated immediately. (Unless of course I'm in the create controller in which case I think it may be done "transactionally" since the #purchase doesn't exist until I call save, but that doesn't help me for update.) There is also the #purchase.artwork_ids= method, but that is immediate as well.
I think something like this will work for the update action, but it is very inelegant.
#purchase = Purchase.find(params[:id])
result = #purchase.transaction do
#purchase.update_attributes(params[:purchase])
params[:artwork_ids].each do |artwork|
artwork.purchase = #purchase
artwork.save!
end
end
This would be followed by the conventional:
if result
redirect_to purchase_url(#purchase), notice: 'Purchase was successfully updated.' }
else
render action: "edit"
end
What I'm looking for is something like the way it would work from the other direction where I could just put accepts_nested_attributes_for in my model and then call result = #artwork.save and everything works like magic.
I have figured out a way to do what I want which fairly elegant. I needed to make updates to each part of my Product MVC.
Model:
attr_accessible: artwork_ids
I had to add artwork_ids to attr_accessible since it wasn't included before.
View:
= check_box_tag "purchase[artwork_ids][]", artwork.id, artwork.purchase == #purchase
In my view I have an array for each artwork with a check_box_tag. I couldn't use check_box because of the gotcha where not checking the box would cause a hidden value of "true" to be sent instead of an artwork id. However, this leaves me with the problem of deleting all the artwork from a purchase. When doing update, if I uncheck each check box, then the params[:purchase] hash won't have an :artwork_ids entry.
Controller:
params[:purchase][:artwork_ids] ||= []
Adding this guarantees that the value is set, and will have the desired effect of removing all existing associations. However, this causes a pesky rspec failure
Purchase.any_instance.should_receive(:update_attributes).with({'these' => 'params'}) fails because :update_attributes actually received {"these"=>"params", "artwork_ids"=>[]}). I tried setting a hidden_value_tag in the view instead, but couldn't get it to work. I think this nit is worthy of a new question.
It is probably best to use make the purchase model a join table and have many to many associations.
Here is an example for your use case.
Customer model
has_many :purchases
has_many :artwork, :through => :purchase
Artwork model
has_many :purchases
has_many :customers, :through => :purchase
Purchase model
belongs_to :customer
belongs_to :artwork
The purchase model should contain customer_id and artwork_id.
you would also need to create a purchase controller that allows you create a new purchase object.
When a customer presses the purchase button it would create a new purchase object which includes the customer_id and the artwork_id. This allows you to create an association between the customer and the artwork they purchase. You can also have a price_paid column to save the price the customer paid at the time of purchase.
if you need more help you can research join many to many associations using :through.
hope it helps

Rails ActiveRecord has_one association together with Object method override

I came across a peculiar problem with has_one association in combination with an Object method override. Can somebody explain to me what is going on?
Here is how it goes:
I have a has_one relationship between Supplier and Account, like in the example of the has_one example used in Rails Guides.
Supplier:
class Supplier < ActiveRecord::Base
validates :name, :presence => true
has_one :account
nilify_blanks
end
Account:
class Account < ActiveRecord::Base
belongs_to :supplier
validates :supplier_id, :presence => true
nilify_blanks
def foo
puts 'in account'
end
def to_s
puts 'in account'
end
end
I also have a method foo on Object as follows:
class Object
def foo
puts 'in object'
end
end
When I call:
Supplier#account#to_s
I get 'in account'
When I call:
Supplier#account#foo
I get 'in object'
whereas I would expect it to print 'in account'
Does anybody have any clue why does this happen? Is this a bug in Rails ActiveRecord?
Thanks in advance
P.S. If you want, you can get a full fledged application that demonstrates the problem from here:
https://github.com/pmatsinopoulos/test_association_and_object_method_override.git
After doing some investingation with one of my friends, got the way assoiciation works.
when we do Supplier.account it will give you object of AssociationProxy not an object of account.
AssociationProxy delegates all methods to associated object if its definition not present in itself(it also delegates methods like class, inspect etc. so you can get the actual class name).
Now, when we add foo in Object class its available in AssociationProxy and when you say Supplier.account it invokes foo from AssociationProxy not from account.
if you want to invoke foo from account use target method to get actual account object like
Supplier.account.target.foo #=> foo from account

How to get a scope for all database rows in rails 3?

assume we the following setup:
class Post < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :posts
end
assume further the user has a boolean attribute 'admin', which indicates if he is a global admin or not.
I want to write a method (or a scope?) for the User class, called 'visible_posts'. If the user is no admin, it should return just its own posts. If he IS admin the method should return all posts in the system.
My first attempt was something like this:
class User < ActiveRecord::Base
[...]
def visible_posts
if admin?
Post.all
else
posts
end
end
end
Problem here is that Post.all returns an Array, but I would rather like to have an ActiveRecord::Relation like I get from posts to work with it later on.
Is it somehow possible to get an ActiveRecord::Relation that represents ALL posts ?
You can do Post.scoped i guess in Rails
And later on this you can call .all to fetch the results