Skip to content


Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP


Make AR::Relation include Enumerable #8794

wants to merge 1 commit into from

8 participants


In this way Relation will gain more Array behaviors (like as_json method that is no longer required in Relation).
Also it delegates every method to to_a without method_missing which is faster.

A few tests need a change because collect without a block doesn't load the relation anymore. This seems ok.


I'm worried about the order of the include here for Enumerable. Wouldn't this override certain methods, such as count from ActiveRecord::Calculations? It would delegate to to_a and the records would be loaded instead of just querying for the count. Not sure if there are other cases like this, just putting this out there. Otherwise I think this makes sense.

Edit: just thought of others as well. first, last, select, find


@luke-gru Enumerable goes below any other module included in relation in hierarchy, so a method from any other module including Calculations and many others will be called before.


oh, right. My bad!


Any news from about this?

@guiocavalcanti guiocavalcanti referenced this pull request in apotonick/roar-rails

Fail to evaluate ActiveRecord::Relation #40


This totally makes sense and allows us to detect collections easily.


@jonleighton @tenderlove what do you think?


In the past we made methods like select behave differently depending on whether or not they were passed a block. This turned out to cause confusion, so we removed that. I believe select now raises an error when passed a block. Basicalley I don't think Relation can be transparently treated as enumerable, so I don't think it should include Enumerable.


Agree with @jonleighton

@tenderlove tenderlove closed this

It is not raising anything when block is passed:

As I remember "Make Relation behave like Enumerable" started from the fact that in previous versions of Rails association was magically showing itself as array:

User.has_many :projects
User.first.projects.class # => Array

Maybe that was a confusion mentioned by @jonleighton, but there is right idea behind it:
Anyway people expecting associations and relations to behave like Array even if it never was it
I think we should give people what they want instead of teaching them that they have to do instead.

My personal concern behind this patch (that supposed to be next step) was ability to embed batches into relation's each method:
Long running Relation#each can appear in places where you didn't expect it initially. Like one object appeared with 10k+ records in has_many relation while the average is below 10.
That is why random memory usage tsunamis appear.

Making Relation to include Enumerable would introduce single point of control for each method. That should make each to use batches by default an easy feature.
It also would be super-cool to forget about batches and make their usage completely automatic.


We already discussed this a lot and we will stand with this decision. Relations are not Arrays even if they have some methods in common.

@bogdan bogdan deleted the branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 7, 2013
  1. @bogdan
This page is out of date. Refresh to see the latest.
10 activerecord/lib/active_record/relation.rb
@@ -14,7 +14,7 @@ class Relation
- include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
+ include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation, ::Enumerable
attr_reader :table, :klass, :loaded
attr_accessor :default_scoped
@@ -198,10 +198,6 @@ def to_a
- def as_json(options = nil) #:nodoc:
- to_a.as_json(options)
- end
# Returns size of the records.
def size
loaded? ? @records.length : count
@@ -218,7 +214,7 @@ def empty?
# Returns true if there are any records.
def any?
if block_given?
- to_a.any? { |*block_args| yield(*block_args) }
+ super
@@ -227,7 +223,7 @@ def any?
# Returns true if there is more than one record.
def many?
if block_given?
- to_a.many? { |*block_args| yield(*block_args) }
+ super
limit_value ? to_a.many? : size > 1
2  activerecord/lib/active_record/relation/delegation.rb
@@ -11,7 +11,7 @@ module Delegation # :nodoc:
# may vary depending on the klass of a relation, so we create a subclass of Relation
# for each different klass, and the delegations are compiled into that subclass only.
- delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :to => :to_a
+ delegate :to_xml, :to_yaml, :length, :each, :to_ary, :to => :to_a
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
:connection, :columns_hash, :auto_explain_threshold_in_seconds, :to => :klass
2  activerecord/lib/active_record/relation/query_methods.rb
@@ -196,7 +196,7 @@ def references!(*args) # :nodoc:
# # => ActiveModel::MissingAttributeError: missing attribute: other_field
def select(*fields)
if block_given?
- { |*block_args| yield(*block_args) }
+ super
raise ArgumentError, 'Call this with at least one field' if fields.empty?!(*fields)
2  activerecord/test/cases/associations/has_many_associations_test.rb
@@ -1451,7 +1451,7 @@ def test_calling_many_should_count_instead_of_loading_association
def test_calling_many_on_loaded_association_should_not_use_query
firm = companies(:first_firm)
- firm.clients.collect # force load
+ firm.clients.to_a # force load
assert_no_queries { assert firm.clients.many? }
12 activerecord/test/cases/named_scope_test.rb
@@ -22,8 +22,8 @@ def test_found_items_are_cached
all_posts = Topic.base
assert_queries(1) do
- all_posts.collect
- all_posts.collect
+ all_posts.to_a
+ all_posts.to_a
@@ -192,7 +192,7 @@ def test_any_should_call_proxy_found_if_using_a_block
def test_any_should_not_fire_query_if_scope_loaded
topics = Topic.base
- topics.collect # force load
+ topics.to_a # force load
assert_no_queries { assert topics.any? }
@@ -221,7 +221,7 @@ def test_many_should_call_proxy_found_if_using_a_block
def test_many_should_not_fire_query_if_scope_loaded
topics = Topic.base
- topics.collect # force load
+ topics.to_a # force load
assert_no_queries { assert topics.many? }
@@ -446,4 +446,8 @@ def test_eager_default_scope_relations_are_deprecated
assert_equal [posts(:welcome).title],
+ def test_json_export
+ assert_equal '[{"comment":{"id":1,"post_id":1,"parent_id":null}}]', Comment.where(:id => 1).to_json(:only => [:id, :post_id, :parent_id])
+ end
Something went wrong with that request. Please try again.