From 2e379c1e63b3646f9aff4d7e242ca37b4a57f529 Mon Sep 17 00:00:00 2001 From: jeroeningen Date: Sat, 26 May 2012 19:17:05 +0200 Subject: ActiveRecord#pluck now accepts multiple columns --- .../lib/active_record/relation/calculations.rb | 50 +++++++++++++++------- activerecord/test/cases/calculations_test.rb | 17 +++++++- 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 22c3e6a324..0abf57cd91 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -139,6 +139,12 @@ module ActiveRecord # # SELECT people.id FROM people # # => [1, 2, 3] # + # Person.pluck([:id, :name]) + # # SELECT people.id, people.name FROM people + # + # Person.pluck(:id, :name) + # # SELECT people.id, people.name FROM people + # # Person.uniq.pluck(:role) # # SELECT DISTINCT role FROM people # # => ['admin', 'member', 'guest'] @@ -151,29 +157,43 @@ module ActiveRecord # # SELECT DATEDIFF(updated_at, created_at) FROM people # # => ['0', '27761', '173'] # - def pluck(column_name) - if column_name.is_a?(Symbol) && column_names.include?(column_name.to_s) - column_name = "#{table_name}.#{column_name}" + def pluck(*column_names) + column_names = column_names.flatten + + if column_names.first.is_a?(Symbol) && self.column_names.include?(column_names.first.to_s) + if column_names.one? + column_names = "#{table_name}.#{column_names.first}" + else + column_names = column_names.collect{|column_name| "#{table_name}.#{column_name}"} + end end - if has_include?(column_name) - construct_relation_for_association_calculations.pluck(column_name) + if has_include?(column_names) + construct_relation_for_association_calculations.pluck(column_names) else - result = klass.connection.select_all(select(column_name).arel, nil, bind_values) - - key = result.columns.first - column = klass.column_types.fetch(key) { - result.column_types.fetch(key) { - Class.new { def type_cast(v); v; end }.new + result = klass.connection.select_all(select(column_names).arel, nil, bind_values) + + keys = column_names.is_a?(Array) && !column_names.one? ? result.columns : [result.columns.first] + columns = keys.map do |key| + klass.column_types.fetch(key) { + result.column_types.fetch(key) { + 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? + if attributes.one? + value = klass.initialize_attributes(attributes).values.first - value = klass.initialize_attributes(attributes).values.first + columns.first.type_cast(value) + else + values = klass.initialize_attributes(attributes).values - column.type_cast(value) + values.each_with_index.map do |value, i| + columns[i].type_cast(value) + end + end end end end diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index f748b897ee..e86cf33b66 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -532,7 +532,7 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal [50 + 53 + 55 + 60], Account.pluck('SUM(DISTINCT(credit_limit)) as credit_limit') end - def test_pluck_expects_a_single_selection + def test_pluck_expects_a_multiple_selection_as_array assert_raise(ArgumentError) { Account.pluck 'id, credit_limit' } end @@ -546,4 +546,19 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal Company.count, ids.length assert_equal [7], ids.compact 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]) + 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) + end end -- cgit v1.2.3 From 6aae17e85613fe8c2816ba278f9348f168692479 Mon Sep 17 00:00:00 2001 From: Carlos Antonio da Silva Date: Tue, 19 Jun 2012 22:12:01 -0300 Subject: Refactor pluck with multiple columns Ensure it works with mix of symbols and strings, and with a select clause possibly containing more than one column. Also remove support for pluck with an array of columns, in favor of passing the list of attributes: Model.pluck(:a, :b) See comments: https://github.com/rails/rails/pull/6500#issuecomment-6030292 --- .../lib/active_record/relation/calculations.rb | 39 ++++++++-------------- activerecord/test/cases/calculations_test.rb | 26 +++++++++------ 2 files changed, 29 insertions(+), 36 deletions(-) diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 0abf57cd91..9c27392299 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -107,7 +107,6 @@ module ActiveRecord relation = with_default_scope if relation.equal?(self) - if has_include?(column_name) construct_relation_for_association_calculations.calculate(operation, column_name, options) else @@ -139,9 +138,6 @@ module ActiveRecord # # SELECT people.id FROM people # # => [1, 2, 3] # - # Person.pluck([:id, :name]) - # # SELECT people.id, people.name FROM people - # # Person.pluck(:id, :name) # # SELECT people.id, people.name FROM people # @@ -158,23 +154,19 @@ module ActiveRecord # # => ['0', '27761', '173'] # def pluck(*column_names) - column_names = column_names.flatten - - if column_names.first.is_a?(Symbol) && self.column_names.include?(column_names.first.to_s) - if column_names.one? - column_names = "#{table_name}.#{column_names.first}" + column_names.map! do |column_name| + if column_name.is_a?(Symbol) && self.column_names.include?(column_name.to_s) + "#{table_name}.#{column_name}" else - column_names = column_names.collect{|column_name| "#{table_name}.#{column_name}"} + column_name end end - if has_include?(column_names) - construct_relation_for_association_calculations.pluck(column_names) + if has_include?(column_names.first) + construct_relation_for_association_calculations.pluck(*column_names) else - result = klass.connection.select_all(select(column_names).arel, nil, bind_values) - - keys = column_names.is_a?(Array) && !column_names.one? ? result.columns : [result.columns.first] - columns = keys.map do |key| + result = klass.connection.select_all(select(column_names).arel, nil, bind_values) + columns = result.columns.map do |key| klass.column_types.fetch(key) { result.column_types.fetch(key) { Class.new { def type_cast(v); v; end }.new @@ -182,19 +174,14 @@ module ActiveRecord } end - result.map do |attributes| - if attributes.one? - value = klass.initialize_attributes(attributes).values.first - - columns.first.type_cast(value) - else - values = klass.initialize_attributes(attributes).values + result = result.map do |attributes| + values = klass.initialize_attributes(attributes).values - values.each_with_index.map do |value, i| - columns[i].type_cast(value) - end + columns.zip(values).map do |column, value| + column.type_cast(value) end end + columns.one? ? result.map!(&:first) : result end end diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index e86cf33b66..4df613488a 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -532,10 +532,6 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal [50 + 53 + 55 + 60], Account.pluck('SUM(DISTINCT(credit_limit)) as credit_limit') end - def test_pluck_expects_a_multiple_selection_as_array - assert_raise(ArgumentError) { Account.pluck 'id, credit_limit' } - end - def test_plucks_with_ids assert_equal Company.all.map(&:id).sort, Company.ids.sort end @@ -551,14 +547,24 @@ class CalculationsTest < ActiveRecord::TestCase 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]) + ], 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]) - 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) + ], 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 -- cgit v1.2.3 From e5cd300becab8e05f4568a402e3fce4f4497733a Mon Sep 17 00:00:00 2001 From: Carlos Antonio da Silva Date: Fri, 22 Jun 2012 09:30:00 -0300 Subject: Add changelog entry and guide updates for pluck with multiple columns --- activerecord/CHANGELOG.md | 9 +++++++++ .../lib/active_record/relation/calculations.rb | 1 + guides/source/active_record_querying.textile | 22 ++++++++++++++++------ 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index f2248efd71..860ceae04a 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,14 @@ ## 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. 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. diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 9c27392299..86eb8f35b5 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -140,6 +140,7 @@ module ActiveRecord # # Person.pluck(:id, :name) # # SELECT people.id, people.name FROM people + # # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']] # # Person.uniq.pluck(:role) # # SELECT DISTINCT role FROM people diff --git a/guides/source/active_record_querying.textile b/guides/source/active_record_querying.textile index 4b14671efc..101988c59e 100644 --- a/guides/source/active_record_querying.textile +++ b/guides/source/active_record_querying.textile @@ -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: -SELECT date(created_at) as ordered_date, sum(price) as total_price -FROM orders +SELECT date(created_at) as ordered_date, sum(price) as total_price +FROM orders GROUP BY date(created_at) @@ -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: -SELECT date(created_at) as ordered_date, sum(price) as total_price -FROM orders -GROUP BY date(created_at) +SELECT date(created_at) as ordered_date, sum(price) as total_price +FROM orders +GROUP BY date(created_at) HAVING sum(price) > 100 @@ -1286,26 +1286,36 @@ Client.connection.select_all("SELECT * FROM clients WHERE id = '1'") h3. +pluck+ -pluck 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. +pluck 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. Client.where(:active => true).pluck(:id) # SELECT id FROM clients WHERE active = 1 +# => [1, 2, 3] Client.uniq.pluck(:role) # 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']] +pluck+ makes it possible to replace code like Client.select(:id).map { |c| c.id } +# or +Client.select(:id).map { |c| [c.id, c.name] } with Client.pluck(:id) +# or +Client.pluck(:id, :name) h3. +ids+ -- cgit v1.2.3