diff options
author | Pratik Naik <pratiknaik@gmail.com> | 2009-07-03 13:01:39 +0100 |
---|---|---|
committer | Pratik Naik <pratiknaik@gmail.com> | 2009-07-03 13:01:39 +0100 |
commit | 2fe263bb328c20539f2970057f31e567ec4ab7c8 (patch) | |
tree | 63e85164fb09aca6beb78e1a5c52424fa49ed098 /activerecord/lib/active_record/associations | |
parent | bf5ac9965f12840d469ef2a4a16e8205dbbe5253 (diff) | |
parent | a4bdc00fec623f72592e663e6d7830eea0bc6ea4 (diff) | |
download | rails-2fe263bb328c20539f2970057f31e567ec4ab7c8.tar.gz rails-2fe263bb328c20539f2970057f31e567ec4ab7c8.tar.bz2 rails-2fe263bb328c20539f2970057f31e567ec4ab7c8.zip |
Merge commit 'mainstream/master'
Conflicts:
actionpack/lib/action_controller.rb
actionpack/lib/action_controller/base/base.rb
actionpack/lib/action_view/template/path.rb
activesupport/lib/active_support/json/encoders/hash.rb
Diffstat (limited to 'activerecord/lib/active_record/associations')
7 files changed, 192 insertions, 193 deletions
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index c3e5bd8575..e67ccfb228 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -351,7 +351,19 @@ module ActiveRecord protected def construct_find_options!(options) end - + + def construct_counter_sql + if @reflection.options[:counter_sql] + @counter_sql = interpolate_sql(@reflection.options[:counter_sql]) + elsif @reflection.options[:finder_sql] + # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */ + @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" } + @counter_sql = interpolate_sql(@reflection.options[:counter_sql]) + else + @counter_sql = @finder_sql + end + end + def load_target if !@owner.new_record? || foreign_key_present begin diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index af9ce3dfb2..fd23e59e82 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -85,15 +85,7 @@ module ActiveRecord @join_sql = "INNER JOIN #{@owner.connection.quote_table_name @reflection.options[:join_table]} ON #{@reflection.quoted_table_name}.#{@reflection.klass.primary_key} = #{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.association_foreign_key}" - if @reflection.options[:counter_sql] - @counter_sql = interpolate_sql(@reflection.options[:counter_sql]) - elsif @reflection.options[:finder_sql] - # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */ - @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" } - @counter_sql = interpolate_sql(@reflection.options[:counter_sql]) - else - @counter_sql = @finder_sql - end + construct_counter_sql end def construct_scope diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index 73dd50dd07..e4b631bc54 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -97,15 +97,7 @@ module ActiveRecord @finder_sql << " AND (#{conditions})" if conditions end - if @reflection.options[:counter_sql] - @counter_sql = interpolate_sql(@reflection.options[:counter_sql]) - elsif @reflection.options[:finder_sql] - # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */ - @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" } - @counter_sql = interpolate_sql(@reflection.options[:counter_sql]) - else - @counter_sql = @finder_sql - end + construct_counter_sql end def construct_scope diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index e8dbae9011..e21ef90391 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -1,6 +1,10 @@ +require "active_record/associations/through_association_scope" + module ActiveRecord module Associations class HasManyThroughAssociation < HasManyAssociation #:nodoc: + include ThroughAssociationScope + alias_method :new, :build def create!(attrs = nil) @@ -72,114 +76,7 @@ module ActiveRecord def find_target return [] unless target_reflection_has_associated_record? - @reflection.klass.find(:all, - :select => construct_select, - :conditions => construct_conditions, - :from => construct_from, - :joins => construct_joins, - :order => @reflection.options[:order], - :limit => @reflection.options[:limit], - :group => @reflection.options[:group], - :readonly => @reflection.options[:readonly], - :include => @reflection.options[:include] || @reflection.source_reflection.options[:include] - ) - end - - # Construct attributes for associate pointing to owner. - def construct_owner_attributes(reflection) - if as = reflection.options[:as] - { "#{as}_id" => @owner.id, - "#{as}_type" => @owner.class.base_class.name.to_s } - else - { reflection.primary_key_name => @owner.id } - end - end - - # Construct attributes for :through pointing to owner and associate. - def construct_join_attributes(associate) - # TODO: revist this to allow it for deletion, supposing dependent option is supported - raise ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection.new(@owner, @reflection) if @reflection.source_reflection.macro == :has_many - join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id) - if @reflection.options[:source_type] - join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s) - end - join_attributes - end - - # Associate attributes pointing to owner, quoted. - def construct_quoted_owner_attributes(reflection) - if as = reflection.options[:as] - { "#{as}_id" => owner_quoted_id, - "#{as}_type" => reflection.klass.quote_value( - @owner.class.base_class.name.to_s, - reflection.klass.columns_hash["#{as}_type"]) } - elsif reflection.macro == :belongs_to - { reflection.klass.primary_key => @owner[reflection.primary_key_name] } - else - { reflection.primary_key_name => owner_quoted_id } - end - end - - # Build SQL conditions from attributes, qualified by table name. - def construct_conditions - table_name = @reflection.through_reflection.quoted_table_name - conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value| - "#{table_name}.#{attr} = #{value}" - end - conditions << sql_conditions if sql_conditions - "(" + conditions.join(') AND (') + ")" - end - - def construct_from - @reflection.quoted_table_name - end - - def construct_select(custom_select = nil) - distinct = "DISTINCT " if @reflection.options[:uniq] - selected = custom_select || @reflection.options[:select] || "#{distinct}#{@reflection.quoted_table_name}.*" - end - - def construct_joins(custom_joins = nil) - polymorphic_join = nil - if @reflection.source_reflection.macro == :belongs_to - reflection_primary_key = @reflection.klass.primary_key - source_primary_key = @reflection.source_reflection.primary_key_name - if @reflection.options[:source_type] - polymorphic_join = "AND %s.%s = %s" % [ - @reflection.through_reflection.quoted_table_name, "#{@reflection.source_reflection.options[:foreign_type]}", - @owner.class.quote_value(@reflection.options[:source_type]) - ] - end - else - reflection_primary_key = @reflection.source_reflection.primary_key_name - source_primary_key = @reflection.through_reflection.klass.primary_key - if @reflection.source_reflection.options[:as] - polymorphic_join = "AND %s.%s = %s" % [ - @reflection.quoted_table_name, "#{@reflection.source_reflection.options[:as]}_type", - @owner.class.quote_value(@reflection.through_reflection.klass.name) - ] - end - end - - "INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]} #{custom_joins}" % [ - @reflection.through_reflection.quoted_table_name, - @reflection.quoted_table_name, reflection_primary_key, - @reflection.through_reflection.quoted_table_name, source_primary_key, - polymorphic_join - ] - end - - def construct_scope - { :create => construct_owner_attributes(@reflection), - :find => { :from => construct_from, - :conditions => construct_conditions, - :joins => construct_joins, - :include => @reflection.options[:include], - :select => construct_select, - :order => @reflection.options[:order], - :limit => @reflection.options[:limit], - :readonly => @reflection.options[:readonly], - } } + with_scope(construct_scope) { @reflection.klass.find(:all) } end def construct_sql @@ -193,59 +90,9 @@ module ActiveRecord @finder_sql = construct_conditions end - if @reflection.options[:counter_sql] - @counter_sql = interpolate_sql(@reflection.options[:counter_sql]) - elsif @reflection.options[:finder_sql] - # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */ - @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" } - @counter_sql = interpolate_sql(@reflection.options[:counter_sql]) - else - @counter_sql = @finder_sql - end - end - - def conditions - @conditions = build_conditions unless defined?(@conditions) - @conditions + construct_counter_sql end - def build_conditions - association_conditions = @reflection.options[:conditions] - through_conditions = build_through_conditions - source_conditions = @reflection.source_reflection.options[:conditions] - uses_sti = !@reflection.through_reflection.klass.descends_from_active_record? - - if association_conditions || through_conditions || source_conditions || uses_sti - all = [] - - [association_conditions, source_conditions].each do |conditions| - all << interpolate_sql(sanitize_sql(conditions)) if conditions - end - - all << through_conditions if through_conditions - all << build_sti_condition if uses_sti - - all.map { |sql| "(#{sql})" } * ' AND ' - end - end - - def build_through_conditions - conditions = @reflection.through_reflection.options[:conditions] - if conditions.is_a?(Hash) - interpolate_sql(sanitize_sql(conditions)).gsub( - @reflection.quoted_table_name, - @reflection.through_reflection.quoted_table_name) - elsif conditions - interpolate_sql(sanitize_sql(conditions)) - end - end - - def build_sti_condition - @reflection.through_reflection.klass.send(:type_condition) - end - - alias_method :sql_conditions, :conditions - def has_cached_counter? @owner.attribute_present?(cached_counter_attribute_name) end diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index b72b84343b..c2568d0c0c 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -1,6 +1,6 @@ module ActiveRecord module Associations - class HasOneAssociation < BelongsToAssociation #:nodoc: + class HasOneAssociation < AssociationProxy #:nodoc: def initialize(owner, reflection) super construct_sql @@ -77,7 +77,7 @@ module ActiveRecord the_target = @reflection.klass.find(:first, :conditions => @finder_sql, :select => @reflection.options[:select], - :order => @reflection.options[:order], + :order => @reflection.options[:order], :include => @reflection.options[:include], :readonly => @reflection.options[:readonly] ) @@ -88,7 +88,7 @@ module ActiveRecord def construct_sql case when @reflection.options[:as] - @finder_sql = + @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " + "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}" else @@ -96,7 +96,7 @@ module ActiveRecord end @finder_sql << " AND (#{conditions})" if conditions end - + def construct_scope create_scoping = {} set_belongs_to_association_for(create_scoping) @@ -113,7 +113,7 @@ module ActiveRecord end if replace_existing - replace(record, true) + replace(record, true) else record[@reflection.primary_key_name] = @owner.id unless @owner.new_record? self.target = record diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb index d93c8e7852..830aa1808a 100644 --- a/activerecord/lib/active_record/associations/has_one_through_association.rb +++ b/activerecord/lib/active_record/associations/has_one_through_association.rb @@ -1,6 +1,16 @@ +require "active_record/associations/through_association_scope" + module ActiveRecord module Associations - class HasOneThroughAssociation < HasManyThroughAssociation + class HasOneThroughAssociation < HasOneAssociation + include ThroughAssociationScope + + def replace(new_value) + create_through_record(new_value) + @target = new_value + end + + private def create_through_record(new_value) #nodoc: klass = @reflection.through_reflection.klass @@ -15,16 +25,8 @@ module ActiveRecord end private - def find(*args) - super(args.merge(:limit => 1)) - end - def find_target - super.first - end - - def reset_target! - @target = nil + with_scope(construct_scope) { @reflection.klass.find(:first) } end end end diff --git a/activerecord/lib/active_record/associations/through_association_scope.rb b/activerecord/lib/active_record/associations/through_association_scope.rb new file mode 100644 index 0000000000..8e7ce33814 --- /dev/null +++ b/activerecord/lib/active_record/associations/through_association_scope.rb @@ -0,0 +1,154 @@ +module ActiveRecord + module Associations + module ThroughAssociationScope + + protected + + def construct_scope + { :create => construct_owner_attributes(@reflection), + :find => { :from => construct_from, + :conditions => construct_conditions, + :joins => construct_joins, + :include => @reflection.options[:include] || @reflection.source_reflection.options[:include], + :select => construct_select, + :order => @reflection.options[:order], + :limit => @reflection.options[:limit], + :readonly => @reflection.options[:readonly], + } } + end + + # Build SQL conditions from attributes, qualified by table name. + def construct_conditions + table_name = @reflection.through_reflection.quoted_table_name + conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value| + "#{table_name}.#{attr} = #{value}" + end + conditions << sql_conditions if sql_conditions + "(" + conditions.join(') AND (') + ")" + end + + # Associate attributes pointing to owner, quoted. + def construct_quoted_owner_attributes(reflection) + if as = reflection.options[:as] + { "#{as}_id" => owner_quoted_id, + "#{as}_type" => reflection.klass.quote_value( + @owner.class.base_class.name.to_s, + reflection.klass.columns_hash["#{as}_type"]) } + elsif reflection.macro == :belongs_to + { reflection.klass.primary_key => @owner[reflection.primary_key_name] } + else + { reflection.primary_key_name => owner_quoted_id } + end + end + + def construct_from + @reflection.quoted_table_name + end + + def construct_select(custom_select = nil) + distinct = "DISTINCT " if @reflection.options[:uniq] + selected = custom_select || @reflection.options[:select] || "#{distinct}#{@reflection.quoted_table_name}.*" + end + + def construct_joins(custom_joins = nil) + polymorphic_join = nil + if @reflection.source_reflection.macro == :belongs_to + reflection_primary_key = @reflection.klass.primary_key + source_primary_key = @reflection.source_reflection.primary_key_name + if @reflection.options[:source_type] + polymorphic_join = "AND %s.%s = %s" % [ + @reflection.through_reflection.quoted_table_name, "#{@reflection.source_reflection.options[:foreign_type]}", + @owner.class.quote_value(@reflection.options[:source_type]) + ] + end + else + reflection_primary_key = @reflection.source_reflection.primary_key_name + source_primary_key = @reflection.through_reflection.klass.primary_key + if @reflection.source_reflection.options[:as] + polymorphic_join = "AND %s.%s = %s" % [ + @reflection.quoted_table_name, "#{@reflection.source_reflection.options[:as]}_type", + @owner.class.quote_value(@reflection.through_reflection.klass.name) + ] + end + end + + "INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]} #{custom_joins}" % [ + @reflection.through_reflection.quoted_table_name, + @reflection.quoted_table_name, reflection_primary_key, + @reflection.through_reflection.quoted_table_name, source_primary_key, + polymorphic_join + ] + end + + # Construct attributes for associate pointing to owner. + def construct_owner_attributes(reflection) + if as = reflection.options[:as] + { "#{as}_id" => @owner.id, + "#{as}_type" => @owner.class.base_class.name.to_s } + else + { reflection.primary_key_name => @owner.id } + end + end + + # Construct attributes for :through pointing to owner and associate. + def construct_join_attributes(associate) + # TODO: revist this to allow it for deletion, supposing dependent option is supported + raise ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection.new(@owner, @reflection) if @reflection.source_reflection.macro == :has_many + + join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id) + + if @reflection.options[:source_type] + join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s) + end + + if @reflection.through_reflection.options[:conditions].is_a?(Hash) + join_attributes.merge!(@reflection.through_reflection.options[:conditions]) + end + + join_attributes + end + + def conditions + @conditions = build_conditions unless defined?(@conditions) + @conditions + end + + def build_conditions + association_conditions = @reflection.options[:conditions] + through_conditions = build_through_conditions + source_conditions = @reflection.source_reflection.options[:conditions] + uses_sti = !@reflection.through_reflection.klass.descends_from_active_record? + + if association_conditions || through_conditions || source_conditions || uses_sti + all = [] + + [association_conditions, source_conditions].each do |conditions| + all << interpolate_sql(sanitize_sql(conditions)) if conditions + end + + all << through_conditions if through_conditions + all << build_sti_condition if uses_sti + + all.map { |sql| "(#{sql})" } * ' AND ' + end + end + + def build_through_conditions + conditions = @reflection.through_reflection.options[:conditions] + if conditions.is_a?(Hash) + interpolate_sql(sanitize_sql(conditions)).gsub( + @reflection.quoted_table_name, + @reflection.through_reflection.quoted_table_name) + elsif conditions + interpolate_sql(sanitize_sql(conditions)) + end + end + + def build_sti_condition + @reflection.through_reflection.klass.send(:type_condition) + end + + alias_method :sql_conditions, :conditions + end + end +end |