Alec Wenzowski
Alec Wenzowski

Reputation: 3908

Why does the "each" iterator method break rspec?

Background

I'm attempting to test my models.

app/models/user.rb

class User < ActiveRecord::Base

  has_many :payor_transactions, class_name: 'Transaction', inverse_of: :payor, foreign_key: :payor_id
  has_many :payee_transactions, class_name: 'Transaction', inverse_of: :payee, foreign_key: :payee_id

  def transactions
    transactions = Transaction.where(["payor_id=? OR payee_id=?", self.id, self.id])
    transactions
  end

end

app/models/transaction.rb

class Transaction < ActiveRecord::Base

  attr_accessor :user

  belongs_to :payor, class_name: 'User'
  belongs_to :payee, class_name: 'User'

end

In the Transactions class, @user is an ephemeral object instance representing the user accessing the model.

spec/models/user_spec.rb

require 'spec_helper'

describe User do

  let(:user) { Factory(:user) }
  let(:user2) { Factory(:user) }
  let(:user3) { Factory(:user) }

  let(:transaction_user_user2) { Factory(:transaction, payor: user, payee: user2) }
  let(:transaction_user2_user) { Factory(:transaction, payor: user2, payee: user) }
  let(:transaction_user2_user3) { Factory(:transaction, payor: user2, payee: user3) }

  describe ".transactions" do
    it "should include payor and payee transactions but not 3rd party transactions" do
      user.transactions.should == [transaction_user_user2, transaction_user2_user]
      user2.transactions.should == [transaction_user_user2, transaction_user2_user, transaction_user2_user3]
      user3.transactions.should == [transaction_user2_user3]
    end
  end

end

Using rspec 2.6.4, factory_girl 2.1.2, rails 3.1.0, ruby 1.9.2p290. As shown, the spec passes.

Problem

When I modify the transactions method in app/models/user.rb to iterate over the results such that it reads:

class User < ActiveRecord::Base

  has_many :payor_transactions, class_name: 'Transaction', inverse_of: :payor, foreign_key: :payor_id
  has_many :payee_transactions, class_name: 'Transaction', inverse_of: :payee, foreign_key: :payee_id

  def transactions
    transactions = Transaction.where(["payor_id=? OR payee_id=?", self.id, self.id])
    transactions.each {|transaction| transaction.user = self}
    transactions
  end

end

the method transactions now returns [] in rspec, however it works perfectly in the app views.

Since Transaction.user is ephemeral (representing the user accessing the transaction) it must be set (if it exists) every time a Transaction is initialized or built from db records.

I'm at a loss for where to begin to debug this.

All suggestions appreciated!

Upvotes: 1

Views: 715

Answers (3)

Alec Wenzowski
Alec Wenzowski

Reputation: 3908

Following the suggestion from @obrok, the solution I settled on to retain the advantage of lazy-loading let in other tests was to touch each transaction before testing User#transactions as so:

describe ".transactions" do
  it "should include payor and payee transactions but not 3rd party transactions" do

    [transaction_user_user2, transaction_user2_user, transaction_user2_user3].each do |transaction|
      [transaction.payor_id, transaction.payee_id].each {|id| id.should_not be_nil }
    end

    user.transactions.should == [transaction_user_user2, transaction_user2_user]
    user2.transactions.should == [transaction_user_user2, transaction_user2_user, transaction_user2_user3]
    user3.transactions.should == [transaction_user2_user3]
  end
end

Upvotes: 0

Jamie Penney
Jamie Penney

Reputation: 9522

Couldn't you just return payor_transactions + payee_transactions instead of manually selecting them?

Upvotes: 1

Paweł Obrok
Paweł Obrok

Reputation: 23164

I think your problem lies in the fact that let is lazy. Basically what is happening is that the transactions are not even created yet when the transactions method is called in the test. Use let! for a non-lazy version. See let and let! for more details.

Upvotes: 3

Related Questions