-
Notifications
You must be signed in to change notification settings - Fork 21.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added #or to ActiveRecord::Relation #9052
Conversation
@@ -452,6 +468,60 @@ def where!(opts = :chain, *rest) # :nodoc: | |||
end | |||
end | |||
|
|||
# Returns a new relation, which is the result of filtering the current relation | |||
# according to the conditions in the arguments, joining WHERE clauses with OR | |||
# operand, contraty to the default behaviour that uses AND. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo contrary
👍 Would love to see this one added. Simple idea that has been missing for a long time just like |
def method_missing(method, *args, &block) | ||
right_relation = @scope.klass.unscoped { | ||
@scope.klass.send(method, *args, &block) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally multi-line blocks use the do..end
style.
Thank you @mattdbridges for your feedback. I updated the commit with corrections for the typo and the multiline block. |
Sorry but some time ago we reached the conclusion to not add this method. Some reason:
Thank you so much. |
@rafaelfranca, I feel like the arguments to not add this method are those used for the
If you still disagree, I will try to extract it as a plugin. |
@tenderlove, @steveklabnik, you commented on a similar pull request (#6817). What is your opinion about this ? |
I don't have strong opinions about this one, so I think we can reopen to discuss. @jeremy thoughts? |
I would +1 this too but for a true scoping issue... Developing a re-occurring calendaring gem and it would be awesome to chain scopes in the following ways Calendar::Event.for_current_user.today there are 3 scoped chains for 3 tables... an events table based on whether the user created the event or whether the user has been assigned/notified to/of the event in an event_users table. This is handled with no problem, my issue is when there is a re-occurring event which falls between a reoccurring start and end as well as an event start and end of which I need to OR - this is perfectly valid reason for this request and I believe a missing feature especially when wanting to create complex but efficient queries FYI |
👍 for this feature |
👍 ❤️ |
👍 for this feature |
+1 |
1 similar comment
👍 |
👍 💛 |
ActiveRecord::Relation#or returns a new relation, which is the result of filtering the current relation according to the conditions in the arguments, joining WHERE clauses with OR operand, contraty to the default behaviour that uses AND. ActiveRecord::Relation#or accepts conditions in one of several formats. In the examples below, the resulting SQL is given as an illustration; the actual query generated may be different depending on the database adapter. * without arguments If ActiveRecord::Relation#or is used without arguments, it returns an ActiveRecord::OrChain object that can be used to chain queries with any other relation method, like where: Post.where("id = 1").or.where("id = 2") > SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'id = 2')) It can also be chained with a named scope: Post.where("id = 1").or.containing_the_letter_a > SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'body LIKE \\'%a%\\'')) * ActiveRecord::Relation When #or is used with an ActiveRecord::Relation as an argument, it merges the two relations, with the exception of the WHERE clauses, that are joined using the OR operand. Post.where("id = 1").or(Post.where("id = 2")) > SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'id = 2')) * anything you would pass to #where ActiveRecord::Relation#or also accepts anything that could be passed to the #where method, as a shortcut: Post.where("id = 1").or("id = ?", 2) > SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'id = 2'))
+1 |
+1 for this feature. I need some way to merge scopes together without having to do it manually column-by-column in squeel. |
👍 |
1 similar comment
👍 |
+1 |
In my humble opinion, leaving this to arel is a much more elegant ie user_arel_table = User.arel_table
User.where(user_arel_table[:name].eq('bob').or(user_arel_table[:age].lt(25))).to_sql translates to SELECT "users".* FROM "users" WHERE (("users"."name" = 'bob' OR "users"."age" < 25)) or to convert one of your examples from the pull request
posts = Post.arel_table
Post.where(posts[:id].eq(1).or(posts[:id].eq(2))).to_sql SELECT "posts".* FROM "posts" WHERE (("posts"."id" = 1 OR "posts"."id" = 2)) you can also check out a couple other good examples here (only the upvoted ones of course) http://stackoverflow.com/questions/7976358/activerecord-arel-or-condition you can also join in other model/tables and then use their arel tables also. I can add some examples like that if anyone wants to see. |
It's embarrassing enough that this feature is NOT in ActiveRecord yet. +1 |
@williscool That example works for small queries, but what if I have a large number of columns to search across? When I try the inject methods in the SO example I end up with Hash objects instead of ARel objects which don't work. I would prefer to have a native solution to combining scopes with OR. Rails is really good about having elegant solutions to many things, but this doesn't seem to be one of them. |
@descentintomael can you give an example with a large number of columns where the arel version is cumbersome? And you would do your method chaining in between the where functions on the ActiveRecord::Relation objects. Not in the middle of them on the arel ones. Man the naming of those things is quite confusing I wish they had completely different names. What I mean is User.containing_the_letter_a.where(admins.or(authors)) not User.where(admins.or(authors).containing_the_letter_a) |
Right now I'm trying to search through my Order model which has quite a few columns. For instance, it has an amount and a currency column, those two need to be joined with 'AND' but then there are other columns like customer ID and PO number that come into play and need to be combined with 'OR'. I'm not really sure yet how the syntax would go (without using Squeel), but I do like the direction taken in the first example: Post.where("id = 1").or(Post.where("id = 2")) |
right = (ActiveRecord::Relation === opts) ? opts : klass.unscoped.where(opts, rest) | ||
|
||
unless left.where_values.empty? || right.where_values.empty? | ||
left.where_values = [left.where_ast.or(right.where_ast)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a problem here: you are modifying the original relations. If you're lucky, this will fail with ActiveRecord::ImmutableRelation
, but this only happens if the relations have already been loaded.
The fix is simple:
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 1e291e6..ced097c 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -607,6 +607,7 @@ module ActiveRecord
right = (ActiveRecord::Relation === opts) ? opts : klass.unscoped.where(opts, rest)
unless left.where_values.empty? || right.where_values.empty?
+ left, right = left.dup, right.dup
left.where_values = [left.where_ast.or(right.where_ast)]
right.where_values = []
end
@schuetzm, thank you for your feedback. I updated the commit to avoid ImmutableRelation exceptions. |
awesome, thank you. |
Here's another example that needs The To make this work in rails currently, all query methods in PatientSearch would need to return arel instead of a relation so that they can be ORed together. This has a flow on effect such that any scopes used would also need to be rewritten as methods that return arel. Basically, throw the ActiveRecord query dsl and all its conveniences out the window. This wouldn't be an issue if queries.each do |query|
next if !query # skip if no query
if match_criteria == 'all'
results = results.where(query) # each search gets ANDed
elsif match_criteria == 'any'
results = results.or(query) # each search gets ORed
end
end |
The problem is more that all these chainable methods are an incitation to violating the law of demeter, which we all know is wrong. |
@midu I would say that the law of demeter isn't an issue here. You can think of it as the builder pattern: we don't rely on object's internals, or dependencies of dependency, if you wish; instead we add pieces to one object. The returning object is always the same. In alternative syntax it could look like
|
@midu, the example I provided is not an 'incitation to violating the law of demeter'. In fact it should quite cleanly build a query if there was an or operator. The example I provided is a very legitimate example of where or requires support from ActiveRecord. Besides, so long as its implementation is consistent with the rest of the dsl it should be the programmers prerogative to decide how they build queries. |
@midu, the law of demeter (and all other concerns) is secondary to a much larger issue. Here is why an 'or` operator should be supported by the ActiveRecord query dsl:
This fact should be considered above all else as it is at the heart of SQL and all the mathematical theory that it is built around. (on a side note, imagine if ruby didn't have an |
+1 |
1 similar comment
+1 |
sounds good to me, but i have no interest in rails/AR. |
all the +1's are also strange. you would swear someone was using a bot-net to sway public opinion. |
"law of demeter", oh get on that, +1 +1 +1 +1. |
+1 |
1 similar comment
+1 |
would like to see this feature be added |
@br3nt said it right: OR is a fundamental mathematical operation which deserves support. Without using OR, our actions are limited. |
I locked the conversation. We already have enough reasons to thinks about it so we don't need more support comments like "+1" or "I agree with this". We are going to review and if we decide to merge we will reopen the discussion. |
ActiveRecord::Relation#or returns a new relation, which is the
result of filtering the current relation according to the
conditions in the arguments, joining WHERE clauses with OR
operand, contraty to the default behaviour that uses AND.
ActiveRecord::Relation#or accepts conditions in one of several
formats. In the examples below, the resulting SQL is given as an
illustration; the actual query generated may be different depending
on the database adapter.
If ActiveRecord::Relation#or is used without arguments, it returns
an ActiveRecord::OrChain object that can be used to chain queries
with any other relation method, like where:
It can also be chained with a named scope:
When #or is used with an ActiveRecord::Relation as an argument, it
merges the two relations, with the exception of the WHERE clauses,
that are joined using the OR operand.
ActiveRecord::Relation#or also accepts anything that could be passed
to the #where method, as a shortcut:
This is my first contribution to rails and I tried to keep things coherent with the current code, but I am obviously open for feedback.