Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

ActiveRecord support for where_not #5950

Closed
wants to merge 1 commit into from
@jacobo

It's incredibly annoying to negate things in Active Record! I can do:

Person.where(:name => "Jacob")

but to negate it, I'd have to use a SQL fragment like this:

Person.where("name <> ? ", "Jacob")

I want support:

Person.where(:name.not => "Jacob")

(see: #5948)

But barring that, let's support:

Person.where_not(:name => "Jacob")
@jeremyf

Also closes #5160

@jeremyf

@jacobo Unfortunatley the tests do not cleanly merge with master. Could you resolve the merge and let me know.

@jacobo

cool, I'm glad to see interest. Will work on this soon.

How do you feel about a "where_like" method being included also?

@jeremyf

@jacobo Get the rebase passing, and keep the pull request to a single focus; If the rails core team merges the "where_not" then by all means pursue the "where_like"

@jeremy
Owner

Perhaps a Model.where with no args should be chainable: Model.where.not field: nil or Model.where.like name: 'John%'

@tinogomes

where should be chainable. I like it!

@amatsuda
Collaborator

Perhaps a Model.where with no args should be chainable: Model.where.not field: nil or Model.where.like name: 'John%'

I added this feature to my existing "everywhere" gem as a proof of concept. amatsuda/everywhere@v1.0.1...v2.0.0
You can try this syntax just by adding 'everywhere' to your existing Rails 3 or 4 app's Gemfile.

@jeremy
Does this implementation satisfy your spec?

@tenderlove @jonleighton
And of course I'm willing to push this feature into AR core. Any thoughts? Shall I go ahead and send a PR?

@tenderlove
Owner

@amatsuda seems good. I'd rather we have chainable where than monkeypatch symbol! :+1:

@jeremy
Owner

@amatsuda Yes, very nice! Clean implementation, too. :+1:

@carlosantoniodasilva

@amatsuda really cool, thanks for that :)

@carlosantoniodasilva

@amatsuda do you have any thoughts whether we're going to be able add support for this? Thanks

@amatsuda
Collaborator

@carlosantoniodasilva Yeah, I'm already having a working branch in my local machine. I'll add some tests and documents, then PR within few days.

@nijikon

+1

@carlosantoniodasilva

@amatsuda alright, great! That'd be a great addition for sure :)

@jonleighton
Collaborator

Chainable where also gets a :+1: from me

@sikachu
Collaborator

@amatsuda すごい!

Would you mind finish up the test, doc change, so we can get this bad boy in before Rails 4 release? :D

@kostyantyn

What are you thinking about implementation of #gt, #gte and etc methods which mongoid orm has. As for me, this syntax is better than "where_#{method_name}"

@frodsan

@amatsuda Hi, did you open a pull request with your implementation? Can we close this PR? Thanks.

@amatsuda amatsuda referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@amatsuda amatsuda referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@claudiob claudiob referenced this pull request from a commit in claudiob/rails
@amatsuda amatsuda Relation.where with no args can be chained with not, like, and not_like
examples:

  Model.where.not field: nil
  #=> "SELECT * FROM models WHERE field IS NOT NULL

  Model.where.like name: 'Jeremy%'
  #=> "SELECT * FROM models WHERE name LIKE 'Jeremy%'

this feature was originally suggested by Jeremy Kemper rails#5950 (comment)

Closes #5950
bbc4526
@claudiob claudiob referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@claudiob claudiob referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@claudiob claudiob referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@claudiob claudiob referenced this pull request from a commit in claudiob/rails
@claudiob claudiob Transforms WhereChain from a mixin to a builder
This feature was originally suggested by José Valim rails#8332 (comment)

The original commit fddf9c2 by Akira Matsuda enabled the methods #not, #like,
and #not_like to be chained to a Relation.where with no args.
This commit maintains the same behavior, but requires using #where before
chaining any of the previous methods, which makes chaining clearer.

Closes #5950
be71e1a
@amatsuda amatsuda referenced this pull request from a commit in amatsuda/rails
@amatsuda amatsuda Relation.where with no args can be chained with not, like, and not_like
examples:

  Model.where.not field: nil
  #=> "SELECT * FROM models WHERE field IS NOT NULL

  Model.where.like name: 'Jeremy%'
  #=> "SELECT * FROM models WHERE name LIKE 'Jeremy%'

this feature was originally suggested by Jeremy Kemper rails#5950 (comment)

Closes #5950
1d4e319
@jeremy
Owner

Moved to #8332

@jeremy jeremy closed this
@amatsuda amatsuda referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@amatsuda amatsuda referenced this pull request from a commit in amatsuda/rails
@amatsuda amatsuda Relation.where with no args can be chained with not, like, and not_like
examples:

  Model.where.not field: nil
  #=> "SELECT * FROM models WHERE field IS NOT NULL

  Model.where.like name: 'Jeremy%'
  #=> "SELECT * FROM models WHERE name LIKE 'Jeremy%'

this feature was originally suggested by Jeremy Kemper rails#5950 (comment)

Closes #5950
de75af7
@Alamoz

What about when mixing WHERE and NOT clauses? Tried chaining them, which doesn't work. Don't see any example of combining/chaining where and where.not anywhere in the documentation.

@robin850
Collaborator

@Alamoz : What about this example?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 24, 2012
  1. @jacobo
This page is out of date. Refresh to see the latest.
View
2  activerecord/lib/active_record/associations/collection_proxy.rb
@@ -38,7 +38,7 @@ class CollectionProxy # :nodoc:
instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to|proxy_/ }
- delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from,
+ delegate :group, :order, :limit, :joins, :where, :where_not, :preload, :eager_load, :includes, :from,
:lock, :readonly, :having, :pluck, :to => :scoped
delegate :target, :load_target, :loaded?, :to => :@association
View
2  activerecord/lib/active_record/querying.rb
@@ -9,7 +9,7 @@ module Querying
delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped
delegate :find_each, :find_in_batches, :to => :scoped
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins,
- :where, :preload, :eager_load, :includes, :from, :lock, :readonly,
+ :where, :where_not, :preload, :eager_load, :includes, :from, :lock, :readonly,
:having, :create_with, :uniq, :references, :none, :to => :scoped
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :to => :scoped
View
44 activerecord/lib/active_record/relation/predicate_builder.rb
@@ -1,6 +1,6 @@
module ActiveRecord
class PredicateBuilder # :nodoc:
- def self.build_from_hash(engine, attributes, default_table)
+ def self.build_from_hash(engine, attributes, default_table, operator = nil)
attributes.map do |column, value|
table = default_table
@@ -8,14 +8,18 @@ def self.build_from_hash(engine, attributes, default_table)
table = Arel::Table.new(column, engine)
build_from_hash(engine, value, table)
else
- column = column.to_s
+ if operator
+ build(table[column.to_sym], value, operator)
+ else
+ column = column.to_s
- if column.include?('.')
- table_name, column = column.split('.', 2)
- table = Arel::Table.new(table_name, engine)
- end
+ if column.include?('.')
+ table_name, column = column.split('.', 2)
+ table = Arel::Table.new(table_name, engine)
+ end
- build(table[column.to_sym], value)
+ build(table[column.to_sym], value)
+ end
end
end.flatten
end
@@ -32,11 +36,15 @@ def self.references(attributes)
end
private
- def self.build(attribute, value)
+ def self.build(attribute, value, operator = nil)
+ in_pred, eq_pred = :in, :eq
+ if operator == :not
+ in_pred, eq_pred = :not_in, :not_eq
+ end
case value
when ActiveRecord::Relation
value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
- attribute.in(value.arel.ast)
+ attribute.send(in_pred,value.arel.ast)
when Array, ActiveRecord::Associations::CollectionProxy
values = value.to_a.map {|x| x.is_a?(ActiveRecord::Model) ? x.id : x}
ranges, values = values.partition {|v| v.is_a?(Range)}
@@ -46,28 +54,28 @@ def self.build(attribute, value)
case values.length
when 0
- attribute.eq(nil)
+ attribute.send(eq_pred,nil)
when 1
- attribute.eq(values.first).or(attribute.eq(nil))
+ attribute.send(eq_pred,values.first).or(attribute.send(eq_pred,nil))
else
- attribute.in(values).or(attribute.eq(nil))
+ attribute.send(in_pred,values).or(attribute.send(eq_pred,nil))
end
else
- attribute.in(values)
+ attribute.send(in_pred,values)
end
- array_predicates = ranges.map { |range| attribute.in(range) }
+ array_predicates = ranges.map { |range| attribute.send(in_pred,range) }
array_predicates << values_predicate
array_predicates.inject { |composite, predicate| composite.or(predicate) }
when Range
- attribute.in(value)
+ attribute.send(in_pred,value)
when ActiveRecord::Model
- attribute.eq(value.id)
+ attribute.send(eq_pred,value.id)
when Class
# FIXME: I think we need to deprecate this behavior
- attribute.eq(value.name)
+ attribute.send(eq_pred,value.name)
else
- attribute.eq(value)
+ attribute.send(eq_pred,value)
end
end
end
View
15 activerecord/lib/active_record/relation/query_methods.rb
@@ -194,6 +194,17 @@ def bind!(value)
self
end
+ def where_not(opts, *rest)
+ opts.blank? ? self : clone.where_not!(opts, *rest)
+ end
+
+ def where_not!(opts, *rest)
+ references!(PredicateBuilder.references(opts)) if Hash === opts
+
+ self.where_values += build_where(opts, rest, :not)
+ self
+ end
+
def where(opts, *rest)
opts.blank? ? self : clone.where!(opts, *rest)
end
@@ -452,13 +463,13 @@ def collapse_wheres(arel, wheres)
end
end
- def build_where(opts, other = [])
+ def build_where(opts, other = [], operator = nil)
case opts
when String, Array
[@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
when Hash
attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
- PredicateBuilder.build_from_hash(table.engine, attributes, table)
+ PredicateBuilder.build_from_hash(table.engine, attributes, table, operator)
else
[opts]
end
View
5 activerecord/test/cases/finder_test.rb
@@ -229,6 +229,11 @@ def test_first_bang_missing
end
end
+ def test_where_not
+ assert_equal [authors(:david)], Author.where_not(organization_id: nil).all
+ assert_equal [authors(:bob)], Author.where_not(name: ["David", "Mary"]).all
+ end
+
def test_model_class_responds_to_first_bang
assert Topic.first!
Topic.delete_all
Something went wrong with that request. Please try again.