Skip to content

Loading…

Added initial stab at an or scope operator #6817

Closed
wants to merge 2 commits into from

5 participants

@loz

This includes a test case and an implementation. I think I need some advice however as the only way I could replace the where clauses in a scope was to unscope, which will have killed other aspects, like having etc, so I'm happy to make changes if someone could advice.

Jonathan Loz... added some commits
@emilecantin

This fixes #5545. Thanks!

@daniel2d2art

Fantásticooow!!
We need it!
Thx

@loz loz commented on the diff
activerecord/lib/active_record/relation/query_methods.rb
@@ -297,6 +297,19 @@ def where!(opts, *rest)
self
end
+ # Join scope clauses using or
+ #
+ # The passed scope is joined:
+ #
+ # User.where({ name: "Alice"}).or(User.where({ email: 'joe@example.com'}))
+ # # SELECT * FROM users WHERE name = 'Alice' OR email = 'joe@example.com'
+ #
+ def or(scope)
+ left = self.where_values.inject {|l,r| l.and(r)}
@loz
loz added a note

this particular line makes the assumption that .and is always the 'join' for clauses. This is true until now, but if one had a scope with or joining to another scope with ors, they'd end up ands.

If there's an arel 'join clauses' method which would use correct concat here that would be handy. If not, a hint at where to find the concat used in the where to extract a method here to do it would be good.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@loz loz commented on the diff
activerecord/lib/active_record/relation/query_methods.rb
@@ -297,6 +297,19 @@ def where!(opts, *rest)
self
end
+ # Join scope clauses using or
+ #
+ # The passed scope is joined:
+ #
+ # User.where({ name: "Alice"}).or(User.where({ email: 'joe@example.com'}))
+ # # SELECT * FROM users WHERE name = 'Alice' OR email = 'joe@example.com'
+ #
+ def or(scope)
+ left = self.where_values.inject {|l,r| l.and(r)}
+ right = scope.where_values.inject {|l,r| l.and(r)}
+ self.unscoped.where(left.or(right))
@loz
loz added a note

I tried setting scope.where_values = [left.or(right)] however it did not persist. The only way I could do this was to clear the scope down. This kills other things unintentionally. If there's a way to do this, please can someone advise. If not, I think I need to save all the other 'values' and reassign after the scope clear.

@tenderlove Ruby on Rails member

Haven't tested, but would self.where_values = left.or(right) work?

@loz
loz added a note

Sadly not, the assignment works, but then after the code has executed the assigned value is lost again :(

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

Hi Loz,
using unscope, are you disabling too, joins, selects and other scope setings? nops?

@loz

Yup, this is totally NOT the solution I want, which is why I put it up for some feedback.

@steveklabnik
Ruby on Rails member

Hey @loz! Do you plan to continue to work on this?

@steveklabnik
Ruby on Rails member

Hey @loz! it's been a month since I've heard from you, so I'm giving this a close. If you'd like to keep up with this pull, push some more commits, and I'll gladly re-open.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jun 21, 2012
  1. Added initial stab at an or scope operator

    Jonathan Lozinski committed
  2. Tidied up method calls

    Jonathan Lozinski committed
Showing with 29 additions and 0 deletions.
  1. +13 −0 activerecord/lib/active_record/relation/query_methods.rb
  2. +16 −0 activerecord/test/cases/scope_or_test.rb
View
13 activerecord/lib/active_record/relation/query_methods.rb
@@ -297,6 +297,19 @@ def where!(opts, *rest)
self
end
+ # Join scope clauses using or
+ #
+ # The passed scope is joined:
+ #
+ # User.where({ name: "Alice"}).or(User.where({ email: 'joe@example.com'}))
+ # # SELECT * FROM users WHERE name = 'Alice' OR email = 'joe@example.com'
+ #
+ def or(scope)
+ left = self.where_values.inject {|l,r| l.and(r)}
@loz
loz added a note

this particular line makes the assumption that .and is always the 'join' for clauses. This is true until now, but if one had a scope with or joining to another scope with ors, they'd end up ands.

If there's an arel 'join clauses' method which would use correct concat here that would be handy. If not, a hint at where to find the concat used in the where to extract a method here to do it would be good.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ right = scope.where_values.inject {|l,r| l.and(r)}
+ self.unscoped.where(left.or(right))
@loz
loz added a note

I tried setting scope.where_values = [left.or(right)] however it did not persist. The only way I could do this was to clear the scope down. This kills other things unintentionally. If there's a way to do this, please can someone advise. If not, I think I need to save all the other 'values' and reassign after the scope clear.

@tenderlove Ruby on Rails member

Haven't tested, but would self.where_values = left.or(right) work?

@loz
loz added a note

Sadly not, the assignment works, but then after the code has executed the assigned value is lost again :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ end
+
def having(opts, *rest)
opts.blank? ? self : spawn.having!(opts, *rest)
end
View
16 activerecord/test/cases/scope_or_test.rb
@@ -0,0 +1,16 @@
+require 'cases/helper'
+require 'models/topic'
+require 'models/reply'
+
+class ScopeOrTest < ActiveRecord::TestCase
+ fixtures :topics
+
+ def test_or_includes_union_of_scopes
+ scopea = Topic.where(:author_name => 'Carl', :written_on => Date.parse('2006-07-15 14:28:00 UTC'))
+ scopeb = Topic.where(:approved => false)
+ ored = (scopea.all + scopeb.all).uniq
+ assert_equal scopea.or(scopeb).count, ored.size
+ scopea.each {|a| assert scopea.or(scopeb).include?(a) }
+ scopeb.each {|b| assert scopea.or(scopeb).include?(b) }
+ end
+end
Something went wrong with that request. Please try again.