Rspec 'before' scope in context - ruby-on-rails-3

I am getting an undefinded local variable or method user in this test. Anyone help?
context "[user IS signed in]" do
before do
user = Fabricate(:user)
league = Fabricate(:league)
event = Fabricate(:event, league: league)
#request.env["devise.mapping"] = Devise.mappings[:user]
sign_in user
ApplicationController.any_instance.stub(:primary_leagues).and_return([league])
end
it "[creates a pick for a user]" do
post 'create', {:pick => {user_id: user.id, event_id: event.id, league_id: league.id, points_pick: "home"}}
Pick.all.size.should eq(1)
end

Your before block is declaring user as a local variable so it is out of scope within your it block.
The simplest way to fix this is to declare member variables in your before block, e.g.
before do
#user = Fabricate(:user)
#league = Fabricate(:league)
#event = Fabricate(:event, league: #league)
...
end
it "[creates a pick for a user]" do
post 'create', {:pick => {user_id: #user.id, event_id: #event.id, league_id: #league.id, points_pick: "home"}}
Pick.all.size.should eq(1)
end
This works because before and it are executing in the context of the same (generated) class instance.
Alternatively you could declare your variables with let and leave the code example as it is, e.g.
context "[user IS signed in]" do
let(:user) { Fabricate(:user) }
let(:league) { Fabricate(:league) }
let(:event) { Fabricate(:event, league: league) }
before do
#request.env["devise.mapping"] = Devise.mappings[:user]
sign_in user
ApplicationController.any_instance.stub(:primary_leagues).and_return([league])
end
it "[creates a pick for a user]" do
post 'create', {:pick => {user_id: user.id, event_id: event.id, league_id: league.id, points_pick: "home"}}
Pick.all.size.should eq(1)
end
let will instantiate the given variable when it is first referenced and memoize the result.

Related

Accessing attributes from rails setter

I am kind of stuck trying to access (read) an attribute from a custom setter in Rails:
def password=(new_password)
puts self.firstname # returns nil
# Do other checks here before setting a hashed SCrypt password
end
The firstname is defined on the (user) object, however calling the firstname will not yield the value set. I need to read various attributes from the custom setter to do password checks up against firstname, lastname and email address.
Do you have the corresponding getter method?
def password
#password
end
Or if it's a really simple getter/setter pair just
:attr_accessor password
Update on your comment: so you're wanting to do something other than just set the attribute in the setter?
So obviously I have to advise against that. However you can just do something like:
def password=(new_password)
some_method(new_password)
#password = new_password
end
This will pass the new_password method out of the setter, allowing you to do other things with it, before setting the instance variable to the new value.
If you are trying to access the firstname attribute within the password setter, as in your example, you can do it this way:
def password=(new_password)
some_method(firstname)
#password = new_password
end
You don't use self to call the getter method.
Ok seems to be an issue with my app somewhere. I just did a minimal test application that works fine:
class User < ActiveRecord::Base
def password
password ||= SCrypt::Password.new(password_hash)
end
def password=(new_password)
logger.info 'DEBUG: self.lastname: ' + self.lastname.to_s
self.password_hash = SCrypt::Password.create(new_password, :key_len => 512, :salt_size => 32)
end
end
In console it works fine:
Loading development environment (Rails 3.2.12)
2.0.0-p0 :001 > u = User.new
=> #<User id: nil, firstname: nil, lastname: nil, password_hash: nil, created_at: nil, updated_at: nil>
2.0.0-p0 :002 > u.lastname = "Some Lastname"
=> "Some Lastname"
2.0.0-p0 :003 > u.password = 'test'
DEBUG: self.lastname: Some Lastname
=> "test"
So it seems to be some issue related to a gem / something / somewhere in the application and not a Rails specific problem. Apologies to the list.
The order of the params matters here based on what I have observed.
⚠️ Notice the order of :firstname and :password when passing.
If you're using params.require(:user).permit(:firstname, :password) and then passing it to your setters via .new or =setter methods. Then you can always access self.firstname in your password= setter method because you firstname got set first and it's available to access for next setters.
However, if you have params.require(:user).permit(:password, :firstname) then your password setter method cannot access self.firstname.

How to make tests with find and date conditions?

I'm trying to develop some tests for a method which is responsible for retrieve some users created after some date. I don't know how to mock tests for it. The method is the following:
def user_list
render :nothing => true, :status => 422 if params[:time_param].blank?
time = Time.parse(params[:time_param])
#users = User.find(:all, :select => 'id, login, email',
:conditions => ["created_at > ?", time])
render :json => { :users => #users }
end
end
This is my spec:
describe UsersController do
context "when receiving time parameter" do
before (:each) do
#time_param = "2013-01-25 00:01:00"
user1 = mock_model(User, :created_at => Time.parse('2013-01-25 00:00:00'))
user2 = mock_model(User, :created_at => Time.parse('2013-01-25 00:01:00'))
user3 = mock_model(User, :created_at => Time.parse('2013-01-25 00:02:00'))
#users = []
#users << user1 << user2 << user3
end
it "should retrieve crimes after 00:01:00 time" do
User.stub(:find).with(:all, :select => 'id, login, email').and_return(#users)
get :user_list, { :time_param => #time_param }
JSON.parse(response.body)["users"].size.should eq 1
end
end
end
The problem is that it always returns all users despite of returning just one. (the last one). Where am I mistaking?
Help me =)
You are not testing what you have to test there, on a controller spec you only need to test that the method that you want is called with the parameters that you want, in your case, you have to test that the User model receives :find with parameters :all, :select => 'id, login, email', :conditions => ["created_at > ?", time] (with time the value that should be there.
Also, that logic does not belong to the controller, you should have a class method on User, something like select_for_json(date) to wrap around that find method (you can find a better name for it)
Then your controller becomes:
def user_list
render :nothing => true, :status => 422 if params[:time_param].blank?
time = Time.parse(params[:time_param])
#users = User.select_for_json(time)
render :json => { :users => #users }
end
your spec would be
before(:each) do
#users = mock(:users)
#time_param = "2013-01-25 00:01:00"
end
it "retrieve users for json" do
User.should_receive(:select_for_json).once.with(#time).and_return(#users)
get :user_list, { :time_param => #time }
assigns(:users).should == #users
end
that way you are sure that your action does what it does and the spec is A LOT faster since you are not creating users
then you can test that method on the model specs, there you have to create some users, invoke that method and check the users returned (don't stub/mock anything on your model spec)
Your stub call is telling find to ignore what it thought it was supposed to do and return #users instead. It will not attempt to match the conditions.
Unfortunately, to do your test I think you're going to have to allow the find to execute through your database which means you can't use mock_models. You probably will want to do either User.create(...) or FactoryGirl.create(:user) (or some other factory / fixture).
Of course doing it this way, you may hit MassAssignment issues if you use attr_accessible or attr_protected, but those are easy enough to stub out.
I hope that helps.

FactoryGirl creating an extra object

My factory:
FactoryGirl.define do
factory :comment do
content 'bla bla bla bla bla'
user
end
factory :user do
sequence(:username) { |n| "johnsmith#{n}" }
password '123'
factory :user_with_comments do
ignore do
comments_count 5
end
after(:create) do |user, evaluator|
FactoryGirl.create_list(:comment, evaluator.comments_count, user: user)
end
end
end
end
My spec:
require 'spec_helper'
describe Comment do
let(:comment) { Factory.create :comment }
describe "Attributes" do
it { should have_db_column(:content).of_type(:text) }
it { should have_db_column(:user_id).of_type(:integer) }
it { should have_db_column(:profile_id).of_type(:integer) }
end
describe "Relationships" do
it { should belong_to(:profile) }
it { should belong_to(:user) }
end
describe "Methods" do
describe "#user_name" do
it "Should return the comment creater username" do
user = Factory.create :user
binding.pry
comment.user = user
binding.pry
comment.user_username.should == user.username
end
end
end
end
On the first binding.pry, User.count returns 1 as expected. But on the second binding.pry, User.count returns 2. My question is, why the comment.user = user assignment creates a new user record ? Thanks in advance.
The reason is that your let for comment calls Factory.create :comment. In the factory for comment, it calls the association for user.
So when you use your let, it makes a comment object and a user object and connects them. Then, you override that user when you set comment.user=user.

RSpec - how to test default value for attribute using callback

Here's my test:
require 'spec_helper'
describe League do
it 'should default weekly to false' do
league = Factory.create(:league, :weekly => nil)
league.weekly.should == false
end
end
end
And here's my model:
class League < ActiveRecord::Base
validates :weekly, :inclusion => { :in => [true, false] }
before_create :default_values
protected
def default_values
self.weekly ||= false
end
end
When I run my test, I get the following error message:
Failure/Error: league = Factory.create(:league, :weekly => nil)
ActiveRecord::RecordInvalid:
Validation failed: Weekly is not included in the list
I've tried a couple different approaches to trying to create a league record and trigger the callback, but I haven't had any luck. Is there something that I am missing about testing callbacks using RSpec?
I believe that what you are saying is, before create, set weekly to false, then create actually sets weekly to nil, overwriting the false.
Just do
require 'spec_helper'
describe League do
it 'should default weekly to false' do
league = Factory.create(:league) # <= this line changed
league.weekly.should == false
end
end
end
in your test. No need to explicitly set nil.

Rails assigning names to variables

I'm building a user ranking system, and am trying to assign user.rank values with a name.
I wanted to define something like this in my User model and then be able to reference it when displaying each user's rank, but this probably isn't the best way:
class User < ActiveRecord::Base
RANK_NAMES = {
'Peasant' => (0..75),
'Craftsman' => (76..250),
'Vassal' => (251..750),
'Noble' => (750..1500),
'Monarch' => (1501..999999)
}
Perhaps it would be better to define a method in a controller or helper like:
if user.rank == 0..75
rank_name = "Peasant"
elsif...
But not sure how to do that. Anyone have any thoughts? I'm not even sure what to call what it is I'm trying to do, thus making it difficult to research on my own.
It could be something even as simple as this, assuming user.rank exists.
class User < ActiveRecord::Base
...
def rank_name
case self.rank
when 0..75
'Peasant'
when 76..250
'Craftsman'
when 251..750
'Vassal'
when 750..1500
'Noble'
when 1501..999999
'Monarch'
end
end
...
end
If rank_name is specific to the User, I'd make it a method of User.
You could try something like below. It might give you some ideas.
class User
RANKS = [
{:name => 'Peasant', :min => 0, :max => 75},
{:name => 'Craftsman', :min => 76, :max => 250}
# ...
]
attr_accessor :rank
def rank_name
# TODO what happens if rank is out of range of all ranks or rank is nil
# or not an integer
User::RANKS[rank_index][:name]
end
private
def rank_index
User::RANKS.index { |r| (r[:min]..r[:max]).include? #rank }
end
end
user = User.new
user.rank = 76
puts user.rank_name # -> Craftsman