RSpec not recognizing deleted association - ruby-on-rails

In my rails application, a user has many baskets, and a basket belongs to a user. The User class also contains an instance method that removes the user's id from all associated baskets. I am having issues testing that method using RSpec, FactoryGirl and Faker.
class Basket
belongs_to :user, optional: true
end
class User
has_many: baskets
def disassociate_baskets
baskets.each { |b| b.update(user: nil) }
end
end
The disassociate_baskets method works with real data, my RSpec test fails because in the test environment because the basket-user association persists even after running the method. Here's my testing code:
#Basket Factory
FactoryGirl.define do
factory :basket do
date { Faker::Date.backward }
end
end
#User Factory
FactoryGirl.define do
factory :user do
name { Faker::Name.name }
end
end
#user_spec.rb
describe "When deleting purchase history" do
before do
#user = create(:user)
#user1 = create(:user)
#basket = create(:basket, user: #user)
#basket1 = create(:basket, user: #user)
#basket2 = create(:basket, user: #user1)
end
it "disassociates the user from the basket" do
#user.disassociate_baskets
expect(#basket.user).to eq nil
end
end
I've inspected the objects inside the it block, and they are fine, but #basket is still associated to #user even after running disassociate_baskets method, so my test fails. What am I missing here?

Possible solution:
expect(#basket.reload.user).to eq nil
Why you need to reload it:
When you create the new basket and assign it to #basket, it will have an associated User assigned to it. Then, you call #user.disassociate_baskets, which loops over all the baskets of that user by instantiating them one by one, and updating their user attribute.
But since #user.disassociate_baskets instantiated brand new Basket instances, the user attribute will be set to nil only in these new instances. #basket has no idea that another instance got modified, and has to be reloaded manually.

Related

Attach Account owner_id to record after create Rails 5

Im building a Rails 5 App with Ruby 2.4.0
I am creating the Account creation page in which a user creates their account and user in the same form. What I am looking to do is attach the "owner_id" owner being a class of User. the relationships are all working as when the account is created it assigns the account_id to the owner, however it is not assigning the owner_id to the account. When I create the new account and user, I can call them both in the rails console a = Account.last (displays the Account details but owner_id is set to nil) and when I run a.owner in the console it dose display the account_id as 1 which is correct.)
I am trying the following call back to set the owner_id to the account after create:
Account.rb (model)
after_create :attach_owner_to_account, if: :new_record?
def attach_owner_to_account
self.owner_id = #account.owner.id
end
I have also tried the following in the accounts_controller create action:
def create
#account = Account.new(account_params)
if #account.valid?
#account.owner_id = #account.owner.id
#account.owner.role = 1
#account.save
redirect_to root_path, notice: 'Company subdomain created successfully.'
else
render action: 'new', alert: 'There was a problem. Please try again.'
end
end
my relationships are as follows:
Account.rb
has_one :owner, class_name: 'User'
has_many :users
User.rb
belongs_to :account
Any assistance here would be greatly appreciated!
#account is an instance method belonging to the instance of the controller class.
#account (when used in User) is an instance method of the user object. It's a completely different field and in the user object will have a value of 'nil' unless it's assigned a value somewhere in the user object's methods.
The default key for the relationships will be account_id in the user instance. Provided that it exists and has the account's id, then my_account.owner will automatically work by building the appropriate query to retrieve the users table record with the account_id = (my_account.id)
In summary, you don't need an owner_id. You can just remove it via a migration. A has_one relationship automatically uses the account_id in the target class to find the associated record.

How to test if other models were created correctly

I have a Account.create! method that creates multiple other models.
In my RSpec test how can I test if the other models were created correctly?
it "should create an account" do
params = ....
Account.create!(params)
expect(account.valid?).to eq(true)
end
My Account.create! method looks like:
def self.create!(params)
account = Account.new(params)
user = ...
user.save!
location = ...
location.save!
account
end
For one thing, you can test that the records were created at all.
it "should create a user" do
params = ....
expect(Account.create!(params)).to change{User.count}.by(1)
end
Also, you can use User.last to retrieve the last user created and check the attributes are what you would want to see.

Why must I reload records in rspec after using customer validators?

I've got a model User that has options created in a callback after it is created
# User
has_one :user_options
after_create :create_options
private
def create_options
UserOptions.create(user: self)
end
I have some simple Rspec coverage for this:
describe "new user" do
it "creates user_options after the user is created" do
user = create(:user)
user.user_options.should be_kind_of(UserOptions)
end
end
Everything worked until I added custom validation to the User model.
validate :check_whatever, if: :blah_blah
Now the spec fails and the only way to make it pass is to reload the record in the spec:
it "creates user_preferences for the user" do
user = create(:user)
user.reload
user.user_options.should be_kind_of(UserOptions)
end
What is the reason for this?
First of all I would recommend reading this article about debugging rails applications: http://nofail.de/2013/10/debugging-rails-applications-in-development/
Secondly I would propose some changes to your code:
def create_options
UserOptions.create(user: self)
end
should be
def create_options
self.user_option.create
end
that way you don't have to reload an object after save, because the object already has the new UserOptions entity in it's relation.
Assuming from the code create(:user) you are using fixtures. There might be a problem with the data that you are using the in the user.yml and the validation that you wrote, but unfortunately did not post here.

Set current_user in test

I have a test that looks like this:
test "should get create" do
current_user = FactoryGirl.build(:user, email: 'not_saved_email#example.com')
assert_difference('Inquiry.count') do
post :create, FactoryGirl.build(:inquiry)
end
assert_not_nil assigns(:inquiry)
assert_response :redirect
end
That's testing this part of the controller:
def create
#inquiry = Inquiry.new(params[:inquiry])
#inquiry.user_id = current_user.id
if #inquiry.save
flash[:success] = "Inquiry Saved"
redirect_to root_path
else
render 'new'
end
end
and the factory:
FactoryGirl.define do
factory :inquiry do
product_id 2
description 'I have a question about....'
end
end
but I keep getting errors in my tests:
1) Error:
test_should_get_create(InquiriesControllerTest):
RuntimeError: Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id
What am I doing wrong? I need to set the current_user, and I believe I am in the test, but obviously, that's not working.
You didn't create current_user. It was initialized only in test block.
There are two differents ways to do it:
First, use devise test helpers. Something like that
let(:curr_user) { FactoryGirl.create(:user, ...attrs...) }
sign_in curr_user
devise doc
Second, you can stub current_user method in your controllers for test env
controller.stub(current_user: FactroryGirl.create(:user, ...attrs...))
And you should use FactoryGirld.create(...) instead of FactoryGirl.build(...), because you factory objects have to be persisted.(be saved in db and has id attribute not nil)
There are several things which come to mind:
FactoryGirl.build(:user, ...) returns unsaved instance of a user. I'd suggest to use Factory.create instead of it, because with unsaved instance there's no id and there's no way for (usually session based) current_user getter to load it from database. If you're using Devise, you should "sign in" user after creating it. This includes saving record in DB and putting reference to it into session. See devise wiki
Also, passing ActiveRecord object to create action like this looks weird to me:
post :create, FactoryGirl.build(:inquiry)
Maybe there's some rails magic in play which recognizes your intent, but I'd suggest doing it explicitly:
post :create, :inquiry => FactoryGirl.build(:inquiry).attributes
or better yet, decouple it from factory (DRY and aesthetic principles in test code differ from application code):
post :create, :inquiry => {product_id: '2', description: 'I have a question about....'}
This references product with id = 2, unless your DB doesn't have FK reference constraints, product instance may need to be present in DB before action fires.

undefined method `stringify_keys' while using Factory Girl

I have the following block of code in my User_spec.rb:
#user = { username:'newuser',
email:'new#user.com',
fname:'new',
lname:'user',
password:'userpw',
password_confirmation:'userpw'}
for creating a using using these attributes. However while I moved all these attributes to Factories.rb:
require 'factory_girl'
Factory.define :user do |u|
u.username 'newuser'
u.email 'new#user.com'
u.fname 'new'
u.lname 'user'
u.password 'newuserpw'
u.password_confirmation 'newuserpw'
end
and replace the line in user_spec.rb with:
#user = Factory(:user)
all my tests that related to the User model failed(such as tests for email, password, username etc), all were giving me
"undefined method `stringify_keys' for…"
the new user object
I had a similar problem, and it was because I was passing a FactoryGirl object to the ActiveRecord create/new method (whoops!). It looks like you are doing the same thing here.
The first/top #user you have listed is a hash of values, but the second/bottom #user is an instance of your User ojbect (built by FactoryGirl on the fly).
If you are calling something like this in your specs:
user = User.new(#user)
The first (hashed) version of #user will work, but the second (objectified) version will not work (and throw you the 'stringify_keys' error). To use the second version of #user properly, you should have this in your specs:
user = Factory(:user)
Hope that helps.
We need to see an example of a failing test to diagnose, but here is one thing that can cause it – sending an object when attributes are required. I once fixed one of my failing tests by changing:
post :create, organization: #organization
to
post :create, organization: #organization.attributes
#rowanu Answered your question, but let me layout my example too for future reference:
What was failing in my case was:
#user = User.new user_attr
#user.bookings_build(Booking.new booking_attr)
Note that I am trying to build with a booking instance and not hash of attributes
The working example:
user_attr_hash = FactoryGirl.attributes_for(:user)
booking_attr_hash = FactoryGirl.attributes_for(:booking)
#user = User.new user_attr_hash
#user.bookings.build(booking_attr_hash)
And in spec/factories/domain_factory.rb I have
FactoryGirl.define do
factory :user do
# DEFAULT USER...
password "123123123"
email "factory_girl#aaa.aaa"
# there rest of attributes set...
end
factory :booking do
start_date Date.today
end_date Date.today+3
# the rest of attributes
end
end

Resources