mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Implemented ActiveRecord::Relation#excluding
method.
This method excludes the specified record (or collection of records) from the resulting relation. For example: `Post.excluding(post)`, `Post.excluding(post_one, post_two)`, and `post.comments.excluding(comment)`. This is short-hand for `Post.where.not(id: post.id)` (for a single record) and `Post.where.not(id: [post_one.id, post_two.id])` (for a collection).
This commit is contained in:
parent
5585c375d1
commit
690fdbb2b9
4 changed files with 139 additions and 1 deletions
|
@ -1,3 +1,25 @@
|
|||
* Implemented `ActiveRecord::Relation#excluding` method.
|
||||
|
||||
This method excludes the specified record (or collection of records) from
|
||||
the resulting relation:
|
||||
|
||||
```ruby
|
||||
Post.excluding(post)
|
||||
Post.excluding(post_one, post_two)
|
||||
```
|
||||
|
||||
Also works on associations:
|
||||
|
||||
```ruby
|
||||
post.comments.excluding(comment)
|
||||
post.comments.excluding(comment_one, comment_two)
|
||||
```
|
||||
|
||||
This is short-hand for `Post.where.not(id: post.id)` (for a single record)
|
||||
and `Post.where.not(id: [post_one.id, post_two.id])` (for a collection).
|
||||
|
||||
*Glen Crawford*
|
||||
|
||||
* Skip optimised #exist? query when #include? is called on a relation
|
||||
with a having clause
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ module ActiveRecord
|
|||
:and, :or, :annotate, :optimizer_hints, :extending,
|
||||
:having, :create_with, :distinct, :references, :none, :unscope, :merge, :except, :only,
|
||||
:count, :average, :minimum, :maximum, :sum, :calculate,
|
||||
:pluck, :pick, :ids, :strict_loading
|
||||
:pluck, :pick, :ids, :strict_loading, :excluding
|
||||
].freeze # :nodoc:
|
||||
delegate(*QUERYING_METHODS, to: :all)
|
||||
|
||||
|
|
|
@ -1105,6 +1105,51 @@ module ActiveRecord
|
|||
self
|
||||
end
|
||||
|
||||
# Excludes the specified record (or collection of records) from the resulting
|
||||
# relation. For example:
|
||||
#
|
||||
# Post.excluding(post)
|
||||
# # SELECT "posts".* FROM "posts" WHERE "posts"."id" != 1
|
||||
#
|
||||
# Post.excluding(post_one, post_two)
|
||||
# # SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
|
||||
#
|
||||
# This can also be called on associations. As with the above example, either
|
||||
# a single record of collection thereof may be specified:
|
||||
#
|
||||
# post = Post.find(1)
|
||||
# comment = Comment.find(2)
|
||||
# post.comments.excluding(comment)
|
||||
# # SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."id" != 2
|
||||
#
|
||||
# This is short-hand for <tt>.where.not(id: post.id)</tt> and <tt>.where.not(id: [post_one.id, post_two.id])</tt>.
|
||||
#
|
||||
# An <tt>ArgumentError</tt> will be raised if either no records are
|
||||
# specified, or if any of the records in the collection (if a collection
|
||||
# is passed in) are not instances of the same model that the relation is
|
||||
# scoping.
|
||||
def excluding(*records)
|
||||
records.flatten!(1)
|
||||
|
||||
raise ArgumentError, "You must pass at least one #{klass.name} object to #excluding." if records.empty?
|
||||
|
||||
if records.any? { |record| !record.is_a?(klass) }
|
||||
raise ArgumentError, "You must only pass a single or collection of #{klass.name} objects to #excluding."
|
||||
end
|
||||
|
||||
spawn.excluding!(records)
|
||||
end
|
||||
|
||||
def excluding!(records) # :nodoc:
|
||||
# Treat single and multiple records differently in order to keep query
|
||||
# clean in case of single record, ie, use != operator instead of NOT IN ().
|
||||
if records.one?
|
||||
where.not(primary_key => records.first.id)
|
||||
else
|
||||
where.not(primary_key => records)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the Arel object associated with the relation.
|
||||
def arel(aliases = nil) # :nodoc:
|
||||
@arel ||= build_arel(aliases)
|
||||
|
|
71
activerecord/test/cases/excluding_test.rb
Normal file
71
activerecord/test/cases/excluding_test.rb
Normal file
|
@ -0,0 +1,71 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "cases/helper"
|
||||
require "models/post"
|
||||
require "models/comment"
|
||||
|
||||
class ExcludingTest < ActiveRecord::TestCase
|
||||
fixtures :posts, :comments
|
||||
|
||||
def test_result_set_does_not_include_single_excluded_record
|
||||
post = posts(:welcome)
|
||||
|
||||
assert_not_includes Post.excluding(post).to_a, post
|
||||
end
|
||||
|
||||
def test_result_set_does_not_include_collection_of_excluded_records
|
||||
post_welcome = posts(:welcome)
|
||||
post_thinking = posts(:thinking)
|
||||
|
||||
relation = Post.excluding(post_welcome, post_thinking)
|
||||
|
||||
assert_not_includes relation.to_a, post_welcome
|
||||
assert_not_includes relation.to_a, post_thinking
|
||||
end
|
||||
|
||||
def test_result_set_through_association_does_not_include_single_excluded_record
|
||||
post = posts(:welcome)
|
||||
comment_greetings = comments(:greetings)
|
||||
comment_more_greetings = comments(:more_greetings)
|
||||
|
||||
relation = post.comments.excluding(comment_greetings)
|
||||
|
||||
assert_not_includes relation.to_a, comment_greetings
|
||||
assert_includes relation.to_a, comment_more_greetings
|
||||
end
|
||||
|
||||
def test_result_set_through_association_does_not_include_collection_of_excluded_records
|
||||
post = posts(:welcome)
|
||||
comment_greetings = comments(:greetings)
|
||||
comment_more_greetings = comments(:more_greetings)
|
||||
|
||||
relation = post.comments.excluding([comment_greetings, comment_more_greetings])
|
||||
|
||||
assert_not_includes relation.to_a, comment_greetings
|
||||
assert_not_includes relation.to_a, comment_more_greetings
|
||||
end
|
||||
|
||||
def test_raises_on_no_arguments
|
||||
exception = assert_raises ArgumentError do
|
||||
Post.excluding()
|
||||
end
|
||||
assert_equal "You must pass at least one Post object to #excluding.", exception.message
|
||||
end
|
||||
|
||||
def test_raises_on_empty_collection_argument
|
||||
exception = assert_raises ArgumentError do
|
||||
Post.excluding([])
|
||||
end
|
||||
assert_equal "You must pass at least one Post object to #excluding.", exception.message
|
||||
end
|
||||
|
||||
def test_raises_on_record_from_different_class
|
||||
post = posts(:welcome)
|
||||
comment = comments(:greetings)
|
||||
|
||||
exception = assert_raises ArgumentError do
|
||||
Post.excluding(post, comment)
|
||||
end
|
||||
assert_equal "You must only pass a single or collection of Post objects to #excluding.", exception.message
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue