Same method returns different results depending on how it's called

31 views
Skip to first unread message

IAmNaN

unread,
Apr 24, 2012, 6:41:01 AM4/24/12
to factor...@googlegroups.com
When I make an object with FactoryGirl, I can use a setter method on it, but not through an association.

Assume Task has_many Assignments, and Assignment belongs_to a Task. Then the following works in the console but not in the spec.

  it 'does something useful' do
    task = FactoryGirl.create(:task)
    task.assignments.count.should eq 1
    task.assignments.first.status.should eq 0
    task.assignments.first.status = 1
    task.assignments.first.status.should eq 1  # Fails with "expected: 1, got: 0"

    # However....
    a = task.assignments.first
    a.should eq task.assignments.first
    a.status = 1
    a.status.should eq 1                       # Passes!!!
  end

Why would the one line pass and the last line fail? And why doesn't this happen in the console?

The factory has a slight twist because of the one-to-many relationship:

  factory :task do
    name "Task name"
    after_create do |task, eval|
      FactoryGirl.create_list(:assignment, 1, task: task)
    end
  end

  factory :assignment do
    name "Assignment name"
    status_id 0
  end

Any clues would be greatly appreciated.

My gems: FactoryGirl 3.1.1, FactoryGirlRails 3.1.0, Rails 3.2.3, Ruby 1.9.3, Rspec 2.9.0, Capybara 1.1.2

Josh Clayton

unread,
Apr 24, 2012, 11:57:35 PM4/24/12
to factory_girl
This has nothing to do with FactoryGirl and everything to do with
ActiveRecord.

When you assign task.assignments.first to a local variable, and then
set status on that variable. you're mutating an instance of that
object. Every time you call task.assignments.first, you'll get a
different object; try running tasks.assignments.first.object_id ==
task.assignments.first.object_id. They're different! What does this
mean?

It means that if you mutate status on task.assignments.first (without
storing it to a local variable), you're mutating that instance without
saving it to the database. That's why, when you access it again, the
status is incorrect; you mutated a different instance, even though the
DATA itself (yes, including its primary key, id) is the exact same.

If you were to try this *without* FactoryGirl, you'd see the same
result - modifying the result of task.assignments.first (without
assigning it to a local variable first) won't actually change the
data, and when it's called again, you'll get a brand new instance from
the DB (without any of your previous mutation).

On Apr 24, 5:41 am, IAmNaN <dger...@gmail.com> wrote:
> When I make an object with FactoryGirl, I can use a setter method on it,
> but not through an association.
>
> Assume Task has_many Assignments, and Assignment belongs_to a Task. Then
> the following works in the console but not in the spec.
>
>   it 'does something useful' do
>     task = FactoryGirl.create(:task)
>     task.assignments.count.should eq 1
>     task.assignments.first.status.should eq 0
>     task.assignments.first.status = 1
>     task.assignments.first.status.should eq 1  *# Fails with "expected: 1,
> got: 0"*
> *
> *
> *    **# However....*
>     a = task.assignments.first
>     a.should eq task.assignments.first
>     a.status = 1
>     a.status.should eq 1                       *# Passes!!!*

IAmNaN

unread,
Apr 26, 2012, 1:53:09 AM4/26/12
to factor...@googlegroups.com
Josh, thanks for the explanation. I started to figure that out on my own and posed the question Rails-talk and got a similar answer. Your answer is more thorough. I understand why it's happening. I was thrown off by the fact that ActiveRecord's assignment operator and first method are behaving different from the same in ruby's Hash and Array classes, which is to say atomically. I wrongly assumed that assignments would have been cached to an array of Assignment models, and first was an Array method, not a new query. I've been developing in RoR since 2006, and this is the first time I've encountered this gotcha. Gaps....

Since my message didn't show up on this forum (I checked a few times) I couldn't remove it. Sorry for the static.

Thanks again. And thanks for you effort on the deprecation warning too.
Reply all
Reply to author
Forward
0 new messages