From 54122067acaad39b277a5363c6d11d6804c7bf6b Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Wed, 1 May 2013 11:33:11 -0700 Subject: Handle aliased attributes in ActiveRecord::Relation. When using symbol keys, ActiveRecord will now translate aliased attribute names to the actual column name used in the database: With the model class Topic alias_attribute :heading, :title end The call Topic.where(heading: 'The First Topic') should yield the same result as Topic.where(title: 'The First Topic') This also applies to ActiveRecord::Relation::Calculations calls such as `Model.sum(:aliased)` and `Model.pluck(:aliased)`. This will not work with SQL fragment strings like `Model.sum('DISTINCT aliased')`. Github #7839 *Godfrey Chan* --- activerecord/CHANGELOG.md | 24 ++++++++++++++++++++ .../lib/active_record/relation/calculations.rb | 26 +++++++++++++++------- .../active_record/relation/predicate_builder.rb | 4 ++++ activerecord/test/cases/calculations_test.rb | 12 ++++++++++ activerecord/test/cases/finder_test.rb | 3 ++- activerecord/test/cases/relation/where_test.rb | 8 +++++++ activerecord/test/models/company.rb | 2 ++ 7 files changed, 70 insertions(+), 9 deletions(-) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 9f10a31512..c1b849ce68 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,27 @@ +* Handle aliased attributes in ActiveRecord::Relation. + + When using symbol keys, ActiveRecord will now translate aliased attribute names to the actual column name used in the database: + + With the model + + class Topic + alias_attribute :heading, :title + end + + The call + + Topic.where(heading: 'The First Topic') + + should yield the same result as + + Topic.where(title: 'The First Topic') + + This also applies to ActiveRecord::Relation::Calculations calls such as `Model.sum(:aliased)` and `Model.pluck(:aliased)`. + + This will not work with SQL fragment strings like `Model.sum('DISTINCT aliased')`. + + *Godfrey Chan* + * Mute `psql` output when running rake db:schema:load. *Godfrey Chan* diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 64e1ff9a6a..7239270c4d 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -27,7 +27,7 @@ module ActiveRecord # Calculates the average value on a given column. Returns +nil+ if there's # no row. See +calculate+ for examples with options. # - # Person.average('age') # => 35.8 + # Person.average(:age) # => 35.8 def average(column_name, options = {}) calculate(:average, column_name, options) end @@ -36,7 +36,7 @@ module ActiveRecord # with the same data type of the column, or +nil+ if there's no row. See # +calculate+ for examples with options. # - # Person.minimum('age') # => 7 + # Person.minimum(:age) # => 7 def minimum(column_name, options = {}) calculate(:minimum, column_name, options) end @@ -45,7 +45,7 @@ module ActiveRecord # with the same data type of the column, or +nil+ if there's no row. See # +calculate+ for examples with options. # - # Person.maximum('age') # => 93 + # Person.maximum(:age) # => 93 def maximum(column_name, options = {}) calculate(:maximum, column_name, options) end @@ -54,7 +54,7 @@ module ActiveRecord # with the same data type of the column, 0 if there's no row. See # +calculate+ for examples with options. # - # Person.sum('age') # => 4562 + # Person.sum(:age) # => 4562 def sum(*args) if block_given? ActiveSupport::Deprecation.warn( @@ -101,6 +101,10 @@ module ActiveRecord def calculate(operation, column_name, options = {}) relation = with_default_scope + if column_name.is_a?(Symbol) && attribute_aliases.key?(column_name.to_s) + column_name = attribute_aliases[column_name.to_s].to_sym + end + if relation.equal?(self) if has_include?(column_name) construct_relation_for_association_calculations.calculate(operation, column_name, options) @@ -149,11 +153,17 @@ module ActiveRecord # def pluck(*column_names) column_names.map! do |column_name| - if column_name.is_a?(Symbol) && self.column_names.include?(column_name.to_s) - "#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(column_name)}" - else - column_name + if column_name.is_a?(Symbol) + if attribute_aliases.key?(column_name.to_s) + column_name = attribute_aliases[column_name.to_s].to_sym + end + + if self.columns_hash.key?(column_name.to_s) + column_name = "#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(column_name)}" + end end + + column_name end if has_include?(column_names.first) diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index f44d46d15b..b7609c97b5 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -6,6 +6,10 @@ module ActiveRecord attributes.each do |column, value| table = default_table + if column.is_a?(Symbol) && klass.attribute_aliases.key?(column.to_s) + column = klass.attribute_aliases[column.to_s] + end + if value.is_a?(Hash) if value.empty? queries << '1=0' diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index b0b647cbf7..f49bef2346 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -28,6 +28,10 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal 53.0, value end + def test_should_resolve_aliased_attributes + assert_equal 318, Account.sum(:available_credit) + end + def test_should_return_decimal_average_of_integer_field value = Account.average(:id) assert_equal 3.5, value @@ -352,6 +356,10 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal 4, Account.select(:credit_limit).uniq.count end + def test_count_with_aliased_attribute + assert_equal 6, Account.count(:available_credit) + end + def test_count_with_column_and_options_parameter assert_equal 2, Account.where("credit_limit = 50 AND firm_id IS NOT NULL").count(:firm_id) end @@ -488,6 +496,10 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal [contract.id], company.contracts.pluck(:id) end + def test_pluck_on_aliased_attribute + assert_equal 'The First Topic', Topic.order(:id).pluck(:heading).first + end + def test_pluck_with_serialization t = Topic.create!(:content => { :foo => :bar }) assert_equal [{:foo => :bar}], Topic.where(:id => t.id).pluck(:content) diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 557cc7e7a0..7db9aef218 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -47,7 +47,8 @@ class FinderTest < ActiveRecord::TestCase def test_exists assert Topic.exists?(1) assert Topic.exists?("1") - assert Topic.exists?(:author_name => "David") + assert Topic.exists?(title: "The First Topic") + assert Topic.exists?(heading: "The First Topic") assert Topic.exists?(:author_name => "Mary", :approved => true) assert Topic.exists?(["parent_id = ?", 1]) assert !Topic.exists?(45) diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index c43c7601a2..d333be3560 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -5,6 +5,7 @@ require 'models/treasure' require 'models/post' require 'models/comment' require 'models/edge' +require 'models/topic' module ActiveRecord class WhereTest < ActiveRecord::TestCase @@ -80,6 +81,13 @@ module ActiveRecord assert_equal expected.to_sql, actual.to_sql end + def test_aliased_attribute + expected = Topic.where(heading: 'The First Topic') + actual = Topic.where(title: 'The First Topic') + + assert_equal expected.to_sql, actual.to_sql + end + def test_where_error assert_raises(ActiveRecord::StatementInvalid) do Post.where(:id => { 'posts.author_id' => 10 }).first diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 3ca8f69646..dcda62e71d 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -206,6 +206,8 @@ class Account < ActiveRecord::Base belongs_to :firm, :class_name => 'Company' belongs_to :unautosaved_firm, :foreign_key => "firm_id", :class_name => "Firm", :autosave => false + alias_attribute :available_credit, :credit_limit + def self.destroyed_account_ids @destroyed_account_ids ||= Hash.new { |h,k| h[k] = [] } end -- cgit v1.2.3