rails/rails — pull request #5494 by armstrjare (2012-03-18 07:07:31+00:00)
Title: ActiveRecord::Relation - maintain context of joined associations on merges

Adds functionality that allows scopes such as: `Author.joins(:posts).merge(Post.joins(:comments)).group("authors.id")`, which would presently raise an error because there is no association `:comments` on `Author`.

---

ActiveRecord::Relation merges presently don't maintain the context of joined associations. That is, merging a Relation/scope that references joined associations into another with a different base ActiveRecord model will fail because the join associations are resolved from the base model at the time of query generation.

For example, consider the following ActiveRecord models:

```ruby
class Author
has_many :posts
end

class Post
belongs_to :author
has_many :comments
end

class Comment
belongs_to :post
end
```

One might want to query all Comments on Posts by a specific Author. One way to write such a query could be:

```ruby
Comment.joins(:post).merge(Post.joins(:author).merge(Author.where(:name => "Joe Blogs"))).all
```

Unfortunately, as the association joins are resolved at the time of query generation from the model of the new base `Relation`(as they are merged in as the original values passed to `joins` on the merged `Relation`), this query will fail as there is no association `:author` on `Comment`.

---

My proposed solution is to ensure that the source ActiveRecord model for association joins is persisted through Relation merges.

There is already logic for applying explicit association joins within `JoinDependency`: `JoinDependency#graft`. `Relation::SpawnMethods#build_joins` already treats any `JoinDependency::JoinAssociation` object within the `joins_values` in this way, so my proposed solution is to, whenever a `Relation` is merged into another, instead convert any `Relation#joins` arguments which would be treated as association joins into `JoinAssociation` objects (which encapsulate the parent model and source association) *at the time of merge*, thus maintaining the original intended context of the association to join.

At query time, `JoinDependency#graft` then can resolve the stashed associations joins from the new base ActiveRecord model *and* its [chain of] joined associations.