aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
authorJon Leighton <j@jonathanleighton.com>2011-11-04 16:10:18 +0000
committerJon Leighton <j@jonathanleighton.com>2011-11-05 16:22:18 +0000
commit562583c7667f508493ab8c5b1a4215087fafd22d (patch)
treed61a983ca8e5b9d38164f34d1eb6c36b0f822be8 /activerecord
parent6aaae3de277b572f37e09f16ae12737c3c87dfb7 (diff)
downloadrails-562583c7667f508493ab8c5b1a4215087fafd22d.tar.gz
rails-562583c7667f508493ab8c5b1a4215087fafd22d.tar.bz2
rails-562583c7667f508493ab8c5b1a4215087fafd22d.zip
Add ActiveRecord::Relation#uniq for toggling DISTINCT in the SQL query
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md16
-rw-r--r--activerecord/lib/active_record/base.rb4
-rw-r--r--activerecord/lib/active_record/relation.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb30
-rw-r--r--activerecord/test/cases/base_test.rb6
-rw-r--r--activerecord/test/cases/relation_test.rb2
-rw-r--r--activerecord/test/cases/relations_test.rb16
7 files changed, 67 insertions, 9 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 426f6b48b3..2524082062 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,21 @@
## Rails 3.2.0 (unreleased) ##
+* Add ActiveRecord::Relation#uniq for generating unique queries.
+
+ Before:
+
+ Client.select('DISTINCT name')
+
+ After:
+
+ Client.select(:name).uniq
+
+ This also allows you to revert the unqueness in a relation:
+
+ Client.select(:name).uniq.uniq(false)
+
+ *Jon Leighton*
+
* Support index sort order in sqlite, mysql and postgres adapters. *Vlad Jebelev*
* Allow the :class_name option for associations to take a symbol (:Client) in addition to
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 360e494af1..455b299270 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -445,7 +445,9 @@ module ActiveRecord #:nodoc:
delegate :first_or_create, :first_or_create!, :first_or_initialize, :to => :scoped
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, :having, :create_with, :to => :scoped
+ delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins,
+ :where, :preload, :eager_load, :includes, :from, :lock, :readonly,
+ :having, :create_with, :uniq, :to => :scoped
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
# Executes a custom SQL query against your database and returns all the results. The results will
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index ecefaa633c..3baf9b3f49 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -7,7 +7,7 @@ module ActiveRecord
JoinOperation = Struct.new(:relation, :join_class, :on)
ASSOCIATION_METHODS = [:includes, :eager_load, :preload]
MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having, :bind]
- SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reorder, :reverse_order]
+ SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reorder, :reverse_order, :uniq]
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 670ba0987d..c281bead0d 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -9,7 +9,8 @@ module ActiveRecord
:select_values, :group_values, :order_values, :joins_values,
:where_values, :having_values, :bind_values,
:limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value,
- :from_value, :reorder_value, :reverse_order_value
+ :from_value, :reorder_value, :reverse_order_value,
+ :uniq_value
def includes(*args)
args.reject! {|a| a.blank? }
@@ -38,7 +39,7 @@ module ActiveRecord
end
# Works in two unique ways.
- #
+ #
# First: takes a block so it can be used just like Array#select.
#
# Model.scoped.select { |m| m.field == value }
@@ -176,9 +177,25 @@ module ActiveRecord
relation
end
+ # Specifies whether the records should be unique or not. For example:
+ #
+ # User.select(:name)
+ # # => Might return two records with the same name
+ #
+ # User.select(:name).uniq
+ # # => Returns 1 record per unique name
+ #
+ # User.select(:name).uniq.uniq(false)
+ # # => You can also remove the uniqueness
+ def uniq(value = true)
+ relation = clone
+ relation.uniq_value = value
+ relation
+ end
+
# Used to extend a scope with additional methods, either through
- # a module or through a block provided.
- #
+ # a module or through a block provided.
+ #
# The object returned is a relation, which can be further extended.
#
# === Using a module
@@ -200,7 +217,7 @@ module ActiveRecord
#
# scope = Model.scoped.extending do
# def page(number)
- # # pagination code goes here
+ # # pagination code goes here
# end
# end
# scope.page(params[:page])
@@ -209,7 +226,7 @@ module ActiveRecord
#
# scope = Model.scoped.extending(Pagination) do
# def per_page(number)
- # # pagination code goes here
+ # # pagination code goes here
# end
# end
def extending(*modules)
@@ -252,6 +269,7 @@ module ActiveRecord
build_select(arel, @select_values.uniq)
+ arel.distinct(@uniq_value)
arel.from(@from_value) if @from_value
arel.lock(@lock_value) if @lock_value
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 12c1cfb30e..fdb656fe13 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1935,4 +1935,10 @@ class BasicsTest < ActiveRecord::TestCase
dev.update_attribute(:updated_at, nil)
assert_match(/\/#{dev.id}$/, dev.cache_key)
end
+
+ def test_uniq_delegates_to_scoped
+ scope = stub
+ Bird.stubs(:scoped).returns(mock(:uniq => scope))
+ assert_equal scope, Bird.uniq
+ end
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index b23ead6feb..715a378431 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -20,7 +20,7 @@ module ActiveRecord
end
def test_single_values
- assert_equal [:limit, :offset, :lock, :readonly, :from, :reorder, :reverse_order].map(&:to_s).sort,
+ assert_equal [:limit, :offset, :lock, :readonly, :from, :reorder, :reverse_order, :uniq].map(&:to_s).sort,
Relation::SINGLE_VALUE_METHODS.map(&:to_s).sort
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 95408a5f29..bf1eb6386a 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1148,4 +1148,20 @@ class RelationTest < ActiveRecord::TestCase
assert_equal posts(:thinking), comments(:more_greetings).post
assert_equal posts(:welcome), comments(:greetings).post
end
+
+ def test_uniq
+ tag1 = Tag.create(:name => 'Foo')
+ tag2 = Tag.create(:name => 'Foo')
+
+ query = Tag.select(:name).where(:id => [tag1.id, tag2.id])
+
+ assert_equal ['Foo', 'Foo'], query.map(&:name)
+ assert_sql(/DISTINCT/) do
+ assert_equal ['Foo'], query.uniq.map(&:name)
+ end
+ assert_sql(/DISTINCT/) do
+ assert_equal ['Foo'], query.uniq(true).map(&:name)
+ end
+ assert_equal ['Foo', 'Foo'], query.uniq(true).uniq(false).map(&:name)
+ end
end