camertron/arel-helpers — pull request #11 by camertron (2014-12-05 09:21:48+00:00)
Title: Monkey patch CollectionProxy to avoid returning unexpected Arel::Attribute instances

#### Problem

When accessing a has_many association on a foreign model that also includes `ArelHelpers::ArelTable`, an `Arel::Attribute` is returned instead of an instance of the model. For example, say you have `Post` and `Comment` models where posts have many comments:

```ruby
class Post < ActiveRecord::Base
include ArelHelpers::ArelTable
has_many :comments
end
```

Executing `post.first.comments[0]` will return an `Arel::Attribute` instead of a comment.

#### Explanation

ActiveRecord as of version 4.0 defines a `CollectionProxy` class that lazily fetches records from the database. If posts has many comments, then `post.comments` returns a `CollectionProxy` that can be iterated over. A query to fetch the pertinent records isn't fired until iteration starts, so `post.comments` itself isn't enough to cause a trip to the database.

ArelHelpers adds a `[]` method to `ActiveRecord::Relation` which means it also adds the same method to `CollectionProxy`, since `CollectionProxy` inherits from `Relation`. For some reason, `CollectionProxy` doesn't define its own `[]` method but instead lets ActiveRecord's complicated `Delegation` module handle calls to it via `method_missing`. In the `post.comments` case illustrated above, a call to `[]` is handled by [this](https://github.com/rails/rails/blob/4-1-stable/activerecord/lib/active_record/relation/delegation.rb#L91) `method_missing`. Here, `@klass` refers to the `Comment` model, which means `[]` calls on an instance of `CollectionProxy` actually get re-routed. Instead of calling `[]` on a `CollectionProxy` like we were expecting, it calls `[]` on `Comment`, and therefore on the mixed-in `ArelHelpers::ArelTable`. The final result is that the caller gets back an `Arel::Attribute` instead of the instance of `Comment` they were expecting.

The "simple" monkey patch in this PR defines `[]` on `CollectionProxy` so the convoluted `method_missing` logic in `Delegation` doesn't get triggered, and therefore doesn't end up returning an unexpected `Arel::Attribute`.