diff options
author | Pratik Naik <pratiknaik@gmail.com> | 2008-10-05 19:46:48 +0100 |
---|---|---|
committer | Pratik Naik <pratiknaik@gmail.com> | 2008-10-05 19:46:48 +0100 |
commit | 6090513cfb8acb5554a6653a6f2cb87648585d41 (patch) | |
tree | 99bfd589a48153e33f19ae72baa6e98f5708a9b8 /activerecord/lib | |
parent | 01159a6431bbc2dc7d7d95ce294c8567c954f39e (diff) | |
parent | 4df45d86097efbeabceecfe53d8ea2da9ccbb107 (diff) | |
download | rails-6090513cfb8acb5554a6653a6f2cb87648585d41.tar.gz rails-6090513cfb8acb5554a6653a6f2cb87648585d41.tar.bz2 rails-6090513cfb8acb5554a6653a6f2cb87648585d41.zip |
Merge commit 'mainstream/master'
Conflicts:
activerecord/lib/active_record/association_preload.rb
Diffstat (limited to 'activerecord/lib')
10 files changed, 150 insertions, 99 deletions
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index cef8cd8647..99e3973830 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -161,12 +161,13 @@ module ActiveRecord # the objects' IDs to the relevant objects. Returns a 2-tuple # <tt>(id_to_record_map, ids)</tt> where +id_to_record_map+ is the Hash, # and +ids+ is an Array of record IDs. - def construct_id_map(records) + def construct_id_map(records, primary_key=nil) id_to_record_map = {} ids = [] records.each do |record| - ids << record.id - mapped_records = (id_to_record_map[record.id.to_s] ||= []) + primary_key ||= record.class.primary_key + ids << record[primary_key] + mapped_records = (id_to_record_map[ids.last.to_s] ||= []) mapped_records << record end ids.uniq! @@ -180,7 +181,7 @@ module ActiveRecord options = reflection.options conditions = "t0.#{reflection.primary_key_name} #{in_or_equals_for_ids(ids)}" - conditions << append_conditions(options, preload_options) + conditions << append_conditions(reflection, preload_options) associated_records = reflection.klass.find(:all, :conditions => [conditions, ids], :include => options[:include], @@ -213,23 +214,24 @@ module ActiveRecord end def preload_has_many_association(records, reflection, preload_options={}) - id_to_record_map, ids = construct_id_map(records) - records.each {|record| record.send(reflection.name).loaded} options = reflection.options + primary_key_name = reflection.through_reflection_primary_key_name + id_to_record_map, ids = construct_id_map(records, primary_key_name) + records.each {|record| record.send(reflection.name).loaded} + if options[:through] through_records = preload_through_records(records, reflection, options[:through]) through_reflection = reflections[options[:through]] - through_primary_key = through_reflection.primary_key_name unless through_records.empty? source = reflection.source_reflection.name - #add conditions from reflection! - through_records.first.class.preload_associations(through_records, source, reflection.options) + through_records.first.class.preload_associations(through_records, source, options) through_records.each do |through_record| - add_preloaded_records_to_collection(id_to_record_map[through_record[through_primary_key].to_s], - reflection.name, through_record.send(source)) + through_record_id = through_record[reflection.through_reflection_primary_key].to_s + add_preloaded_records_to_collection(id_to_record_map[through_record_id], reflection.name, through_record.send(source)) end end + else set_association_collection_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options), reflection.primary_key_name) @@ -317,7 +319,7 @@ module ActiveRecord end end conditions = "#{table_name}.#{connection.quote_column_name(primary_key)} #{in_or_equals_for_ids(ids)}" - conditions << append_conditions(options, preload_options) + conditions << append_conditions(reflection, preload_options) associated_records = klass.find(:all, :conditions => [conditions, ids], :include => options[:include], :select => options[:select], @@ -338,7 +340,7 @@ module ActiveRecord conditions = "#{reflection.klass.quoted_table_name}.#{foreign_key} #{in_or_equals_for_ids(ids)}" end - conditions << append_conditions(options, preload_options) + conditions << append_conditions(reflection, preload_options) reflection.klass.find(:all, :select => (preload_options[:select] || options[:select] || "#{table_name}.*"), @@ -354,9 +356,9 @@ module ActiveRecord instance_eval("%@#{sql.gsub('@', '\@')}@") end - def append_conditions(options, preload_options) + def append_conditions(reflection, preload_options) sql = "" - sql << " AND (#{interpolate_sql_for_preload(sanitize_sql(options[:conditions]))})" if options[:conditions] + sql << " AND (#{interpolate_sql_for_preload(reflection.sanitized_conditions)})" if reflection.sanitized_conditions sql << " AND (#{sanitize_sql preload_options[:conditions]})" if preload_options[:conditions] sql end diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index acdcd14ec8..b617147f67 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -240,7 +240,7 @@ module ActiveRecord # the kind of the class of the associated objects. Meant to be used as # a sanity check when you are about to assign an associated record. def raise_on_type_mismatch(record) - unless record.is_a?(@reflection.klass) + unless record.is_a?(@reflection.klass) || record.is_a?(@reflection.class_name.constantize) message = "#{@reflection.class_name}(##{@reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})" raise ActiveRecord::AssociationTypeMismatch, message end 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 3171665e19..a0bb3a45b0 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -32,6 +32,14 @@ module ActiveRecord end protected + def target_reflection_has_associated_record? + if @reflection.through_reflection.macro == :belongs_to && @owner[@reflection.through_reflection.primary_key_name].blank? + false + else + true + end + end + def construct_find_options!(options) options[:select] = construct_select(options[:select]) options[:from] ||= construct_from @@ -61,6 +69,7 @@ module ActiveRecord end def find_target + return [] unless target_reflection_has_associated_record? @reflection.klass.find(:all, :select => construct_select, :conditions => construct_conditions, @@ -102,6 +111,8 @@ module ActiveRecord "#{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 diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index e5486738f0..1c753524de 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -342,7 +342,9 @@ module ActiveRecord method_name = method.to_s if super return true - elsif self.private_methods.include?(method_name) && !include_private_methods + elsif !include_private_methods && super(method, true) + # If we're here than we haven't found among non-private methods + # but found among all methods. Which means that given method is private. return false elsif !self.class.generated_methods? self.class.define_attribute_methods diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 6fb05b5551..6a1a3794a2 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1780,10 +1780,10 @@ module ActiveRecord #:nodoc: #{'result = ' if bang}if options[:conditions] with_scope(:find => finder_options) do - ActiveSupport::Deprecation.silence { send(:#{finder}, options) } + find(:#{finder}, options) end else - ActiveSupport::Deprecation.silence { send(:#{finder}, options.merge(finder_options)) } + find(:#{finder}, options.merge(finder_options)) end #{'result || raise(RecordNotFound)' if bang} end @@ -1806,9 +1806,9 @@ module ActiveRecord #:nodoc: options = { :conditions => find_attributes } set_readonly_option!(options) - record = find_initial(options) + record = find(:first, options) - if record.nil? + if record.nil? record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) } #{'yield(record) if block_given?'} #{'record.save' if instantiator == :create} diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index 80992dd34b..5e33cf1bd4 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -217,7 +217,7 @@ module ActiveRecord sql << " ORDER BY #{options[:order]} " if options[:order] add_limit!(sql, options, scope) - sql << ") AS #{aggregate_alias}_subquery" if use_workaround + sql << ") #{aggregate_alias}_subquery" if use_workaround sql end @@ -285,11 +285,15 @@ module ActiveRecord operation = operation.to_s.downcase case operation when 'count' then value.to_i - when 'sum' then value =~ /\./ ? value.to_f : value.to_i - when 'avg' then value && value.to_f - else column ? column.type_cast(value) : value + when 'sum' then type_cast_using_column(value || '0', column) + when 'avg' then value && value.to_d + else type_cast_using_column(value, column) end end + + def type_cast_using_column(value, column) + column ? column.type_cast(value) : value + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 10cae5d840..432c341e6c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -172,21 +172,24 @@ module ActiveRecord # within the timeout period. def checkout # Checkout an available connection - conn = @connection_mutex.synchronize do - if @checked_out.size < @connections.size - checkout_existing_connection - elsif @connections.size < @size - checkout_new_connection - end - end - return conn if conn - - # No connections available; wait for one @connection_mutex.synchronize do - if @queue.wait(@timeout) - checkout_existing_connection - else - raise ConnectionTimeoutError, "could not obtain a database connection within #{@timeout} seconds. The pool size is currently #{@size}, perhaps you need to increase it?" + loop do + conn = if @checked_out.size < @connections.size + checkout_existing_connection + elsif @connections.size < @size + checkout_new_connection + end + return conn if conn + # No connections available; wait for one + if @queue.wait(@timeout) + next + else + # try looting dead threads + clear_stale_cached_connections! + if @size == @checked_out.size + raise ConnectionTimeoutError, "could not obtain a database connection within #{@timeout} seconds. The pool size is currently #{@size}, perhaps you need to increase it?" + end + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index a26fd02b90..1e452ae88a 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -218,7 +218,7 @@ module ActiveRecord s = column.class.string_to_binary(value).unpack("H*")[0] "x'#{s}'" elsif value.kind_of?(BigDecimal) - "'#{value.to_s("F")}'" + value.to_s("F") else super end @@ -371,9 +371,9 @@ module ActiveRecord end end - def recreate_database(name) #:nodoc: + def recreate_database(name, options = {}) #:nodoc: drop_database(name) - create_database(name) + create_database(name, options) end # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>. diff --git a/activerecord/lib/active_record/dynamic_finder_match.rb b/activerecord/lib/active_record/dynamic_finder_match.rb index f4a5712981..8f9f05ce36 100644 --- a/activerecord/lib/active_record/dynamic_finder_match.rb +++ b/activerecord/lib/active_record/dynamic_finder_match.rb @@ -6,11 +6,11 @@ module ActiveRecord end def initialize(method) - @finder = :find_initial + @finder = :first case method.to_s when /^find_(all_by|last_by|by)_([_a-zA-Z]\w*)$/ - @finder = :find_last if $1 == 'last_by' - @finder = :find_every if $1 == 'all_by' + @finder = :last if $1 == 'last_by' + @finder = :all if $1 == 'all_by' names = $2 when /^find_by_([_a-zA-Z]\w*)\!$/ @bang = true @@ -31,7 +31,7 @@ module ActiveRecord end def instantiator? - @finder == :find_initial && !@instantiator.nil? + @finder == :first && !@instantiator.nil? end def bang? diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index a1b498eceb..dbff4f24d6 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -13,14 +13,15 @@ module ActiveRecord def create_reflection(macro, name, options, active_record) case macro when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many - reflection = AssociationReflection.new(macro, name, options, active_record) + klass = options[:through] ? ThroughReflection : AssociationReflection + reflection = klass.new(macro, name, options, active_record) when :composed_of reflection = AggregateReflection.new(macro, name, options, active_record) end write_inheritable_hash :reflections, name => reflection reflection end - + # Returns a hash containing all AssociationReflection objects for the current class # Example: # @@ -30,7 +31,7 @@ module ActiveRecord def reflections read_inheritable_attribute(:reflections) || write_inheritable_attribute(:reflections, {}) end - + # Returns an array of AggregateReflection objects for all the aggregations in the class. def reflect_on_all_aggregations reflections.values.select { |reflection| reflection.is_a?(AggregateReflection) } @@ -116,6 +117,11 @@ module ActiveRecord @sanitized_conditions ||= klass.send(:sanitize_sql, options[:conditions]) if options[:conditions] end + # Returns +true+ if +self+ is a +belongs_to+ reflection. + def belongs_to? + macro == :belongs_to + end + private def derive_class_name name.to_s.camelize @@ -192,6 +198,52 @@ module ActiveRecord end end + def check_validity! + end + + def through_reflection + false + end + + def through_reflection_primary_key_name + end + + def source_reflection + nil + end + + private + def derive_class_name + class_name = name.to_s.camelize + class_name = class_name.singularize if [ :has_many, :has_and_belongs_to_many ].include?(macro) + class_name + end + + def derive_primary_key_name + if belongs_to? + "#{name}_id" + elsif options[:as] + "#{options[:as]}_id" + else + active_record.name.foreign_key + end + end + end + + # Holds all the meta-data about a :through association as it was specified in the Active Record class. + class ThroughReflection < AssociationReflection #:nodoc: + # Gets the source of the through reflection. It checks both a singularized and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>. + # (The <tt>:tags</tt> association on Tagging below.) + # + # class Post < ActiveRecord::Base + # has_many :taggings + # has_many :tags, :through => :taggings + # end + # + def source_reflection + @source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first + end + # Returns the AssociationReflection object specified in the <tt>:through</tt> option # of a HasManyThrough or HasOneThrough association. Example: # @@ -204,7 +256,7 @@ module ActiveRecord # taggings_reflection = tags_reflection.through_reflection # def through_reflection - @through_reflection ||= options[:through] ? active_record.reflect_on_association(options[:through]) : false + @through_reflection ||= active_record.reflect_on_association(options[:through]) end # Gets an array of possible <tt>:through</tt> source reflection names: @@ -215,63 +267,40 @@ module ActiveRecord @source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym } end - # Gets the source of the through reflection. It checks both a singularized and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>. - # (The <tt>:tags</tt> association on Tagging below.) - # - # class Post < ActiveRecord::Base - # has_many :taggings - # has_many :tags, :through => :taggings - # end - # - def source_reflection - return nil unless through_reflection - @source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first - end - def check_validity! - if options[:through] - if through_reflection.nil? - raise HasManyThroughAssociationNotFoundError.new(active_record.name, self) - end - - if source_reflection.nil? - raise HasManyThroughSourceAssociationNotFoundError.new(self) - end + if through_reflection.nil? + raise HasManyThroughAssociationNotFoundError.new(active_record.name, self) + end - if options[:source_type] && source_reflection.options[:polymorphic].nil? - raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection) - end - - if source_reflection.options[:polymorphic] && options[:source_type].nil? - raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection) - end - - unless [:belongs_to, :has_many].include?(source_reflection.macro) && source_reflection.options[:through].nil? - raise HasManyThroughSourceAssociationMacroError.new(self) - end + if source_reflection.nil? + raise HasManyThroughSourceAssociationNotFoundError.new(self) + end + + if options[:source_type] && source_reflection.options[:polymorphic].nil? + raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection) end + + if source_reflection.options[:polymorphic] && options[:source_type].nil? + raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection) + end + + unless [:belongs_to, :has_many].include?(source_reflection.macro) && source_reflection.options[:through].nil? + raise HasManyThroughSourceAssociationMacroError.new(self) + end + end + + def through_reflection_primary_key + through_reflection.belongs_to? ? through_reflection.klass.primary_key : through_reflection.primary_key_name + end + + def through_reflection_primary_key_name + through_reflection.primary_key_name if through_reflection.belongs_to? end private def derive_class_name # get the class_name of the belongs_to association of the through reflection - if through_reflection - options[:source_type] || source_reflection.class_name - else - class_name = name.to_s.camelize - class_name = class_name.singularize if [ :has_many, :has_and_belongs_to_many ].include?(macro) - class_name - end - end - - def derive_primary_key_name - if macro == :belongs_to - "#{name}_id" - elsif options[:as] - "#{options[:as]}_id" - else - active_record.name.foreign_key - end + options[:source_type] || source_reflection.class_name end end end |