Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added `none` query method to return zero records.

And added NullRelation class implementing the null object pattern for the `Relation` class.
  • Loading branch information...
commit 8270e4a8ce8337fca98030953922e5992b06a3dd 1 parent bb842e8
@xuanxu xuanxu authored
View
13 activerecord/CHANGELOG.md
@@ -1,5 +1,18 @@
## Rails 4.0.0 (unreleased) ##
+* Implemented ActiveRecord::Relation#none method
+
+ The `none` method returns a chainable relation with zero records
+ (an instance of the NullRelation class).
+
+ Any subsequent condition chained to the returned relation will continue
+ generating an empty relation and will not fire any query to the database.
+
+ *Juanjo Bazán*
+
+* Added the `ActiveRecord::NullRelation` class implementing the null
+ object pattern for the Relation class. *Juanjo Bazán*
+
* Added deprecation for the `:dependent => :restrict` association option.
Please note:
View
1  activerecord/lib/active_record.rb
@@ -43,6 +43,7 @@ module ActiveRecord
autoload :AutosaveAssociation
autoload :Relation
+ autoload :NullRelation
autoload_under 'relation' do
autoload :QueryMethods
View
10 activerecord/lib/active_record/null_relation.rb
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+
+module ActiveRecord
+ # = Active Record Null Relation
+ class NullRelation < Relation
+ def exec_queries
+ @records = []
+ end
+ end
+end
View
2  activerecord/lib/active_record/querying.rb
@@ -8,7 +8,7 @@ module Querying
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,
- :having, :create_with, :uniq, :references, :to => :scoped
+ :having, :create_with, :uniq, :references, :none, :to => :scoped
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :to => :scoped
# Executes a custom SQL query against your database and returns all the results. The results will
View
33 activerecord/lib/active_record/relation/query_methods.rb
@@ -196,6 +196,39 @@ def lock(locks = true)
relation
end
+ # Returns a chainable relation with zero records, specifically an
+ # instance of the NullRelation class.
+ #
+ # The returned NullRelation inherits from Relation and implements the
+ # Null Object pattern so it is an object with defined null behavior:
+ # it always returns an empty array of records and avoids any database query.
+ #
+ # Any subsequent condition chained to the returned relation will continue
+ # generating an empty relation and will not fire any query to the database.
+ #
+ # Used in cases where is needed a method or a scope that could return zero
+ # results but the response has to be chainable.
+ #
+ # For example:
+ #
+ # @posts = current_user.visible_posts.where(:name => params[:name])
+ # # => the visible_post method response has to be a chainable Relation
+ #
+ # def visible_posts
+ # case role
+ # if 'Country Manager'
@korny
korny added a note

If think "if" should be "when" ;-)

@xuanxu
xuanxu added a note

is already changed via docrails :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ # Post.where(:country => country)
+ # if 'Reviewer'
+ # Post.published
+ # if 'Bad User'
+ # Post.none # => returning [] instead breaks the previous code
+ # end
+ # end
+ #
+ def none
+ NullRelation.new(@klass, @table)
+ end
+
def readonly(value = true)
relation = clone
relation.readonly_value = value
View
13 activerecord/test/cases/relations_test.rb
@@ -215,6 +215,19 @@ def test_select_with_block
assert_equal [2, 4, 6, 8, 10], even_ids.sort
end
+ def test_none
+ assert_no_queries do
+ assert_equal [], Developer.none
+ assert_equal [], Developer.scoped.none
+ end
+ end
+
+ def test_none_chainable
+ assert_no_queries do
+ assert_equal [], Developer.none.where(:name => 'David')
+ end
+ end
+
def test_joins_with_nil_argument
assert_nothing_raised { DependentFirm.joins(nil).first }
end

2 comments on commit 8270e4a

@josevalim
Owner

I love this pull request but while trying it out I have noticed there is still a long way to go. Many relation methods that does a query straight to the database need to overridden, otherwise none doesn't work as expected. For example, Post.none.pluck(:id) should return an empty array, but it doesn't. I guess other methods like update_all, delete will all have the same issue. /cc @jonleighton

@xuanxu

Thanks @josevalim
You are right, I see a couple of options here:

1) we can modify every method doing straight database queries (select_all, update_all, delete_all, etc...). That would solve the problem, but it'll modify core methods (like execute) and probably we'll need to change every database adapter.

2) We can add a false condition to the NullRelation class (something like where('1<0')) so once none is called if you fire a query directly to the db, it will always return zero records.

I'm +1 for this second option, it is simpler and keeps all none-related code in the NullRelation class. Here is the pull request for it:
#5590

Please sign in to comment.
Something went wrong with that request. Please try again.