Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

ActiveRecord support for where_not #5950

Closed
wants to merge 1 commit into from

15 participants

Jacob Burkhart Jeremy Friesen Jeremy Kemper Celestino Gomes Akira Matsuda Aaron Patterson Carlos Antonio da Silva Tomasz Pajor Jeroen van Ingen Jon Leighton Prem Sichanugrist Kostyantyn Stepanyuk Francesco Rodríguez Adrien Lamothe Robin Dupret
Jacob Burkhart

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")
Jeremy Friesen

Also closes #5160

Jeremy Friesen

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

Jacob Burkhart

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

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

Jeremy Friesen

@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 Kemper
Owner

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

Celestino Gomes

where should be chainable. I like it!

Akira Matsuda
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?

Aaron Patterson
Owner

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

Jeremy Kemper
Owner

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

Carlos Antonio da Silva

@amatsuda really cool, thanks for that :)

Carlos Antonio da Silva

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

Akira Matsuda
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.

Tomasz Pajor

+1

Carlos Antonio da Silva

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

Jon Leighton
Collaborator

Chainable where also gets a :+1: from me

Prem Sichanugrist
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 Stepanyuk

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}"

Francesco Rodríguez

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

Akira Matsuda amatsuda referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Akira Matsuda amatsuda referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Claudio B. claudiob referenced this pull request from a commit in claudiob/rails
Akira Matsuda 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
Claudio B. claudiob referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Claudio B. claudiob referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Claudio B. claudiob referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Claudio B. claudiob referenced this pull request from a commit in claudiob/rails
Claudio B. 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
Akira Matsuda amatsuda referenced this pull request from a commit in amatsuda/rails
Akira Matsuda 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 Kemper
Owner

Moved to #8332

Jeremy Kemper jeremy closed this
Akira Matsuda amatsuda referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Akira Matsuda amatsuda referenced this pull request from a commit in amatsuda/rails
Akira Matsuda 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
Adrien Lamothe

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.

Robin Dupret
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. Jacob Burkhart
This page is out of date. Refresh to see the latest.
2  activerecord/lib/active_record/associations/collection_proxy.rb
View
@@ -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
2  activerecord/lib/active_record/querying.rb
View
@@ -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
44 activerecord/lib/active_record/relation/predicate_builder.rb
View
@@ -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
15 activerecord/lib/active_record/relation/query_methods.rb
View
@@ -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
5 activerecord/test/cases/finder_test.rb
View
@@ -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.