Skip to content

Commit

Permalink
Merge branch 'pluck-multiple-columns'
Browse files Browse the repository at this point in the history
Allow ActiveRecord::Relation#pluck to accept multiple columns. Returns an
array of arrays containing the type casted values:

    Person.pluck(:id, :name)
    # SELECT people.id, people.name FROM people
    # [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]

Closes rails#6500
  • Loading branch information
carlosantoniodasilva committed Jun 22, 2012
2 parents 8b173f3 + e5cd300 commit d59b2ab
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 28 deletions.
9 changes: 9 additions & 0 deletions activerecord/CHANGELOG.md
@@ -1,5 +1,14 @@
## Rails 4.0.0 (unreleased) ## ## Rails 4.0.0 (unreleased) ##


* Allow ActiveRecord::Relation#pluck to accept multiple columns. Returns an
array of arrays containing the type casted values:

Person.pluck(:id, :name)
# SELECT people.id, people.name FROM people
# [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]

*Jeroen van Ingen & Carlos Antonio da Silva*

* Improve the derivation of HABTM join table name to take account of nesting. * Improve the derivation of HABTM join table name to take account of nesting.
It now takes the table names of the two models, sorts them lexically and It now takes the table names of the two models, sorts them lexically and
then joins them, stripping any common prefix from the second table name. then joins them, stripping any common prefix from the second table name.
Expand Down
44 changes: 26 additions & 18 deletions activerecord/lib/active_record/relation/calculations.rb
Expand Up @@ -107,7 +107,6 @@ def calculate(operation, column_name, options = {})
relation = with_default_scope relation = with_default_scope


if relation.equal?(self) if relation.equal?(self)

if has_include?(column_name) if has_include?(column_name)
construct_relation_for_association_calculations.calculate(operation, column_name, options) construct_relation_for_association_calculations.calculate(operation, column_name, options)
else else
Expand Down Expand Up @@ -139,6 +138,10 @@ def calculate(operation, column_name, options = {})
# # SELECT people.id FROM people # # SELECT people.id FROM people
# # => [1, 2, 3] # # => [1, 2, 3]
# #
# Person.pluck(:id, :name)
# # SELECT people.id, people.name FROM people
# # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
#
# Person.uniq.pluck(:role) # Person.uniq.pluck(:role)
# # SELECT DISTINCT role FROM people # # SELECT DISTINCT role FROM people
# # => ['admin', 'member', 'guest'] # # => ['admin', 'member', 'guest']
Expand All @@ -151,30 +154,35 @@ def calculate(operation, column_name, options = {})
# # SELECT DATEDIFF(updated_at, created_at) FROM people # # SELECT DATEDIFF(updated_at, created_at) FROM people
# # => ['0', '27761', '173'] # # => ['0', '27761', '173']
# #
def pluck(column_name) def pluck(*column_names)
if column_name.is_a?(Symbol) && column_names.include?(column_name.to_s) column_names.map! do |column_name|
column_name = "#{table_name}.#{column_name}" if column_name.is_a?(Symbol) && self.column_names.include?(column_name.to_s)
"#{table_name}.#{column_name}"
else
column_name
end
end end


if has_include?(column_name) if has_include?(column_names.first)
construct_relation_for_association_calculations.pluck(column_name) construct_relation_for_association_calculations.pluck(*column_names)
else else
result = klass.connection.select_all(select(column_name).arel, nil, bind_values) result = klass.connection.select_all(select(column_names).arel, nil, bind_values)

columns = result.columns.map do |key|
key = result.columns.first klass.column_types.fetch(key) {
column = klass.column_types.fetch(key) { result.column_types.fetch(key) {
result.column_types.fetch(key) { Class.new { def type_cast(v); v; end }.new
Class.new { def type_cast(v); v; end }.new }
} }
} end

result.map do |attributes|
raise ArgumentError, "Pluck expects to select just one attribute: #{attributes.inspect}" unless attributes.one?


value = klass.initialize_attributes(attributes).values.first result = result.map do |attributes|
values = klass.initialize_attributes(attributes).values


column.type_cast(value) columns.zip(values).map do |column, value|
column.type_cast(value)
end
end end
columns.one? ? result.map!(&:first) : result
end end
end end


Expand Down
29 changes: 25 additions & 4 deletions activerecord/test/cases/calculations_test.rb
Expand Up @@ -532,10 +532,6 @@ def test_pluck_with_selection_clause
assert_equal [50 + 53 + 55 + 60], Account.pluck('SUM(DISTINCT(credit_limit)) as credit_limit') assert_equal [50 + 53 + 55 + 60], Account.pluck('SUM(DISTINCT(credit_limit)) as credit_limit')
end end


def test_pluck_expects_a_single_selection
assert_raise(ArgumentError) { Account.pluck 'id, credit_limit' }
end

def test_plucks_with_ids def test_plucks_with_ids
assert_equal Company.all.map(&:id).sort, Company.ids.sort assert_equal Company.all.map(&:id).sort, Company.ids.sort
end end
Expand All @@ -546,4 +542,29 @@ def test_pluck_not_auto_table_name_prefix_if_column_included
assert_equal Company.count, ids.length assert_equal Company.count, ids.length
assert_equal [7], ids.compact assert_equal [7], ids.compact
end end

def test_pluck_multiple_columns
assert_equal [
[1, "The First Topic"], [2, "The Second Topic of the day"],
[3, "The Third Topic of the day"], [4, "The Fourth Topic of the day"]
], Topic.order(:id).pluck(:id, :title)
assert_equal [
[1, "The First Topic", "David"], [2, "The Second Topic of the day", "Mary"],
[3, "The Third Topic of the day", "Carl"], [4, "The Fourth Topic of the day", "Carl"]
], Topic.order(:id).pluck(:id, :title, :author_name)
end

def test_pluck_with_multiple_columns_and_selection_clause
assert_equal [[1, 50], [2, 50], [3, 50], [4, 60], [5, 55], [6, 53]],
Account.pluck('id, credit_limit')
end

def test_pluck_with_multiple_columns_and_includes
Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
companies_and_developers = Company.order('companies.id').includes(:contracts).pluck(:name, :developer_id)

assert_equal Company.count, companies_and_developers.length
assert_equal ["37signals", nil], companies_and_developers.first
assert_equal ["test", 7], companies_and_developers.last
end
end end
22 changes: 16 additions & 6 deletions guides/source/active_record_querying.textile
Expand Up @@ -609,8 +609,8 @@ And this will give you a single +Order+ object for each date where there are ord
The SQL that would be executed would be something like this: The SQL that would be executed would be something like this:


<sql> <sql>
SELECT date(created_at) as ordered_date, sum(price) as total_price SELECT date(created_at) as ordered_date, sum(price) as total_price
FROM orders FROM orders
GROUP BY date(created_at) GROUP BY date(created_at)
</sql> </sql>


Expand All @@ -627,9 +627,9 @@ Order.select("date(created_at) as ordered_date, sum(price) as total_price").grou
The SQL that would be executed would be something like this: The SQL that would be executed would be something like this:


<sql> <sql>
SELECT date(created_at) as ordered_date, sum(price) as total_price SELECT date(created_at) as ordered_date, sum(price) as total_price
FROM orders FROM orders
GROUP BY date(created_at) GROUP BY date(created_at)
HAVING sum(price) > 100 HAVING sum(price) > 100
</sql> </sql>


Expand Down Expand Up @@ -1286,26 +1286,36 @@ Client.connection.select_all("SELECT * FROM clients WHERE id = '1'")


h3. +pluck+ h3. +pluck+


<tt>pluck</tt> can be used to query a single column from the underlying table of a model. It accepts a column name as argument and returns an array of values of the specified column with the corresponding data type. <tt>pluck</tt> can be used to query a single or multiple columns from the underlying table of a model. It accepts a list of column names as argument and returns an array of values of the specified columns with the corresponding data type.


<ruby> <ruby>
Client.where(:active => true).pluck(:id) Client.where(:active => true).pluck(:id)
# SELECT id FROM clients WHERE active = 1 # SELECT id FROM clients WHERE active = 1
# => [1, 2, 3]


Client.uniq.pluck(:role) Client.uniq.pluck(:role)
# SELECT DISTINCT role FROM clients # SELECT DISTINCT role FROM clients
# => ['admin', 'member', 'guest']

Client.pluck(:id, :name)
# SELECT clients.id, clients.name FROM clients
# => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
</ruby> </ruby>


+pluck+ makes it possible to replace code like +pluck+ makes it possible to replace code like


<ruby> <ruby>
Client.select(:id).map { |c| c.id } Client.select(:id).map { |c| c.id }
# or
Client.select(:id).map { |c| [c.id, c.name] }
</ruby> </ruby>


with with


<ruby> <ruby>
Client.pluck(:id) Client.pluck(:id)
# or
Client.pluck(:id, :name)
</ruby> </ruby>


h3. +ids+ h3. +ids+
Expand Down

0 comments on commit d59b2ab

Please sign in to comment.