0

I have this class

class Invoice < ActiveRecord::Base
    scope :last_with_number, -> (firm) {firm.invoices.where('number IS NOT NULL').order("number ASC").last}
end

When I run Invoice.last_with_number(Firm.first).number in the console, I get this error

2.0.0p247 :001 > Invoice.last_with_number(Firm.first).number
  Firm Load (1.5ms)  SELECT "firms".* FROM "firms" ORDER BY "firms"."id" ASC LIMIT 1
  Invoice Load (3.1ms)  SELECT "invoices".* FROM "invoices" WHERE "invoices"."firm_id" = $1 AND (number IS NOT NULL) ORDER BY number DESC LIMIT 1  [["firm_id", 1]]
NoMethodError:   Invoice Load (0.7ms)  SELECT "invoices".* FROM "invoices"
undefined method `number' for #<ActiveRecord::Relation::ActiveRecord_Relation_Invoice:0x00000008793198>

But this spec passes

 describe Invoice do
    let(:firm)     {FactoryGirl.create(:firm)}
    let(:invoice1)   {FactoryGirl.create(:invoice, customer:customer, firm:firm , number: 2)}
    let(:invoice2)   {FactoryGirl.create(:invoice, customer:customer, firm:firm , number:3)}

    it 'gets the last numberd invoice for spesific firm' do 
        invoice1
        invoice2
        Invoice.last_with_number(firm).number.should eq 3
    end
 end

I can solve this problem by making a Invoice class method like this

def self.the_really_last(firm)
   last_with_number(firm).last
end

And change the scope from

scope :last_with_number, -> (firm) {firm.invoices.where('number IS NOT NULL').order("number ASC").last}

to

scope :last_with_number, -> (firm) {firm.invoices.where('number IS NOT NULL').order("number ASC")}

This will work both places.

But, why is the scope behaving differently in the the spec and the console?

Andreas Lyngstad
  • 4,887
  • 2
  • 36
  • 69
  • Is it possible you have other typos that could explain this? In any event, the console is showing the correct behavior. I would do a `puts Invoice.last_with_number(firm)` inside your spec to see what kind of object you're getting and why the `number` method is present. If it's not a `Relation`, then I would double check your scope definition. – Peter Alfvin Feb 27 '14 at 17:37
  • To isolate a bit: when I run the specs with `.last` at the end of the scope it passes. If I remove last and run specs I get `undefined method 'number' for #`. The issue here is that a scope should return a ActiveRecord::Relation. But, it does not when I run the the spec. Why? – Andreas Lyngstad Feb 27 '14 at 21:08
  • Sorry, I didn't scroll your scope definition and missed the `.last` on the end. So, the mystery is not why your spec works, the mystery is why the console fails. I noticed there is an extra SQL query of `select * from invoices` logged to your terminal. If you don't add the `.last` from the console with your original scope definition (which includes the `.last) what does it return? – Peter Alfvin Feb 27 '14 at 22:21
  • To put `.last` in a scope is actually not what its intended for. A scope should return a `ActiveRecord::Relation` not a instance. The behavior in the console is thus correct. It's the spec that's make's it behave funny. – Andreas Lyngstad Feb 27 '14 at 22:38
  • That may be true, but defining a scope is the same as defining a class method, as stated explicitly in http://guides.rubyonrails.org/active_record_querying.html#scopes, so the `.last` _is_ getting called and that scope should return an instance of `Invoice`. Again, I suggest you execute just `Invoice.last_with_number(Firm.first)` from the console and see what it returns. – Peter Alfvin Feb 27 '14 at 23:09
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/48661/discussion-between-andreas-lyngstad-and-peter-alfvin) – Andreas Lyngstad Feb 28 '14 at 09:31

1 Answers1

0

I found out what it does. It happends when all records have number = nil. If you put last at the end of the scope it returns a ActiveRecord::Relation with all records. I made this repo to illustrate

This rails issue gets a good explanation

To avoid confusion never use last first or all in scopes

Andreas Lyngstad
  • 4,887
  • 2
  • 36
  • 69