aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activemodel/test/cases/translation_test.rb17
-rw-r--r--activerecord/lib/active_record/associations/class_methods/join_dependency.rb37
-rw-r--r--activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb26
-rw-r--r--activerecord/lib/active_record/associations/class_methods/join_dependency/join_base.rb2
-rw-r--r--activerecord/lib/active_record/base.rb4
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb65
-rw-r--r--activesupport/lib/active_support/callbacks.rb92
8 files changed, 143 insertions, 102 deletions
diff --git a/activemodel/test/cases/translation_test.rb b/activemodel/test/cases/translation_test.rb
index ac2e56321e..c299d6eb5e 100644
--- a/activemodel/test/cases/translation_test.rb
+++ b/activemodel/test/cases/translation_test.rb
@@ -17,6 +17,23 @@ class ActiveModelI18nTests < ActiveModel::TestCase
assert_equal 'name default attribute', Person.human_attribute_name('name')
end
+ def test_translated_model_attributes_using_default_option
+ assert_equal 'name default attribute', Person.human_attribute_name('name', :default => "name default attribute")
+ end
+
+ def test_translated_model_attributes_using_default_option_as_symbol
+ I18n.backend.store_translations 'en', :default_name => 'name default attribute'
+ assert_equal 'name default attribute', Person.human_attribute_name('name', :default => :default_name)
+ end
+
+ def test_translated_model_attributes_falling_back_to_default
+ assert_equal 'Name', Person.human_attribute_name('name')
+ end
+
+ def test_translated_model_attributes_using_default_option_as_symbol_and_falling_back_to_default
+ assert_equal 'Name', Person.human_attribute_name('name', :default => :default_name)
+ end
+
def test_translated_model_attributes_with_symbols
I18n.backend.store_translations 'en', :activemodel => {:attributes => {:person => {:name => 'person name attribute'} } }
assert_equal 'person name attribute', Person.human_attribute_name(:name)
diff --git a/activerecord/lib/active_record/associations/class_methods/join_dependency.rb b/activerecord/lib/active_record/associations/class_methods/join_dependency.rb
index c578845878..13576e1aec 100644
--- a/activerecord/lib/active_record/associations/class_methods/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/class_methods/join_dependency.rb
@@ -6,14 +6,17 @@ module ActiveRecord
module Associations
module ClassMethods
class JoinDependency # :nodoc:
- attr_reader :join_parts, :reflections, :table_aliases
+ attr_reader :join_parts, :reflections, :table_aliases, :active_record
def initialize(base, associations, joins)
- @table_joins = joins || ''
+ @active_record = base
+ @table_joins = joins
@join_parts = [JoinBase.new(base)]
@associations = {}
@reflections = []
- @table_aliases = Hash.new(0)
+ @table_aliases = Hash.new do |h,name|
+ h[name] = count_aliases_from_table_joins(name)
+ end
@table_aliases[base.table_name] = 1
build(associations)
end
@@ -44,12 +47,26 @@ module ActiveRecord
end
def count_aliases_from_table_joins(name)
+ return 0 if !@table_joins || Arel::Table === @table_joins
+
+ @table_joins.grep(Arel::Nodes::Join).map { |join|
+ right = join.right
+ case right
+ when Arel::Table
+ right.name.downcase == name ? 1 : 0
+ when String
+ count_aliases_from_string(right.downcase, name)
+ else
+ 0
+ end
+ }.sum
+ end
+
+ def count_aliases_from_string(join_sql, name)
# quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
- quoted_name = join_base.active_record.connection.quote_table_name(name.downcase).downcase
- join_sql = @table_joins.downcase
- join_sql.blank? ? 0 :
- # Table names
- join_sql.scan(/join(?:\s+\w+)?\s+#{quoted_name}\son/).size +
+ quoted_name = active_record.connection.quote_table_name(name.downcase).downcase
+ # Table names
+ join_sql.scan(/join(?:\s+\w+)?\s+#{quoted_name}\son/).size +
# Table aliases
join_sql.scan(/join(?:\s+\w+)?\s+\S+\s+#{quoted_name}\son/).size
end
@@ -61,11 +78,11 @@ module ActiveRecord
records = rows.map { |model|
primary_id = model[primary_key]
parent = parents[primary_id] ||= join_base.instantiate(model)
- construct(parent, @associations, join_associations.dup, model)
+ construct(parent, @associations, join_associations, model)
parent
}.uniq
- remove_duplicate_results!(join_base.active_record, records, @associations)
+ remove_duplicate_results!(active_record, records, @associations)
records
end
diff --git a/activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb
index 4dd11a7366..25511a092f 100644
--- a/activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb
@@ -67,8 +67,7 @@ module ActiveRecord
def table
@table ||= Arel::Table.new(
- table_name, :as => aliased_table_name,
- :engine => arel_engine, :columns => active_record.columns
+ table_name, :as => aliased_table_name, :engine => arel_engine
)
end
@@ -78,23 +77,18 @@ module ActiveRecord
protected
def aliased_table_name_for(name, suffix = nil)
- if @join_dependency.table_aliases[name].zero?
- @join_dependency.table_aliases[name] = @join_dependency.count_aliases_from_table_joins(name)
- end
+ aliases = @join_dependency.table_aliases
- if !@join_dependency.table_aliases[name].zero? # We need an alias
- name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}#{suffix}"
- @join_dependency.table_aliases[name] += 1
- if @join_dependency.table_aliases[name] == 1 # First time we've seen this name
- # Also need to count the aliases from the table_aliases to avoid incorrect count
- @join_dependency.table_aliases[name] += @join_dependency.count_aliases_from_table_joins(name)
- end
- table_index = @join_dependency.table_aliases[name]
- name = name[0..active_record.connection.table_alias_length-3] + "_#{table_index}" if table_index > 1
- else
- @join_dependency.table_aliases[name] += 1
+ if aliases[name] != 0 # We need an alias
+ connection = active_record.connection
+
+ name = connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}#{suffix}"
+ table_index = aliases[name] + 1
+ name = name[0, connection.table_alias_length-3] + "_#{table_index}" if table_index > 1
end
+ aliases[name] += 1
+
name
end
diff --git a/activerecord/lib/active_record/associations/class_methods/join_dependency/join_base.rb b/activerecord/lib/active_record/associations/class_methods/join_dependency/join_base.rb
index 97003c1457..67567f06df 100644
--- a/activerecord/lib/active_record/associations/class_methods/join_dependency/join_base.rb
+++ b/activerecord/lib/active_record/associations/class_methods/join_dependency/join_base.rb
@@ -13,7 +13,7 @@ module ActiveRecord
end
def table
- Arel::Table.new(table_name, :engine => arel_engine, :columns => active_record.columns)
+ Arel::Table.new(table_name, arel_engine)
end
def aliased_table_name
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index ba7f6f0129..b55aaddbd7 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -1500,9 +1500,7 @@ MSG
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
def attributes
- attrs = {}
- attribute_names.each { |name| attrs[name] = read_attribute(name) }
- attrs
+ Hash[@attributes.map { |name, _| [name, read_attribute(name)] }]
end
# Returns an <tt>#inspect</tt>-like string for the value of the
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 51ba17c9be..906ad7699c 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -196,7 +196,7 @@ module ActiveRecord
def construct_relation_for_association_calculations
including = (@eager_load_values + @includes_values).uniq
- join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, arel.join_sql)
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, arel.froms.first)
relation = except(:includes, :eager_load, :preload)
apply_join_dependency(relation, join_dependency)
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 0483950db7..6886c7538b 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -162,27 +162,6 @@ module ActiveRecord
@arel ||= build_arel
end
- def custom_join_sql(joins)
- arel = table.select_manager
-
- joins.each do |join|
- next if join.blank?
-
- @implicit_readonly = true
-
- case join
- when Array
- join = Arel.sql(join.join(' ')) if array_of_strings?(join)
- when String
- join = Arel.sql(join)
- end
-
- arel.join(join)
- end
-
- arel.join_sql
- end
-
def build_arel
arel = table
@@ -209,6 +188,32 @@ module ActiveRecord
private
+ def custom_join_ast(table, joins)
+ joins = joins.reject { |join| join.blank? }
+
+ return if joins.empty?
+
+ @implicit_readonly = true
+
+ joins.map! do |join|
+ case join
+ when Array
+ join = Arel.sql(join.join(' ')) if array_of_strings?(join)
+ when String
+ join = Arel.sql(join)
+ end
+ join
+ end
+
+ head = table.create_string_join(table, joins.shift)
+
+ joins.inject(head) do |ast, join|
+ ast.right = table.create_string_join(ast.right, join)
+ end
+
+ head
+ end
+
def collapse_wheres(arel, wheres)
equalities = wheres.grep(Arel::Nodes::Equality)
@@ -252,19 +257,31 @@ module ActiveRecord
stashed_association_joins = joins.grep(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)
non_association_joins = (joins - association_joins - stashed_association_joins)
- custom_joins = custom_join_sql(non_association_joins)
+ join_ast = custom_join_ast(relation, non_association_joins)
- join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, custom_joins)
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, join_ast)
join_dependency.graft(*stashed_association_joins)
@implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
+ # FIXME: refactor this to build an AST
join_dependency.join_associations.each do |association|
relation = association.join_to(relation)
end
- relation.join(custom_joins)
+ if Arel::Table === relation
+ relation.from(join_ast || relation)
+ else
+ if relation.froms.length > 0 && join_ast
+ join_ast.left = relation.froms.first
+ relation.from join_ast
+ elsif relation.froms.length == 0 && join_ast
+ relation.from(join_ast)
+ else
+ relation
+ end
+ end
end
def build_select(arel, selects)
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 905dfb040b..844237fe6a 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -178,49 +178,48 @@ module ActiveSupport
# options[0] is the compiled form of supplied conditions
# options[1] is the "end" for the conditional
#
- if @kind == :before || @kind == :around
- if @kind == :before
- # if condition # before_save :filter_name, :if => :condition
- # filter_name
- # end
- filter = <<-RUBY_EVAL
- unless halted
- result = #{@filter}
- halted = (#{chain.config[:terminator]})
- end
- RUBY_EVAL
-
- [@compiled_options[0], filter, @compiled_options[1]].compact.join("\n")
- else
- # Compile around filters with conditions into proxy methods
- # that contain the conditions.
- #
- # For `around_save :filter_name, :if => :condition':
- #
- # def _conditional_callback_save_17
- # if condition
- # filter_name do
- # yield self
- # end
- # else
- # yield self
- # end
- # end
- #
- name = "_conditional_callback_#{@kind}_#{next_id}"
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def #{name}(halted)
- #{@compiled_options[0] || "if true"} && !halted
- #{@filter} do
- yield self
- end
- else
+ case @kind
+ when :before
+ # if condition # before_save :filter_name, :if => :condition
+ # filter_name
+ # end
+ filter = <<-RUBY_EVAL
+ unless halted
+ result = #{@filter}
+ halted = (#{chain.config[:terminator]})
+ end
+ RUBY_EVAL
+
+ [@compiled_options[0], filter, @compiled_options[1]].compact.join("\n")
+ when :around
+ # Compile around filters with conditions into proxy methods
+ # that contain the conditions.
+ #
+ # For `around_save :filter_name, :if => :condition':
+ #
+ # def _conditional_callback_save_17
+ # if condition
+ # filter_name do
+ # yield self
+ # end
+ # else
+ # yield self
+ # end
+ # end
+ #
+ name = "_conditional_callback_#{@kind}_#{next_id}"
+ @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
+ def #{name}(halted)
+ #{@compiled_options[0] || "if true"} && !halted
+ #{@filter} do
yield self
end
+ else
+ yield self
end
- RUBY_EVAL
- "#{name}(halted) do"
- end
+ end
+ RUBY_EVAL
+ "#{name}(halted) do"
end
end
@@ -229,15 +228,14 @@ module ActiveSupport
def end(key=nil, object=nil)
return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
- if @kind == :around || @kind == :after
+ case @kind
+ when :after
# if condition # after_save :filter_name, :if => :condition
# filter_name
# end
- if @kind == :after
- [@compiled_options[0], @filter, @compiled_options[1]].compact.join("\n")
- else
- "end"
- end
+ [@compiled_options[0], @filter, @compiled_options[1]].compact.join("\n")
+ when :around
+ "end"
end
end
@@ -388,7 +386,7 @@ module ActiveSupport
# key. See #define_callbacks for more information.
#
def __define_runner(symbol) #:nodoc:
- body = send("_#{symbol}_callbacks").compile(nil)
+ body = send("_#{symbol}_callbacks").compile
silence_warnings do
undef_method "_run_#{symbol}_callbacks" if method_defined?("_run_#{symbol}_callbacks")