diff options
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") |