unit test fails, but shouldnt have

Hi,

I have a one to many relationship ( stories & votes ), that i wish to test via a unit test. In development everything works great, votes ends up w/ the correct story_id and the relationship is great ( i can Vote.find(:last).story or Story.find(:last).votes ) But when I run the unit test, it fails. The story gets an id of 996332877 ( which is 10000 in dev ) and the votes story_id is 0. Not sure what the heck is going on here. Any help would be greatly appreciated.

Merrick

stories:

Merrick Johnson wrote:

  has_many :votes do     def latest       find :all, :order => 'id DESC', :limit => 3     end   end

Nice! Could someone hint what that syntax does? (But I don't think it's causing the problem...

vote test:

Next time, write the test first! You should have developed all those model-level features entirely in test, using the tests essentially the same way - to feel around and sense changes as you grew the code - as you used your Rails app in development mode.

But enough of the lecture...

  test "story_association" do     assert_equal stories(:two), votes(:two).story #FAILS!   end

What is in your test/fixtures/stories.yml and votes.yml files?

What does the assert_equal spew forth when it fails?

Good call Philip, turns out my votes.yml fixture looked like this:

one:   story_id: one

two:   story_id: one

when it should have looked like this:

one:   story: one

two:   story: one

Waalaa Thanks

Phlip wrote:

Merrick Johnson wrote:

  has_many :votes do     def latest       find :all, :order => 'id DESC', :limit => 3     end   end

Nice! Could someone hint what that syntax does? (But I don't think it's causing the problem...

this creates a method for the resource. in this case it returns the newest 3 records. This is pretty helpful because i can call this from a view or partial ( @story.votes.latest ) and just get the newest three votes. This should work in any DB ( im using Oracle11G )

Merrick Johnson wrote:

one:   story: one

two:   story: one

Ahem. Now for some more lectures!

Your test data fixtures should be literate, so you can document your business rules in your test cases. In other words, your fixtures should not just say "one two" etc. They should, instead, tell a ... story.

(-:

BTW I guessed what this does:

> has_many :votes do > def latest > find :all, :order => 'id DESC', :limit => 3 > end > end

It lets you say a_story.votes.latest and call that find. I can't wait to use that utterly kewl feature!

Merrick Johnson wrote:

this creates a method for the resource. in this case it returns the newest 3 records.

Yup!

> This is pretty helpful because i can call this from a

view or partial ( @story.votes.latest ) and just get the newest three votes. This should work in any DB ( im using Oracle11G )

And you never need to say that about MVC or ActiveRecord. The point is you write the minimum code, all in the right places, to support the maximum features. You don't have to do it the old-fashioned way, with code scattered everywhere, all very similar, getting in the way of each other!

Now this is why that new aggregation notation is so dang cool. Here's the Rails 1 way to do a bunch of links:

   class Customer      has_many :accounts      has_many :accounts_cc, :class_name => 'Account',                     :conditions => { :kind => 'cc' }      has_many :accounts_ach, :class_name => 'Account',                     :conditions => { :kind => 'ach' }    end

Now compared to raw SQL, has_many is already an order of magnitude more DRY (Don't Repeat Yourself). Yet the one requirement to distinguish Credit Card from Checking accounts forces us to create three very wet has_many calls on the same model. They are, once again, irritatingly similar.

Here's the (apparent!) Rails 2 fix:

   class Customer      has_many :accounts do        def kind(k)   find :all, :conditions => { :kind => k }        end      end    end

Boom done. You can write customer.accounts, customer.accounts.kind('cc'), and customer.accounts.kind('ach'), all very similar to what you could write before, but with far fewer executable lines.

I'm stoked because we just spent 3 days refactoring a big system which needed this exact fix, and I didn't know about it. Now I get to install it on Monday. Merry Xmas to me, huh?!

Merrick Johnson wrote:

  test "votes_association_improved" do     assert stories(:one).votes.include? votes(:one)     assert stories(:one).votes.include? votes(:two)   end

You need .to_set, and I will recommend a way to unify assert() and assert_equal() into an assertion that reflects all its variables and values when it fails.

   gem install assert2

   require 'assert2'

   assert{ stories(:one).votes.to_set == votes(:one, :two).to_set }

.to_set converts an ordered array into an unordered group, so == can't see the order and can't complain.

(But don't use assert{ 2.0 } if you use Ruby 1.8.7! A fix for Ruby 1.9 is in the works...)

Merrick Johnson wrote:

      find :all, :order => 'id DESC', :limit => 3

Note that, in a normal database - and in a unit test that generates new records - the 'id' should increment monotonically as new records get added. So 'latest' is always the highest 'id'. (Note I have no idea if databases must eternally enforce this rule, but it stands to reason...)

However, a modern Rails fixture file uses a "magic 'id' system" that builds 'id's out of the hash of each record's fixture name. So the 'id's are not monotonic, and these problems illustrate you ought to use a timestamp, such as 'created_at', instead of an 'id', to find the latest records!

This means, instead of using .to_set in the test, you should actually pin down the 'created_at' times in your fixture files. And this reminds us, again, to use the fixtures to tell a little story. Suggest one record is "2.hours.ago", and another is "5.minutes.ago", for example!