Why does my FactoryGirl callback run when it shouldn't? - ruby-on-rails

I am using FactoryGirl 3.3.0 with RoR 3.2.3
I have a user model which has_one profile like so;
class User < ActiveRecord::Base
has_secure_password
has_one :profile, dependent: :destroy
accepts_nested_attributes_for :profile, update_only: true
attr_accessible :email, :username, :password, :password_confirmation, :profile_attributes
before_create :build_profile
end
class Profile < ActiveRecord::Base
attr_accessible :first_name, :last_name
belongs_to :user
validates :user, presence: true
validates :first_name, presence: true, on: :update
validates :last_name, presence: true, on: :update
end
In my rspec tests I need to sometimes prevent the before_create :build_profile from running so I can have a user without a profile. I manage this with a FactoryGirl callback
after(:build) {|user| user.class.skip_callback(:create, :before, :build_profile)}
My user factories are defined as follows;
FactoryGirl.define do
factory :user do
sequence(:email) {|n| "user_#{n}#example.com"}
sequence(:username) {|n| "user_#{n}"}
password "secret"
factory :user_with_profile do
factory :new_user_with_profile do
before(:create) {|user| user.activated = false}
end
factory :activated_user_with_profile do
before(:create) {|user| user.activated = true}
end
end
factory :user_without_profile do
after(:build) {|user| user.class.skip_callback(:create, :before, :build_profile)}
factory :new_user_without_profile do
before(:create) {|user| user.activated = false}
end
factory :activated_user_without_profile do
before(:create) {|user| user.activated = true}
end
end
end
end
My expectation was that the :new_user_without_profile and :activated_user_without_profile would inherit the after(:build) callback from :user_without_profile while the :new_user_with_profile and :activated_user_with_profile factories would not, but it's not quite working like that. Here's an excerpt from the console to demonstrate my problem;
irb(main):001:0> user = FactoryGirl.create :new_user_with_profile
irb(main):002:0> user.profile
=> #<Profile id: 11, first_name: "", last_name: "", created_at: "2012-07-10 08:40:10", updated_at: "2012-07-10 08:40:10", user_id: 18>
irb(main):003:0> user = FactoryGirl.create :new_user_without_profile
irb(main):004:0> user.profile
=> nil
irb(main):005:0> user = FactoryGirl.create :new_user_with_profile
irb(main):006:0> user.profile
=> nil
So, the first time I create a :new_user_with_profile, a profile is created as expected but the second time (after creating a :new_user_without_profile), it doesn't any more! The after(:build) callback doesn't seem to be getting called again (if I add some code to it to output something, I don't see it in the terminal). I have no idea what's going wrong here. Does anyone else?

This is a dirty solution but have you tried to write the definition of the callback in the factory :user_with_profile:
after(:build) {|user| user.class.set_callback(:create, :before, :build_profile)}
Does it work?

Related

Rails: Can't build object wth Factory Girl after adding accepts_nested_attributes_for

I cannot get Factory Girl to produce a User object when accepts_nested_attributes_for is enabled.
Given the models:
class Account < ApplicationRecord
belongs_to :meta, polymorphic: true, dependent: :destroy
end
class User < ApplicationRecord
has_one :account, as: :meta
validates :fname, presence: true
validates :lname, presence: true
validates_presence_of :account
#accepts_nested_attributes_for :account
end
And, the factories:
FactoryGirl.define do
factory :account do
sequence(:email) { |n| "account#{n}#mack.com" }
password "abcdef123"
password_confirmation "abcdef123"
confirmed_at Date.today
end
end
FactoryGirl.define do
factory :user do
sequence(:fname) { |n| "first#{n}" }
sequence(:lname) { |n| "last#{n}" }
after(:build) do |user|
user.build_account attributes_for(:account)
end
end
end
The following spec:
describe User, :type => :model do
it "has a valid factory" do
user = build(:user)
expect(user).to be_valid
end
end
Fails if I uncomment the accepts_nested_attributes_for in the User model. I would like to use the nested attributes for working with User objects, but I cannot figure out how to get FactorGirl to build a User with nested attributes enabled.
I get the following error:
1) User has a valid factory
Failure/Error: expect(user).to be_valid
expected #<User id: nil, fname: "first1", lname: "last1", created_at: nil, updated_at: nil> \
to be valid, but got errors: Account meta must exist
I am using Rails version 5.0.0beta1 and Factory Girl 4.6.0.
I've been battling this all day -- thanks for your help.
Edit: I see the same behavior using the rails console, so it would seem like the issue is independent of Factory Girl.

Need help understanding why unit test for method is not working

I'm trying hard to cover all of my model's methods, associations and validation in my unit tests and so far it's going great. I've subscribed to TreeHouse and watched Ruby Foundations - Testing religiously.
I stumbled last night on this error while testing a method for my Provider.rb model:
class Provider < ActiveRecord::Base
attr_accessible :description, :name
validates :name, :presence => true
validates :description, :presence => true
validates :name, :length => { :minimum => 6, :maximum => 100 }
validates :description, :length => { :minimum => 6, :maximum => 100 }
has_many :courses
resourcify
def unique_locations
Location.joins(sessions: :course).where(courses: { provider_id: self.id }).uniq.pluck('locations.name')
end
end
My unit test for the unique_locations method is the following:
test 'should return a list of unique locations' do
provider = FactoryGirl.build(:provider)
assert_equal provider.unique_locations, ["location_1", "location_2"]
end
I get the following error after running my tests:
<[]> expected but was
<["location_1", "location_2"]>.
My factories are quite simple:
FactoryGirl.define do
factory :course do
name 'Snowboard 101'
description 'Snowboard course'
association :provider, factory: :provider
end
end
FactoryGirl.define do
factory :provider do
name 'The School of Hard Knocks'
description 'School description'
end
end
FactoryGirl.define do
factory :session_snowboard, class: Session do
name 'Winter Session'
description 'Snowboarding 101'
price 200
class_size 4
association :course, factory: :course
association :location, factory: :location_1
end
factory :session_ski, class: Session do
name 'skiing 101'
description 'Start in november'
price 100
class_size 4
association :course, factory: :course
association :location, factory: :location_2
end
end
FactoryGirl.define do
factory :location_1 do
name 'location_1'
end
factory :location_2 do
name 'location_2'
end
end
My unique_locations method works fine in my different environment. I just can't figure out why it's not returning the list of unique locations in test.
Any idea?
Thanks,
Francis
You are calling FactoryGirl.build(:provider) which only builds and doesn't save to the DB, so the unique_locations obviously wouldn't have the id to go by. Use create and it should fine.
So after much poking around with Factory_Girl I found the solution:
Here are my factories:
FactoryGirl.define do
factory :provider do
name 'McGill University'
description 'McGill is one of the best universities in the world.'
after(:create) {|provider| create_list(:course, 2, provider: provider) }
end
end
FactoryGirl.define do
factory :course do
name 'Snowboard 101'
description 'Snowboard course'
provider
after(:create) {|course| create_list(:session, 2, course: course) }
end
end
FactoryGirl.define do
factory :session do
name 'Winter Session'
description 'Snowboarding 101'
price 150
class_size 4
course
location
end
end
FactoryGirl.define do
factory :location do |l|
l.sequence(:name) { |n| "location_#{n}"}
end
end
I've also updated my test:
test 'should return a list of unique locations' do
provider = create(:provider)
assert_equal provider.unique_locations, %w[location_1 location_2 location_3 location_4]
end
This successfully creates 4 different locations.

How to make my method add_friend work in rake db:seed

I'm folowwing Charles Max Wood tutorial on the twitter clone , flitter.
I'm having and error undefined method friendships when I launch rake db:seed .I'am trying to add friend via the rake db:seed task , The method add_friend is define in the User model. But i need help to define the method friendships so that the task can work .Thank you a lot for your help .
Here is the db/seeds.rb file
require 'faker'
require 'populator'
User.destroy_all
10.times do
user = User.new
user.username = Faker::Internet.user_name
user.email = Faker::Internet.email
user.password = "test"
user.password_confirmation = "test"
user.save
end
User.all.each do |user|
Flit.populate(5..10) do |flit|
flit.user_id = user.id
flit.message = Faker::Lorem.sentence
end
3.times do
User.add_friend(User.all[rand(User.count)])
end
end
and there is the user file.
class User < ActiveRecord::Base
# new columns need to be added here to be writable through mass assignment
attr_accessible :username, :email, :password, :password_confirmation
attr_accessor :password
before_save :prepare_password
validates_presence_of :username
validates_uniqueness_of :username, :email, :allow_blank => true
validates_format_of :username, :with => /^[-\w\._#]+$/i, :allow_blank => true, :message => "should only contain letters, numbers, or .-_#"
validates_format_of :email, :with => /^[-a-z0-9_+\.]+\#([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i
validates_presence_of :password, :on => :create
validates_confirmation_of :password
validates_length_of :password, :minimum => 4, :allow_blank => true
has_many :flits, :dependent => :destroy
has_many :friendships
has_many :friends, :through => :friendships
def self.add_friend(friend)
friendship = friendships.build(:friend_id => friend.id)
if !friendship.save
logger.debug "User '#{friend.email}' already exists in the user's friendship list."
end
end
# login can be either username or email address
def self.authenticate(login, pass)
user = find_by_username(login) || find_by_email(login)
return user if user && user.password_hash == user.encrypt_password(pass)
end
def encrypt_password(pass)
BCrypt::Engine.hash_secret(pass, password_salt)
end
private
def prepare_password
unless password.blank?
self.password_salt = BCrypt::Engine.generate_salt
self.password_hash = encrypt_password(password)
end
end
end
friendship.rb
class Friendship < ActiveRecord::Base
attr_accessible :friend_id, :user_id
belongs_to :user
belongs_to :friend, :class_name => 'User'
validates_uniqueness_of :friend_id, :scope => :user_id
validates_presence_of :user_id, :friend_id
end
I think what you want to be doing is calling add_friend on the instance user, and not on the class User:
3.times do
user.add_friend(User.all[rand(User.count)])
end
Also your add_friend method should be an instance method, not a class method, so you don't need the self:
def add_friend(friend)
friendship = friendships.build(:friend_id => friend.id)
if !friendship.save
logger.debug "User '#{friend.email}' already exists in the user's friendship list."
end
end
You should define this method as a class method not instance method:
def self.add_friend(friend)
friendship = friendships.build(:friend_id => friend.id)
if !friendship.save
logger.debug "User '#{friend.email}' already exists in the user's friendship list."
end
end

Testing cancan abilities and getting MassAssignmentSecurity::Error

I have implemented cancan and would like to test abilities as recommended on the cancan wiki. I trying to replicate "user can only destroy projects which he owns."
spec/models/ability_spec.rb:
require "cancan/matchers"
require 'spec_helper'
describe Ability do
context "user is investigator" do
it "user can only destroy projects which he owns" do
user = FactoryGirl.create(:user)
ability = Ability.new(user)
ability.should be_able_to(:destroy, Project.new(:user => user))
end
end
end
However I get:
ActiveModel::MassAssignmentSecurity::Error:
Can't mass-assign protected attributes: user
Models:
class User < ActiveRecord::Base
has_many :projects, dependent: :destroy
devise :database_authenticatable, etc...
attr_accessible :email, :password, :password_confirmation, :remember_me, :locale
validates :role, :presence => true
end
class Project < ActiveRecord::Base
belongs_to :user
end
Factory:
FactoryGirl.define do
factory :user do |f|
f.email { Faker::Internet.email }
f.password "secret"
f.role 1
end
end
I understand why this error arrises, and have tried various ways round it, but don't have a good enough understanding of factories to crack it. Can you help?
So the problem was related to not using Factory Girl when creating the project. It should have been:
describe Ability do
context "user is investigator" do
it "user can only destroy projects which he owns" do
user = FactoryGirl.create(:user)
ability = Ability.new(user)
ability.should be_able_to(:destroy, FactoryGirl.create(:project, :user => user))
end
end
end

Trouble on initializing a factory object

I am using Ruby on Rails 3.1.0, rspec-rails 2 and Factory gems. I have some trouble related to the validation process when I state a Factory object for an Account class.
In the model file I have:
class Account < ActiveRecord::Base
belongs_to :user
attr_accessible :name, ..., :password
validates :name,
:presence => true
...
validates :password,
:presence => true
end
In the factory file I have:
FactoryGirl.define do
factory :account, do
sequence(:name) { |n| "Foo #{n}"}
...
password 'psw_secret'
association :user
end
factory :user do
auth 'registered'
end
end
When in the spec file I state let!(:account) { Factory(:account) } it works as expected but when I use the following:
let!(:user) { Factory(:user, :account => Factory(:account)) }
I get this error:
Failure/Error: let!(:user) { Factory(:user, :account => Factory(:account)) }
ActiveRecord::RecordInvalid:
Validation failed: Account password can not be blank, Account is invalid
Why I get that error? How can I solve the problem?
I think you should do it the other way around:
#user = Factory(:user)
#account = Factory(:account, :user => #user)
The relation is defined on account, not on user.
Hope this helps.

Resources