Skip to content

Commit dd1f360

Browse files
committed
Merge pull request rails#7839 from chancancode/handle_aliased_attributes_in_ar_relation
Handle aliased attributes in AR::Relation
2 parents 14a75a5 + 5412206 commit dd1f360

File tree

7 files changed

+70
-9
lines changed

7 files changed

+70
-9
lines changed

activerecord/CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,27 @@
1+
* Handle aliased attributes in ActiveRecord::Relation.
2+
3+
When using symbol keys, ActiveRecord will now translate aliased attribute names to the actual column name used in the database:
4+
5+
With the model
6+
7+
class Topic
8+
alias_attribute :heading, :title
9+
end
10+
11+
The call
12+
13+
Topic.where(heading: 'The First Topic')
14+
15+
should yield the same result as
16+
17+
Topic.where(title: 'The First Topic')
18+
19+
This also applies to ActiveRecord::Relation::Calculations calls such as `Model.sum(:aliased)` and `Model.pluck(:aliased)`.
20+
21+
This will not work with SQL fragment strings like `Model.sum('DISTINCT aliased')`.
22+
23+
*Godfrey Chan*
24+
125
* Mute `psql` output when running rake db:schema:load.
226

327
*Godfrey Chan*

activerecord/lib/active_record/relation/calculations.rb

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def count(column_name = nil, options = {})
2727
# Calculates the average value on a given column. Returns +nil+ if there's
2828
# no row. See +calculate+ for examples with options.
2929
#
30-
# Person.average('age') # => 35.8
30+
# Person.average(:age) # => 35.8
3131
def average(column_name, options = {})
3232
calculate(:average, column_name, options)
3333
end
@@ -36,7 +36,7 @@ def average(column_name, options = {})
3636
# with the same data type of the column, or +nil+ if there's no row. See
3737
# +calculate+ for examples with options.
3838
#
39-
# Person.minimum('age') # => 7
39+
# Person.minimum(:age) # => 7
4040
def minimum(column_name, options = {})
4141
calculate(:minimum, column_name, options)
4242
end
@@ -45,7 +45,7 @@ def minimum(column_name, options = {})
4545
# with the same data type of the column, or +nil+ if there's no row. See
4646
# +calculate+ for examples with options.
4747
#
48-
# Person.maximum('age') # => 93
48+
# Person.maximum(:age) # => 93
4949
def maximum(column_name, options = {})
5050
calculate(:maximum, column_name, options)
5151
end
@@ -54,7 +54,7 @@ def maximum(column_name, options = {})
5454
# with the same data type of the column, 0 if there's no row. See
5555
# +calculate+ for examples with options.
5656
#
57-
# Person.sum('age') # => 4562
57+
# Person.sum(:age) # => 4562
5858
def sum(*args)
5959
if block_given?
6060
ActiveSupport::Deprecation.warn(
@@ -101,6 +101,10 @@ def sum(*args)
101101
def calculate(operation, column_name, options = {})
102102
relation = with_default_scope
103103

104+
if column_name.is_a?(Symbol) && attribute_aliases.key?(column_name.to_s)
105+
column_name = attribute_aliases[column_name.to_s].to_sym
106+
end
107+
104108
if relation.equal?(self)
105109
if has_include?(column_name)
106110
construct_relation_for_association_calculations.calculate(operation, column_name, options)
@@ -149,11 +153,17 @@ def calculate(operation, column_name, options = {})
149153
#
150154
def pluck(*column_names)
151155
column_names.map! do |column_name|
152-
if column_name.is_a?(Symbol) && self.column_names.include?(column_name.to_s)
153-
"#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(column_name)}"
154-
else
155-
column_name
156+
if column_name.is_a?(Symbol)
157+
if attribute_aliases.key?(column_name.to_s)
158+
column_name = attribute_aliases[column_name.to_s].to_sym
159+
end
160+
161+
if self.columns_hash.key?(column_name.to_s)
162+
column_name = "#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(column_name)}"
163+
end
156164
end
165+
166+
column_name
157167
end
158168

159169
if has_include?(column_names.first)

activerecord/lib/active_record/relation/predicate_builder.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ def self.build_from_hash(klass, attributes, default_table)
66
attributes.each do |column, value|
77
table = default_table
88

9+
if column.is_a?(Symbol) && klass.attribute_aliases.key?(column.to_s)
10+
column = klass.attribute_aliases[column.to_s]
11+
end
12+
913
if value.is_a?(Hash)
1014
if value.empty?
1115
queries << '1=0'

activerecord/test/cases/calculations_test.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ def test_should_average_field
2828
assert_equal 53.0, value
2929
end
3030

31+
def test_should_resolve_aliased_attributes
32+
assert_equal 318, Account.sum(:available_credit)
33+
end
34+
3135
def test_should_return_decimal_average_of_integer_field
3236
value = Account.average(:id)
3337
assert_equal 3.5, value
@@ -352,6 +356,10 @@ def test_count_with_distinct
352356
assert_equal 4, Account.select(:credit_limit).uniq.count
353357
end
354358

359+
def test_count_with_aliased_attribute
360+
assert_equal 6, Account.count(:available_credit)
361+
end
362+
355363
def test_count_with_column_and_options_parameter
356364
assert_equal 2, Account.where("credit_limit = 50 AND firm_id IS NOT NULL").count(:firm_id)
357365
end
@@ -488,6 +496,10 @@ def test_pluck_in_relation
488496
assert_equal [contract.id], company.contracts.pluck(:id)
489497
end
490498

499+
def test_pluck_on_aliased_attribute
500+
assert_equal 'The First Topic', Topic.order(:id).pluck(:heading).first
501+
end
502+
491503
def test_pluck_with_serialization
492504
t = Topic.create!(:content => { :foo => :bar })
493505
assert_equal [{:foo => :bar}], Topic.where(:id => t.id).pluck(:content)

activerecord/test/cases/finder_test.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ def test_find_with_string
4747
def test_exists
4848
assert Topic.exists?(1)
4949
assert Topic.exists?("1")
50-
assert Topic.exists?(:author_name => "David")
50+
assert Topic.exists?(title: "The First Topic")
51+
assert Topic.exists?(heading: "The First Topic")
5152
assert Topic.exists?(:author_name => "Mary", :approved => true)
5253
assert Topic.exists?(["parent_id = ?", 1])
5354
assert !Topic.exists?(45)

activerecord/test/cases/relation/where_test.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
require 'models/post'
66
require 'models/comment'
77
require 'models/edge'
8+
require 'models/topic'
89

910
module ActiveRecord
1011
class WhereTest < ActiveRecord::TestCase
@@ -80,6 +81,13 @@ def test_polymorphic_sti_nested_where
8081
assert_equal expected.to_sql, actual.to_sql
8182
end
8283

84+
def test_aliased_attribute
85+
expected = Topic.where(heading: 'The First Topic')
86+
actual = Topic.where(title: 'The First Topic')
87+
88+
assert_equal expected.to_sql, actual.to_sql
89+
end
90+
8391
def test_where_error
8492
assert_raises(ActiveRecord::StatementInvalid) do
8593
Post.where(:id => { 'posts.author_id' => 10 }).first

activerecord/test/models/company.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,8 @@ class Account < ActiveRecord::Base
206206
belongs_to :firm, :class_name => 'Company'
207207
belongs_to :unautosaved_firm, :foreign_key => "firm_id", :class_name => "Firm", :autosave => false
208208

209+
alias_attribute :available_credit, :credit_limit
210+
209211
def self.destroyed_account_ids
210212
@destroyed_account_ids ||= Hash.new { |h,k| h[k] = [] }
211213
end

0 commit comments

Comments
 (0)