added ActiveRecord::Relation#left_outer_joins #12071

Merged
merged 1 commit into from Oct 30, 2015

Conversation

Projects
None yet
@Crunch09
Contributor

Crunch09 commented Aug 29, 2013

Hi,

this adds the ability to use a left outer join query without writing pure SQL as an argument to #joins and also query for a specific selection of columns which doesn't work with #includes.

The ability to do this is already implemented in ActiveRecord::Associations::JoinDependency#build but it was only used with Arel::InnerJoin as join_type.
I am not quite sure about the tests, i just used the InnerJoin tests as a starting point. Any help would be appreciated.

Example:

  User.outer_joins(:posts)
  => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
@provideal

This comment has been minimized.

Show comment
Hide comment

+1

@Mario1988

This comment has been minimized.

Show comment
Hide comment

+1

@Kleinitay

This comment has been minimized.

Show comment
Hide comment

+1

@njakobsen

This comment has been minimized.

Show comment
Hide comment
@njakobsen

njakobsen Aug 30, 2013

Contributor

+1 @Crunch09 Your patch is so much more pro than my poor man's outer_join where I gsub the inner joins to outer joins. https://gist.github.com/njakobsen/6395146

Contributor

njakobsen commented Aug 30, 2013

+1 @Crunch09 Your patch is so much more pro than my poor man's outer_join where I gsub the inner joins to outer joins. https://gist.github.com/njakobsen/6395146

@egilburg

This comment has been minimized.

Show comment
Hide comment
@egilburg

egilburg Aug 31, 2013

Contributor

+1

Contributor

egilburg commented Aug 31, 2013

+1

@robin850

This comment has been minimized.

Show comment
Hide comment
@robin850

robin850 Aug 31, 2013

Member

Thanks for your contribution @Crunch09! Actually, it would be awesome if we could update a little bit the guides with this new feature.

Member

robin850 commented Aug 31, 2013

Thanks for your contribution @Crunch09! Actually, it would be awesome if we could update a little bit the guides with this new feature.

@Crunch09

This comment has been minimized.

Show comment
Hide comment
@Crunch09

Crunch09 Aug 31, 2013

Contributor

yes, will write something tomorrow

Contributor

Crunch09 commented Aug 31, 2013

yes, will write something tomorrow

@@ -948,6 +983,11 @@ def build_joins(manager, joins)
end
end
+ build_join_query(manager, buckets, Arel::InnerJoin)
+

This comment has been minimized.

@frodsan

frodsan Aug 31, 2013

Contributor

✂️

@frodsan

frodsan Aug 31, 2013

Contributor

✂️

This comment has been minimized.

@Crunch09

Crunch09 Sep 1, 2013

Contributor

thank you, fixed that!

@Crunch09

Crunch09 Sep 1, 2013

Contributor

thank you, fixed that!

@Vetal4eg

This comment has been minimized.

Show comment
Hide comment

Vetal4eg commented Sep 1, 2013

+1

@csouls

This comment has been minimized.

Show comment
Hide comment

csouls commented Sep 1, 2013

+1

@Crunch09

This comment has been minimized.

Show comment
Hide comment
@Crunch09

Crunch09 Sep 1, 2013

Contributor

@robin850 Added few sentences in the AR-querying guide about it and also rebased against current master.

Contributor

Crunch09 commented Sep 1, 2013

@robin850 Added few sentences in the AR-querying guide about it and also rebased against current master.

@robin850

This comment has been minimized.

Show comment
Hide comment
@robin850

robin850 Sep 7, 2013

Member

@Crunch09 : Awesome, thank you! ❤️

@rafaelfranca : Could you please have a look ? It looks awesome to me!

Member

robin850 commented Sep 7, 2013

@Crunch09 : Awesome, thank you! ❤️

@rafaelfranca : Could you please have a look ? It looks awesome to me!

@Crunch09

This comment has been minimized.

Show comment
Hide comment
@Crunch09

Crunch09 Sep 26, 2013

Contributor

@rafaelfranca @robin850 I just rebased again against current master, anything more i can do?

Contributor

Crunch09 commented Sep 26, 2013

@rafaelfranca @robin850 I just rebased again against current master, anything more i can do?

@dmathieu

This comment has been minimized.

Show comment
Hide comment
Contributor

dmathieu commented Sep 26, 2013

@toreriklinnerud

This comment has been minimized.

Show comment
Hide comment
@toreriklinnerud

toreriklinnerud Oct 10, 2013

+1 Outer joins is one of the few remaining things that force us to fall back to SQL in our codebase

+1 Outer joins is one of the few remaining things that force us to fall back to SQL in our codebase

+ build_join_query(manager, buckets, Arel::InnerJoin)
+ end
+
+ def build_join_query(manager, buckets, join_type=nil)

This comment has been minimized.

@tenderlove

tenderlove Apr 23, 2014

Member

Please do not default join_type to nil. I don't think nil is actually an acceptable value. Change the things that call build_join_query to actually pass in the things it needs.

@tenderlove

tenderlove Apr 23, 2014

Member

Please do not default join_type to nil. I don't think nil is actually an acceptable value. Change the things that call build_join_query to actually pass in the things it needs.

+ end
+
+ def test_join_conditions_allow_nil_associations
+ authors = Author.includes(:essays).where(:essays => {:id => nil})

This comment has been minimized.

@tenderlove

tenderlove Apr 23, 2014

Member

What does this have to do with outer joins?

@tenderlove

tenderlove Apr 23, 2014

Member

What does this have to do with outer joins?

This comment has been minimized.

@Crunch09

Crunch09 Apr 24, 2014

Contributor

nothing, will remove that and try to improve the tests overall. thank you!

@Crunch09

Crunch09 Apr 24, 2014

Contributor

nothing, will remove that and try to improve the tests overall. thank you!

@tenderlove

This comment has been minimized.

Show comment
Hide comment
@tenderlove

tenderlove Apr 23, 2014

Member

Can you please change these tests not to use to_sql? We have a capture_sql command that you can use to make assertions against the sql run against the database.

Member

tenderlove commented Apr 23, 2014

Can you please change these tests not to use to_sql? We have a capture_sql command that you can use to make assertions against the sql run against the database.

@tenderlove

This comment has been minimized.

Show comment
Hide comment
@tenderlove

tenderlove Apr 23, 2014

Member

I'm positive on this, but the copy/paste make me uneasy. :(

Member

tenderlove commented Apr 23, 2014

I'm positive on this, but the copy/paste make me uneasy. :(

guides/source/active_record_querying.md
@@ -937,11 +938,17 @@ This will result in the following SQL:
SELECT clients.* FROM clients LEFT OUTER JOIN addresses ON addresses.client_id = clients.id
```
-### Using Array/Hash of Named Associations
+Instead of writing raw SQL in this case you could also use the `outer_joins` method
+which would produce the same result.

This comment has been minimized.

@matthewd

matthewd Apr 23, 2014

Member

If we're going to introduce an outer_joins, I think this is a bit of an "also-ran" position to mention it... we should probably be offering it before we suggest raw SQL.

In fact, maybe the SQL should change, to show a still-legitimate use case for a raw JOIN, as it previously did, instead of an inferior spelling of built-in functionality.

@matthewd

matthewd Apr 23, 2014

Member

If we're going to introduce an outer_joins, I think this is a bit of an "also-ran" position to mention it... we should probably be offering it before we suggest raw SQL.

In fact, maybe the SQL should change, to show a still-legitimate use case for a raw JOIN, as it previously did, instead of an inferior spelling of built-in functionality.

@matthewd

This comment has been minimized.

Show comment
Hide comment
@matthewd

matthewd Apr 23, 2014

Member

I'm vaguely wary of outer_joins == LEFT OUTER JOIN. With includes, that's abstracted-away implementation detail. But here...

Maybe consider left_joins?

Member

matthewd commented Apr 23, 2014

I'm vaguely wary of outer_joins == LEFT OUTER JOIN. With includes, that's abstracted-away implementation detail. But here...

Maybe consider left_joins?

@tenderlove

This comment has been minimized.

Show comment
Hide comment
@tenderlove

tenderlove Apr 24, 2014

Member

I'm vaguely wary of outer_joins == LEFT OUTER JOIN. With includes, that's abstracted-away implementation detail. But here...

Maybe consider left_joins?

Good point. If it's actually going to do an LOJ, the method should probably be left_joins or left_outer_joins.

Member

tenderlove commented Apr 24, 2014

I'm vaguely wary of outer_joins == LEFT OUTER JOIN. With includes, that's abstracted-away implementation detail. But here...

Maybe consider left_joins?

Good point. If it's actually going to do an LOJ, the method should probably be left_joins or left_outer_joins.

@Crunch09

This comment has been minimized.

Show comment
Hide comment
@Crunch09

Crunch09 Apr 24, 2014

Contributor

Thank you for your feedback! I'll update this PR over the weekend and report back then

Contributor

Crunch09 commented Apr 24, 2014

Thank you for your feedback! I'll update this PR over the weekend and report back then

@al2o3cr

This comment has been minimized.

Show comment
Hide comment
@al2o3cr

al2o3cr Apr 25, 2014

Contributor

One thing I'd love to see but don't have a clear API for - the ability to specify additional bits in the ON clause. Perhaps something like:

User.outer_joins(:posts) { where(featured: true) }

which would produce the SQL:

SELECT ... FROM users
LEFT OUTER JOIN posts ON posts.user_id = users.id AND posts.featured = 't'

assuming there's a boolean featured column on posts.

Note that the above is not equivalent to:

User.outer_joins(:posts).where(featured: true)

since that query doesn't include users who don't have any featured posts.

Without this feature, it would still be possible to create the desired SQL with a scoped association:

# on User
has_many :featured_posts, -> { where(featured: true) }, class_name: 'Post'

User.outer_joins(:featured_posts)

but that method doesn't exactly scale to arbitrary sets of conditions.

Contributor

al2o3cr commented Apr 25, 2014

One thing I'd love to see but don't have a clear API for - the ability to specify additional bits in the ON clause. Perhaps something like:

User.outer_joins(:posts) { where(featured: true) }

which would produce the SQL:

SELECT ... FROM users
LEFT OUTER JOIN posts ON posts.user_id = users.id AND posts.featured = 't'

assuming there's a boolean featured column on posts.

Note that the above is not equivalent to:

User.outer_joins(:posts).where(featured: true)

since that query doesn't include users who don't have any featured posts.

Without this feature, it would still be possible to create the desired SQL with a scoped association:

# on User
has_many :featured_posts, -> { where(featured: true) }, class_name: 'Post'

User.outer_joins(:featured_posts)

but that method doesn't exactly scale to arbitrary sets of conditions.

@matthewd

This comment has been minimized.

Show comment
Hide comment
@matthewd

matthewd Apr 25, 2014

Member

@al2o3cr +1. We should probably address that separately, afterwards, though... whatever we end up with seems like it should apply to includes as well. This would address one of the biggest causes of scoped-association proliferation that I see. (:through targets being the other.)

Member

matthewd commented Apr 25, 2014

@al2o3cr +1. We should probably address that separately, afterwards, though... whatever we end up with seems like it should apply to includes as well. This would address one of the biggest causes of scoped-association proliferation that I see. (:through targets being the other.)

@njakobsen

This comment has been minimized.

Show comment
Hide comment
@njakobsen

njakobsen Nov 13, 2014

Contributor

Any word on when this will be merged? It's been a long time in coming, and every project I make ends up needing left outer joins.

Contributor

njakobsen commented Nov 13, 2014

Any word on when this will be merged? It's been a long time in coming, and every project I make ends up needing left outer joins.

@Crunch09

This comment has been minimized.

Show comment
Hide comment
@Crunch09

Crunch09 Nov 13, 2014

Contributor

it's my fault. There were lots of changes in the affected code and i need to solve the merge conflicts and also apply the feedback. I started with that and i hope i can finish it this weekend, sorry about the long wait.

Contributor

Crunch09 commented Nov 13, 2014

it's my fault. There were lots of changes in the affected code and i need to solve the merge conflicts and also apply the feedback. I started with that and i hope i can finish it this weekend, sorry about the long wait.

@Crunch09

This comment has been minimized.

Show comment
Hide comment
@Crunch09

Crunch09 Nov 18, 2014

Contributor

I updated the code now and renamed the method to left_outer_joins. I also chose a (hopefully) better example for the guides, as @matthewd suggested.

cc: @tenderlove

Contributor

Crunch09 commented Nov 18, 2014

I updated the code now and renamed the method to left_outer_joins. I also chose a (hopefully) better example for the guides, as @matthewd suggested.

cc: @tenderlove

@k0kubun

This comment has been minimized.

Show comment
Hide comment
@k0kubun

k0kubun Nov 29, 2014

Contributor

+1

Contributor

k0kubun commented Nov 29, 2014

+1

@k0kubun

This comment has been minimized.

Show comment
Hide comment
@k0kubun

k0kubun Nov 29, 2014

Contributor

left_outer_joins is easy to understand, but I think it is a long name.

I propose alias :left_joins :left_outer_joins (otherwise I'll send PR after this patch is merged). Because OUTER keyword can be abbreviated in an actual query, a real behavior is reflected in it.

Contributor

k0kubun commented Nov 29, 2014

left_outer_joins is easy to understand, but I think it is a long name.

I propose alias :left_joins :left_outer_joins (otherwise I'll send PR after this patch is merged). Because OUTER keyword can be abbreviated in an actual query, a real behavior is reflected in it.

@rafaelfranca rafaelfranca added this to the 5.0.0 milestone Jan 2, 2015

@Crunch09

This comment has been minimized.

Show comment
Hide comment
@Crunch09

Crunch09 Jan 15, 2015

Contributor

@k0kubun Good point! I would be fine either way.
@tenderlove I don't know if you remember, but we talked briefly at Arrrcamp about this 😄 I rebased it against current master, would be great if you could take another look if you have the time 😉

Contributor

Crunch09 commented Jan 15, 2015

@k0kubun Good point! I would be fine either way.
@tenderlove I don't know if you remember, but we talked briefly at Arrrcamp about this 😄 I rebased it against current master, would be great if you could take another look if you have the time 😉

@dmitry

This comment has been minimized.

Show comment
Hide comment
@dmitry

dmitry Feb 25, 2015

Contributor

@al2o3cr have you already created separate issue for supporting the User.outer_joins(:posts) { where(featured: true) }?

Contributor

dmitry commented Feb 25, 2015

@al2o3cr have you already created separate issue for supporting the User.outer_joins(:posts) { where(featured: true) }?

@njakobsen

This comment has been minimized.

Show comment
Hide comment
@njakobsen

njakobsen Mar 6, 2015

Contributor

Doh. I needed a left outer join again today.

Contributor

njakobsen commented Mar 6, 2015

Doh. I needed a left outer join again today.

@rails rails locked and limited conversation to collaborators Mar 10, 2015

added ActiveRecord::Relation#left_outer_joins
Example:
  User.left_outer_joins(:posts)
  => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"

@rails rails unlocked this conversation Sep 10, 2015

@sgrif sgrif merged commit 3f46ef1 into rails:master Oct 30, 2015

1 check passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details

sgrif added a commit that referenced this pull request Oct 30, 2015

Merge pull request #12071 from Crunch09/outer_joins
added ActiveRecord::Relation#outer_joins

sgrif added a commit that referenced this pull request Oct 30, 2015

Fix test failures caused by #12071
This assumes only one query was ever executed, but it appears to
sometimes be loading schema information. We can just look at the array
of queries, rather than the "first" one that was run
@egilburg

This comment has been minimized.

Show comment
Hide comment
@egilburg

egilburg Oct 30, 2015

Contributor

🎉

Contributor

egilburg commented Oct 30, 2015

🎉

@thibaudgg

This comment has been minimized.

Show comment
Hide comment
@thibaudgg

thibaudgg Oct 31, 2015

Contributor

👏 👏

Contributor

thibaudgg commented Oct 31, 2015

👏 👏

@Crunch09

This comment has been minimized.

Show comment
Hide comment
@Crunch09

Crunch09 Oct 31, 2015

Contributor

Thank you all ❤️ So happy this got merged!

Contributor

Crunch09 commented Oct 31, 2015

Thank you all ❤️ So happy this got merged!

@Crunch09 Crunch09 deleted the Crunch09:outer_joins branch Oct 31, 2015

@njakobsen

This comment has been minimized.

Show comment
Hide comment
@njakobsen

njakobsen Oct 31, 2015

Contributor

👏👏

Contributor

njakobsen commented Oct 31, 2015

👏👏

@rafaelfranca rafaelfranca modified the milestones: 5.0.0 [temp], 5.0.0 Dec 30, 2015

@prathamesh-sonpatki prathamesh-sonpatki changed the title from added ActiveRecord::Relation#outer_joins to added ActiveRecord::Relation#left_outer_joins Mar 10, 2016

@khiav223577 khiav223577 referenced this pull request in khiav223577/left_joins Dec 25, 2017

Merged

implement left joins #1

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