Permalink
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...
xuanxu committed Jan 30, 2012
1 parent bb842e8 commit 8270e4a8ce8337fca98030953922e5992b06a3dd
View
@@ -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:
@@ -43,6 +43,7 @@ module ActiveRecord
autoload :AutosaveAssociation
autoload :Relation
autoload :NullRelation
autoload_under 'relation' do
autoload :QueryMethods
@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
module ActiveRecord
# = Active Record Null Relation
class NullRelation < Relation
def exec_queries
@records = []
end
end
end
@@ -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
@@ -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'

This comment has been minimized.

Show comment
Hide comment
@korny

korny Feb 6, 2012

Contributor

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

@korny

korny Feb 6, 2012

Contributor

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

This comment has been minimized.

Show comment
Hide comment
@xuanxu

xuanxu Feb 6, 2012

Contributor

is already changed via docrails :)

@xuanxu

xuanxu Feb 6, 2012

Contributor

is already changed via docrails :)

# 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
@@ -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

This comment has been minimized.

Show comment
Hide comment
@josevalim

josevalim Mar 23, 2012

Contributor

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

Contributor

josevalim replied Mar 23, 2012

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

This comment has been minimized.

Show comment
Hide comment
@xuanxu

xuanxu Mar 26, 2012

Contributor

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

Contributor

xuanxu replied Mar 26, 2012

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.