rename Relation#uniq
to Relation#distinct
. #uniq
still works.
The similarity of `Relation#uniq` to `Array#uniq` is confusing. Since our Relation API is close to SQL terms I renamed `#uniq` to `#distinct`. There is no deprecation. `#uniq` and `#uniq!` are aliases and will continue to work. I also updated the documentation to promote the use of `#distinct`.
This commit is contained in:
parent
bfee706b2e
commit
a1bb6c8b06
@ -1,5 +1,12 @@
|
||||
## Rails 4.0.0 (unreleased) ##
|
||||
|
||||
* Rename `Relation#uniq` to `Relation#distinct`. `#uniq` is still
|
||||
available as an alias but we encourage to use `#distinct` instead.
|
||||
Also `Relation#uniq_value` is aliased to `Relation#distinct_value`,
|
||||
this is a temporary solution and you should migrate to `distinct_value`.
|
||||
|
||||
*Yves Senn*
|
||||
|
||||
* Fix quoting for sqlite migrations using copy_table_contents() with binary
|
||||
columns.
|
||||
|
||||
|
@ -241,6 +241,7 @@ def association_instance_set(name, association)
|
||||
# others.destroy_all | X | X | X
|
||||
# others.find(*args) | X | X | X
|
||||
# others.exists? | X | X | X
|
||||
# others.distinct | X | X | X
|
||||
# others.uniq | X | X | X
|
||||
# others.reset | X | X | X
|
||||
#
|
||||
|
@ -174,7 +174,7 @@ def count(column_name = nil, count_options = {})
|
||||
|
||||
reflection.klass.count_by_sql(custom_counter_sql)
|
||||
else
|
||||
if association_scope.uniq_value
|
||||
if association_scope.distinct_value
|
||||
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
|
||||
column_name ||= reflection.klass.primary_key
|
||||
count_options[:distinct] = true
|
||||
@ -246,14 +246,14 @@ def destroy(*records)
|
||||
# +count_records+, which is a method descendants have to provide.
|
||||
def size
|
||||
if !find_target? || loaded?
|
||||
if association_scope.uniq_value
|
||||
if association_scope.distinct_value
|
||||
target.uniq.size
|
||||
else
|
||||
target.size
|
||||
end
|
||||
elsif !loaded? && !association_scope.group_values.empty?
|
||||
load_target.size
|
||||
elsif !loaded? && !association_scope.uniq_value && target.is_a?(Array)
|
||||
elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array)
|
||||
unsaved_records = target.select { |r| r.new_record? }
|
||||
unsaved_records.size + count_records
|
||||
else
|
||||
@ -306,12 +306,13 @@ def many?
|
||||
end
|
||||
end
|
||||
|
||||
def uniq
|
||||
def distinct
|
||||
seen = {}
|
||||
load_target.find_all do |record|
|
||||
seen[record.id] = true unless seen.key?(record.id)
|
||||
end
|
||||
end
|
||||
alias uniq distinct
|
||||
|
||||
# Replace this collection with +other_array+. This will perform a diff
|
||||
# and delete/add only records that have changed.
|
||||
@ -352,7 +353,7 @@ def add_to_target(record)
|
||||
callback(:before_add, record)
|
||||
yield(record) if block_given?
|
||||
|
||||
if association_scope.uniq_value && index = @target.index(record)
|
||||
if association_scope.distinct_value && index = @target.index(record)
|
||||
@target[index] = record
|
||||
else
|
||||
@target << record
|
||||
|
@ -649,11 +649,12 @@ def destroy(*records)
|
||||
# # #<Pet name: "Fancy-Fancy">
|
||||
# # ]
|
||||
#
|
||||
# person.pets.select(:name).uniq
|
||||
# person.pets.select(:name).distinct
|
||||
# # => [#<Pet name: "Fancy-Fancy">]
|
||||
def uniq
|
||||
@association.uniq
|
||||
def distinct
|
||||
@association.distinct
|
||||
end
|
||||
alias uniq distinct
|
||||
|
||||
# Count all records using SQL.
|
||||
#
|
||||
|
@ -6,7 +6,7 @@ class HasManyThrough < CollectionAssociation #:nodoc:
|
||||
|
||||
def associated_records_by_owner
|
||||
super.each do |owner, records|
|
||||
records.uniq! if reflection_scope.uniq_value
|
||||
records.uniq! if reflection_scope.distinct_value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -8,7 +8,7 @@ module Querying
|
||||
delegate :find_each, :find_in_batches, :to => :all
|
||||
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins,
|
||||
:where, :preload, :eager_load, :includes, :from, :lock, :readonly,
|
||||
:having, :create_with, :uniq, :references, :none, :to => :all
|
||||
:having, :create_with, :uniq, :distinct, :references, :none, :to => :all
|
||||
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :ids, :to => :all
|
||||
|
||||
# Executes a custom SQL query against your database and returns all the results. The results will
|
||||
|
@ -10,7 +10,7 @@ class Relation
|
||||
:extending]
|
||||
|
||||
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering,
|
||||
:reverse_order, :uniq, :create_with]
|
||||
:reverse_order, :distinct, :create_with]
|
||||
|
||||
VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS
|
||||
|
||||
@ -506,6 +506,12 @@ def joined_includes_values
|
||||
includes_values & joins_values
|
||||
end
|
||||
|
||||
# +uniq+ and +uniq!+ are silently deprecated. +uniq_value+ delegates to +distinct_value+
|
||||
# to maintain backwards compatibility. Use +distinct_value+ instead.
|
||||
def uniq_value
|
||||
distinct_value
|
||||
end
|
||||
|
||||
# Compares two relations for equality.
|
||||
def ==(other)
|
||||
case other
|
||||
|
@ -198,8 +198,8 @@ def has_include?(column_name)
|
||||
def perform_calculation(operation, column_name, options = {})
|
||||
operation = operation.to_s.downcase
|
||||
|
||||
# If #count is used in conjuction with #uniq it is considered distinct. (eg. relation.uniq.count)
|
||||
distinct = options[:distinct] || self.uniq_value
|
||||
# If #count is used with #distinct / #uniq it is considered distinct. (eg. relation.distinct.count)
|
||||
distinct = options[:distinct] || self.distinct_value
|
||||
|
||||
if operation == "count"
|
||||
column_name ||= (select_for_count || :all)
|
||||
|
@ -710,20 +710,22 @@ def from!(value, subquery_name = nil) # :nodoc:
|
||||
# User.select(:name)
|
||||
# # => Might return two records with the same name
|
||||
#
|
||||
# User.select(:name).uniq
|
||||
# # => Returns 1 record per unique name
|
||||
# User.select(:name).distinct
|
||||
# # => Returns 1 record per distinct name
|
||||
#
|
||||
# User.select(:name).uniq.uniq(false)
|
||||
# User.select(:name).distinct.distinct(false)
|
||||
# # => You can also remove the uniqueness
|
||||
def uniq(value = true)
|
||||
spawn.uniq!(value)
|
||||
def distinct(value = true)
|
||||
spawn.distinct!(value)
|
||||
end
|
||||
alias uniq distinct
|
||||
|
||||
# Like #uniq, but modifies relation in place.
|
||||
def uniq!(value = true) # :nodoc:
|
||||
self.uniq_value = value
|
||||
# Like #distinct, but modifies relation in place.
|
||||
def distinct!(value = true) # :nodoc:
|
||||
self.distinct_value = value
|
||||
self
|
||||
end
|
||||
alias uniq! distinct!
|
||||
|
||||
# Used to extend a scope with additional methods, either through
|
||||
# a module or through a block provided.
|
||||
@ -814,7 +816,7 @@ def build_arel
|
||||
|
||||
build_select(arel, select_values.uniq)
|
||||
|
||||
arel.distinct(uniq_value)
|
||||
arel.distinct(distinct_value)
|
||||
arel.from(build_from) if from_value
|
||||
arel.lock(lock_value) if lock_value
|
||||
|
||||
|
@ -316,7 +316,7 @@ def test_uniq_after_the_fact
|
||||
dev.projects << projects(:active_record)
|
||||
|
||||
assert_equal 3, dev.projects.size
|
||||
assert_equal 1, dev.projects.uniq.size
|
||||
assert_equal 1, dev.projects.distinct.size
|
||||
end
|
||||
|
||||
def test_uniq_before_the_fact
|
||||
|
@ -397,14 +397,14 @@ def test_has_many_through_polymorphic_has_one
|
||||
end
|
||||
|
||||
def test_has_many_through_polymorphic_has_many
|
||||
assert_equal taggings(:welcome_general, :thinking_general), authors(:david).taggings.uniq.sort_by { |t| t.id }
|
||||
assert_equal taggings(:welcome_general, :thinking_general), authors(:david).taggings.distinct.sort_by { |t| t.id }
|
||||
end
|
||||
|
||||
def test_include_has_many_through_polymorphic_has_many
|
||||
author = Author.includes(:taggings).find authors(:david).id
|
||||
expected_taggings = taggings(:welcome_general, :thinking_general)
|
||||
assert_no_queries do
|
||||
assert_equal expected_taggings, author.taggings.uniq.sort_by { |t| t.id }
|
||||
assert_equal expected_taggings, author.taggings.distinct.sort_by { |t| t.id }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -410,7 +410,7 @@ def test_nested_has_many_through_with_a_table_referenced_multiple_times
|
||||
|
||||
# Mary and Bob both have posts in misc, but they are the only ones.
|
||||
authors = Author.joins(:similar_posts).where('posts.id' => posts(:misc_by_bob).id)
|
||||
assert_equal [authors(:mary), authors(:bob)], authors.uniq.sort_by(&:id)
|
||||
assert_equal [authors(:mary), authors(:bob)], authors.distinct.sort_by(&:id)
|
||||
|
||||
# Check the polymorphism of taggings is being observed correctly (in both joins)
|
||||
authors = Author.joins(:similar_posts).where('taggings.taggable_type' => 'FakeModel')
|
||||
|
@ -1499,6 +1499,12 @@ def test_uniq_delegates_to_scoped
|
||||
assert_equal scope, Bird.uniq
|
||||
end
|
||||
|
||||
def test_distinct_delegates_to_scoped
|
||||
scope = stub
|
||||
Bird.stubs(:all).returns(mock(:distinct => scope))
|
||||
assert_equal scope, Bird.distinct
|
||||
end
|
||||
|
||||
def test_table_name_with_2_abstract_subclasses
|
||||
assert_equal "photos", Photo.table_name
|
||||
end
|
||||
|
@ -341,7 +341,8 @@ def test_count_with_column_parameter
|
||||
assert_equal 5, Account.count(:firm_id)
|
||||
end
|
||||
|
||||
def test_count_with_uniq
|
||||
def test_count_with_distinct
|
||||
assert_equal 4, Account.select(:credit_limit).distinct.count
|
||||
assert_equal 4, Account.select(:credit_limit).uniq.count
|
||||
end
|
||||
|
||||
|
@ -82,7 +82,7 @@ def test_exists_with_nil_arg
|
||||
|
||||
# ensures +exists?+ runs valid SQL by excluding order value
|
||||
def test_exists_with_order
|
||||
assert Topic.order(:id).uniq.exists?
|
||||
assert Topic.order(:id).distinct.exists?
|
||||
end
|
||||
|
||||
def test_exists_with_includes_limit_and_empty_result
|
||||
|
@ -278,5 +278,17 @@ def relation
|
||||
assert_equal [NullRelation], relation.extending_values
|
||||
assert relation.is_a?(NullRelation)
|
||||
end
|
||||
|
||||
test "distinct!" do
|
||||
relation.distinct! :foo
|
||||
assert_equal :foo, relation.distinct_value
|
||||
assert_equal :foo, relation.uniq_value # deprecated access
|
||||
end
|
||||
|
||||
test "uniq! was replaced by distinct!" do
|
||||
relation.uniq! :foo
|
||||
assert_equal :foo, relation.distinct_value
|
||||
assert_equal :foo, relation.uniq_value # deprecated access
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -492,6 +492,7 @@ def test_dynamic_find_by_attributes
|
||||
expected_taggings = taggings(:welcome_general, :thinking_general)
|
||||
|
||||
assert_no_queries do
|
||||
assert_equal expected_taggings, author.taggings.distinct.sort_by { |t| t.id }
|
||||
assert_equal expected_taggings, author.taggings.uniq.sort_by { |t| t.id }
|
||||
end
|
||||
|
||||
@ -1269,7 +1270,7 @@ def test_update_all_with_joins_and_offset_and_order
|
||||
assert_equal posts(:welcome), comments(:greetings).post
|
||||
end
|
||||
|
||||
def test_uniq
|
||||
def test_distinct
|
||||
tag1 = Tag.create(:name => 'Foo')
|
||||
tag2 = Tag.create(:name => 'Foo')
|
||||
|
||||
@ -1277,11 +1278,14 @@ def test_uniq
|
||||
|
||||
assert_equal ['Foo', 'Foo'], query.map(&:name)
|
||||
assert_sql(/DISTINCT/) do
|
||||
assert_equal ['Foo'], query.distinct.map(&:name)
|
||||
assert_equal ['Foo'], query.uniq.map(&:name)
|
||||
end
|
||||
assert_sql(/DISTINCT/) do
|
||||
assert_equal ['Foo'], query.distinct(true).map(&:name)
|
||||
assert_equal ['Foo'], query.uniq(true).map(&:name)
|
||||
end
|
||||
assert_equal ['Foo', 'Foo'], query.distinct(true).distinct(false).map(&:name)
|
||||
assert_equal ['Foo', 'Foo'], query.uniq(true).uniq(false).map(&:name)
|
||||
end
|
||||
|
||||
|
@ -30,8 +30,8 @@ class Author < ActiveRecord::Base
|
||||
has_many :comments_desc, -> { order('comments.id DESC') }, :through => :posts, :source => :comments
|
||||
has_many :limited_comments, -> { limit(1) }, :through => :posts, :source => :comments
|
||||
has_many :funky_comments, :through => :posts, :source => :comments
|
||||
has_many :ordered_uniq_comments, -> { uniq.order('comments.id') }, :through => :posts, :source => :comments
|
||||
has_many :ordered_uniq_comments_desc, -> { uniq.order('comments.id DESC') }, :through => :posts, :source => :comments
|
||||
has_many :ordered_uniq_comments, -> { distinct.order('comments.id') }, :through => :posts, :source => :comments
|
||||
has_many :ordered_uniq_comments_desc, -> { distinct.order('comments.id DESC') }, :through => :posts, :source => :comments
|
||||
has_many :readonly_comments, -> { readonly }, :through => :posts, :source => :comments
|
||||
|
||||
has_many :special_posts
|
||||
@ -78,7 +78,7 @@ class Author < ActiveRecord::Base
|
||||
has_many :categories_like_general, -> { where(:name => 'General') }, :through => :categorizations, :source => :category, :class_name => 'Category'
|
||||
|
||||
has_many :categorized_posts, :through => :categorizations, :source => :post
|
||||
has_many :unique_categorized_posts, -> { uniq }, :through => :categorizations, :source => :post
|
||||
has_many :unique_categorized_posts, -> { distinct }, :through => :categorizations, :source => :post
|
||||
|
||||
has_many :nothings, :through => :kateggorisatons, :class_name => 'Category'
|
||||
|
||||
@ -91,7 +91,7 @@ class Author < ActiveRecord::Base
|
||||
has_many :post_categories, :through => :posts, :source => :categories
|
||||
has_many :tagging_tags, :through => :taggings, :source => :tag
|
||||
|
||||
has_many :similar_posts, -> { uniq }, :through => :tags, :source => :tagged_posts
|
||||
has_many :similar_posts, -> { distinct }, :through => :tags, :source => :tagged_posts
|
||||
has_many :distinct_tags, -> { select("DISTINCT tags.*").order("tags.name") }, :through => :posts, :source => :tags
|
||||
|
||||
has_many :tags_with_primary_key, :through => :posts
|
||||
|
@ -2,7 +2,7 @@ class Book < ActiveRecord::Base
|
||||
has_many :authors
|
||||
|
||||
has_many :citations, :foreign_key => 'book1_id'
|
||||
has_many :references, -> { uniq }, :through => :citations, :source => :reference_of
|
||||
has_many :references, -> { distinct }, :through => :citations, :source => :reference_of
|
||||
|
||||
has_many :subscriptions
|
||||
has_many :subscribers, :through => :subscriptions
|
||||
|
@ -1,5 +1,4 @@
|
||||
class Liquid < ActiveRecord::Base
|
||||
self.table_name = :liquid
|
||||
has_many :molecules, -> { uniq }
|
||||
has_many :molecules, -> { distinct }
|
||||
end
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
class Project < ActiveRecord::Base
|
||||
has_and_belongs_to_many :developers, -> { uniq.order 'developers.name desc, developers.id desc' }
|
||||
has_and_belongs_to_many :developers, -> { distinct.order 'developers.name desc, developers.id desc' }
|
||||
has_and_belongs_to_many :readonly_developers, -> { readonly }, :class_name => "Developer"
|
||||
has_and_belongs_to_many :selected_developers, -> { uniq.select "developers.*" }, :class_name => "Developer"
|
||||
has_and_belongs_to_many :selected_developers, -> { distinct.select "developers.*" }, :class_name => "Developer"
|
||||
has_and_belongs_to_many :non_unique_developers, -> { order 'developers.name desc, developers.id desc' }, :class_name => 'Developer'
|
||||
has_and_belongs_to_many :limited_developers, -> { limit 1 }, :class_name => "Developer"
|
||||
has_and_belongs_to_many :developers_named_david, -> { where("name = 'David'").uniq }, :class_name => "Developer"
|
||||
has_and_belongs_to_many :developers_named_david_with_hash_conditions, -> { where(:name => 'David').uniq }, :class_name => "Developer"
|
||||
has_and_belongs_to_many :developers_named_david, -> { where("name = 'David'").distinct }, :class_name => "Developer"
|
||||
has_and_belongs_to_many :developers_named_david_with_hash_conditions, -> { where(:name => 'David').distinct }, :class_name => "Developer"
|
||||
has_and_belongs_to_many :salaried_developers, -> { where "salary > 0" }, :class_name => "Developer"
|
||||
|
||||
ActiveSupport::Deprecation.silence do
|
||||
|
@ -76,6 +76,7 @@ The methods are:
|
||||
* `reorder`
|
||||
* `reverse_order`
|
||||
* `select`
|
||||
* `distinct`
|
||||
* `uniq`
|
||||
* `where`
|
||||
|
||||
@ -580,10 +581,10 @@ ActiveModel::MissingAttributeError: missing attribute: <attribute>
|
||||
|
||||
Where `<attribute>` is the attribute you asked for. The `id` method will not raise the `ActiveRecord::MissingAttributeError`, so just be careful when working with associations because they need the `id` method to function properly.
|
||||
|
||||
If you would like to only grab a single record per unique value in a certain field, you can use `uniq`:
|
||||
If you would like to only grab a single record per unique value in a certain field, you can use `distinct`:
|
||||
|
||||
```ruby
|
||||
Client.select(:name).uniq
|
||||
Client.select(:name).distinct
|
||||
```
|
||||
|
||||
This would generate SQL like:
|
||||
@ -595,10 +596,10 @@ SELECT DISTINCT name FROM clients
|
||||
You can also remove the uniqueness constraint:
|
||||
|
||||
```ruby
|
||||
query = Client.select(:name).uniq
|
||||
query = Client.select(:name).distinct
|
||||
# => Returns unique names
|
||||
|
||||
query.uniq(false)
|
||||
query.distinct(false)
|
||||
# => Returns all names, even if there are duplicates
|
||||
```
|
||||
|
||||
@ -1438,7 +1439,7 @@ Client.where(active: true).pluck(:id)
|
||||
# SELECT id FROM clients WHERE active = 1
|
||||
# => [1, 2, 3]
|
||||
|
||||
Client.uniq.pluck(:role)
|
||||
Client.distinct.pluck(:role)
|
||||
# SELECT DISTINCT role FROM clients
|
||||
# => ['admin', 'member', 'guest']
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user