diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index f2248efd71be9..860ceae04a8a2 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 22c3e6a3246ad..86eb8f35b5235 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -107,7 +107,6 @@ def calculate(operation, column_name, options = {}) 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,6 +138,10 @@ def calculate(operation, column_name, options = {}) # # SELECT people.id FROM people # # => [1, 2, 3] # + # 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 # # => ['admin', 'member', 'guest'] @@ -151,30 +154,35 @@ def calculate(operation, column_name, options = {}) # # 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.map! do |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 - if has_include?(column_name) - construct_relation_for_association_calculations.pluck(column_name) + if has_include?(column_names.first) + 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) + 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 + } } - } - - result.map do |attributes| - raise ArgumentError, "Pluck expects to select just one attribute: #{attributes.inspect}" unless attributes.one? + end - 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 + 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 f748b897eea1d..4df613488aa2a 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -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') end - def test_pluck_expects_a_single_selection - 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 @@ -546,4 +542,29 @@ def test_pluck_not_auto_table_name_prefix_if_column_included 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) + 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 diff --git a/guides/source/active_record_querying.textile b/guides/source/active_record_querying.textile index 4b14671efcbd1..101988c59e2b0 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+