Rails enumerables: why would find_all{...}.count return a different value than count{...}?

StackOverflow https://stackoverflow.com/questions/21341660

  •  02-10-2022
  •  | 
  •  

Domanda

Background

I'm using

> ruby -v
ruby 2.1.0p0 (2013-12-25 revision 44422) [x86_64-darwin12.0]

> rails -v
Rails 4.0.2

So normally,

> [1,2,3,4,5].find_all{|x| x == 4}.count

and

> [1,2,3,4,5].count{|x| x == 4}

give the same value:

=> 1

...all good.

Problem

But in my app, something's wrong. When I put in a breakpoint (using pry), I notice that I'm getting an inconsistency:

(don't worry too much about the particular data structures here)

> Meme.find(24).punches.count{|punch| punch.new_to_user?(User.find(14))}
=> 6

Whereas:

> Meme.find(24).punches.find_all{|punch| punch.new_to_user?(User.find(14))}.count
=> 0

Why is it doing this?

6 != 0, amirite? It seems from the http://ruby-doc.org/core-2.1.0/Enumerable.html documentation that ruby 2.1.0 should treat these two cases identically.

When I look at what these commands execute, it's clear that .count{} isn't really evaluating the code inside its block:

> Meme.find(24).punches.count{|punch| punch.new_to_user?(User.find(14))}
 CACHE (0.0ms)  SELECT "memes".* FROM "memes" WHERE "memes"."id" = $1 LIMIT 1  [["id", 24]]
 CACHE (0.0ms)  SELECT COUNT(*) FROM "punches" WHERE "punches"."meme_id" = $1  [["meme_id", 24]]
=> 6

As opposed to the (I think) correct behavior of find_all:

> Meme.find(24).punches.find_all{|punch| punch.new_to_user?(User.find(14))}.count
 CACHE (0.0ms)  SELECT "memes".* FROM "memes" WHERE "memes"."id" = $1 LIMIT 1  [["id", 24]]
 CACHE (0.0ms)  SELECT "punches".* FROM "punches" WHERE "punches"."meme_id" = $1  [["meme_id", 24]]
 CACHE (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", 14]]
 CACHE (0.2ms)  SELECT "votes".* FROM "votes" INNER JOIN "vote_decisions" ON "votes"."vote_decision_id" = "vote_decisions"."id" WHERE "vote_decisions"."user_id" = $1 AND "votes"."punch_id" = 531  [["user_id", 14]]
 CACHE (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", 14]]
 CACHE (0.0ms)  SELECT "votes".* FROM "votes" INNER JOIN "vote_decisions" ON "votes"."vote_decision_id" = "vote_decisions"."id" WHERE "vote_decisions"."user_id" = $1 AND "votes"."punch_id" = 532  [["user_id", 14]]
 CACHE (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", 14]]
 CACHE (0.0ms)  SELECT "votes".* FROM "votes" INNER JOIN "vote_decisions" ON "votes"."vote_decision_id" = "vote_decisions"."id" WHERE "vote_decisions"."user_id" = $1 AND "votes"."punch_id" = 533  [["user_id", 14]]
 CACHE (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", 14]]
 CACHE (0.0ms)  SELECT "votes".* FROM "votes" INNER JOIN "vote_decisions" ON "votes"."vote_decision_id" = "vote_decisions"."id" WHERE "vote_decisions"."user_id" = $1 AND "votes"."punch_id" = 534  [["user_id", 14]]
 CACHE (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", 14]]
 CACHE (0.0ms)  SELECT "votes".* FROM "votes" INNER JOIN "vote_decisions" ON "votes"."vote_decision_id" = "vote_decisions"."id" WHERE "vote_decisions"."user_id" = $1 AND "votes"."punch_id" = 535  [["user_id", 14]]
 CACHE (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1  [["id", 14]]
 CACHE (0.0ms)  SELECT "votes".* FROM "votes" INNER JOIN "vote_decisions" ON "votes"."vote_decision_id" = "vote_decisions"."id" WHERE "vote_decisions"."user_id" = $1 AND "votes"."punch_id" = 536  [["user_id", 14]]
=> 0

Possible answers

  • Does the version of ruby or rails I'm using not support this use of count{block}? I have been using the ruby 2.1.0 doc http://ruby-doc.org/core-2.1.0/Enumerable.html as a reference.

  • Is the version my app is using, or pry is using, different from the 2.1.0/4.0.2 that I expect? FWIW, in my Gemfile I have

    source 'https://rubygems.org'
    ruby "2.1.0"
    gem 'rails', '4.0.2'
    
  • The cacheing? I don't understand this at all.

Thanks!

Edit:

To clarify, new_to_user? does a bit of work with other ActiveRecords. That's why I say the find_all behavior seems correct. count{} seems to be running a simple SQL COUNT command, which is wrong for my purposes (but may be right for the version of ruby, for reasons I don't understand)

È stato utile?

Soluzione

Meme.find(24).punches doesn't return an array. It returns an ActiveRecord::Relation that generally behaves like an array, but it has some different properties.

When you invoke #count on the relation, the ActiveRecord association #count method is executed, and not the Enumerable #count. This means that Meme.find(24).punches.count is supposed to SQL count and return the number of punches for the meme, regardless the block (which is ignored in this case).

If you want to achieve the same result, you first need to convert the association into an Array.

Meme.find(24).punches.to_a.count{|punch| punch.new_to_user?(User.find(14))}

Altri suggerimenti

find_all from enumerable is not the same method as find_all from ActiveRecord.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top