Skip to content

Instantly share code, notes, and snippets.

@jdickey
Created April 15, 2013 04:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jdickey/5385727 to your computer and use it in GitHub Desktop.
Save jdickey/5385727 to your computer and use it in GitHub Desktop.
Deeply confused by a return value from Ruby's `array#count` method that counts the number of items for which a block returns true, where counts with opposing conditions return identical results. WtF?

From a pry session attempting to figure out why Array#count with a block doesn't return the expected value:

[16] pry(#<Article>)> self.contributions
[
    [0] #<Contribution:0x007fbfa8801e10> {
                      :id => 2,
              :article_id => 2,
          :contributor_id => 1,
        :original_content => "Ut enim",
          :original_start => 0,
              :created_at => Mon, 15 Apr 2013 04:16:08 UTC +00:00,
              :updated_at => Mon, 15 Apr 2013 04:16:08 UTC +00:00,
            :contrib_type => "lock",
         :original_length => 7,
          :submit_comment => nil,
            :related_text => nil
    }
]
[17] pry(#<Article>)> self.contributions.count
1
[18] pry(#<Article>)> self.contributions.count {|item| item[:contrib_type] == 'lock'}
1
[19] pry(#<Article>)> self.contributions.count {|item| item[:contrib_type] != 'lock'}
1

Note that self.contributions contains a single record, with an id value of 2 and a contrib_type value of lock. Fine. The call to self.contributions.count at [17] gives the expected (and correct) 1.

But then we go into the weeds, and stay there. The Ruby documentation for Array#count states that if a block is given, the method "counts the number of elements yielding a true value." If that's true, then why does the call at [19] give a value of 1? Even if that were correct, why would the call at [18], testing the exact opposite condition, also return 1?

If someone can point out the error of my ways, I'd greatly appreciate it...

@malclocke
Copy link

Assuming this is Rails, it's probably not an Array but an ActiveRecord association instance.

In this case, the count method just calls an SQL query to perform the account and ignores the block. It's not the same as Array#count.

@jdickey
Copy link
Author

jdickey commented Apr 15, 2013

Of course. It's way too easy to forget when you're looking at an emulation of something that's good enough to lull you into a sense of familiarity but not quite complete enough for that familiarity to be warranted. Ack.

Except that it is an Array.

[20] pry(#<Article>)> self.contributions
[
    [0] #<Contribution:0x007fbfa8801e10> {
                      :id => 2,
              :article_id => 2,
          :contributor_id => 1,
        :original_content => "Ut enim",
          :original_start => 0,
              :created_at => Mon, 15 Apr 2013 04:16:08 UTC +00:00,
              :updated_at => Mon, 15 Apr 2013 04:16:08 UTC +00:00,
            :contrib_type => "lock",
         :original_length => 7,
          :submit_comment => nil,
            :related_text => nil
    }
]
[21] pry(#<Article>)> self.contributions.class
Array < Object
[22] pry(#<Article>)>

Oh, wait. It is, but the array is just a copy of the actual data.

[22] pry(#<Article>)> show-method self.contributions

From: /Users/jeffdickey/src/rails/beta1/vendor/ruby/1.9.1/gems/activerecord-3.2.12/lib/active_record/associations/builder/association.rb @ line 43:
Owner: Article::GeneratedFeatureMethods
Visibility: public
Number of lines: 3

mixin.redefine_method(name) do |*params|
  association(name).reader(*params)
end

So what I guess I need to do is copy the return value from #contributions to a local variable, which would be an actual Array, do the call against that, and then use #contributions= to write back the array if it were modified. That explains this and a few other bits of weirdness.

Thanks again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment