diff options
author | Pratik Naik <pratiknaik@gmail.com> | 2008-09-27 14:04:46 +0100 |
---|---|---|
committer | Pratik Naik <pratiknaik@gmail.com> | 2008-09-27 14:04:46 +0100 |
commit | fda846cf5ddf523b00a39c26591489794b5de568 (patch) | |
tree | 00d4860d53e5c861fd9b3f483f04ff0d2db19307 /activerecord/lib | |
parent | df046298715b1927a832973c4c29955696fee02c (diff) | |
parent | ea609b265ffc30cac00bf09a262027f96964ed6f (diff) | |
download | rails-fda846cf5ddf523b00a39c26591489794b5de568.tar.gz rails-fda846cf5ddf523b00a39c26591489794b5de568.tar.bz2 rails-fda846cf5ddf523b00a39c26591489794b5de568.zip |
Merge commit 'mainstream/master'
Conflicts:
activerecord/lib/active_record/base.rb
railties/Rakefile
railties/doc/guides/activerecord/association_basics.txt
railties/doc/guides/debugging/debugging_rails_applications.txt
railties/doc/guides/getting_started_with_rails/getting_started_with_rails.txt
railties/doc/guides/index.txt
railties/doc/guides/migrations/foreign_keys.txt
railties/doc/guides/migrations/migrations.txt
railties/doc/guides/migrations/writing_a_migration.txt
railties/doc/guides/routing/routing_outside_in.txt
Diffstat (limited to 'activerecord/lib')
13 files changed, 158 insertions, 43 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index a6bbd6fc82..219cd30f94 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -77,5 +77,5 @@ require 'active_record/connection_adapters/abstract_adapter' require 'active_record/schema_dumper' -I18n.load_translations File.dirname(__FILE__) + '/active_record/locale/en-US.yml' - +require 'active_record/i18n_interpolation_deprecation' +I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en-US.yml' diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 33457822ff..4d36216c34 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1026,7 +1026,7 @@ module ActiveRecord # Create the callbacks to update counter cache if options[:counter_cache] cache_column = options[:counter_cache] == true ? - "#{self.to_s.underscore.pluralize}_count" : + "#{self.to_s.demodulize.underscore.pluralize}_count" : options[:counter_cache] method_name = "belongs_to_counter_cache_after_create_for_#{reflection.name}".to_sym @@ -1268,7 +1268,11 @@ module ActiveRecord if association_proxy_class == BelongsToAssociation define_method("#{reflection.primary_key_name}=") do |target_id| - instance_variable_get(ivar).reset if instance_variable_defined?(ivar) + if instance_variable_defined?(ivar) + if association = instance_variable_get(ivar) + association.reset + end + end write_attribute(reflection.primary_key_name, target_id) end end @@ -1424,15 +1428,23 @@ module ActiveRecord [] end + # Creates before_destroy callback methods that nullify, delete or destroy + # has_many associated objects, according to the defined :dependent rule. + # # See HasManyAssociation#delete_records. Dependent associations # delete children, otherwise foreign key is set to NULL. - def configure_dependency_for_has_many(reflection) + # + # The +extra_conditions+ parameter, which is not used within the main + # Active Record codebase, is meant to allow plugins to define extra + # finder conditions. + def configure_dependency_for_has_many(reflection, extra_conditions = nil) if reflection.options.include?(:dependent) # Add polymorphic type if the :as option is present dependent_conditions = [] dependent_conditions << "#{reflection.primary_key_name} = \#{record.quoted_id}" dependent_conditions << "#{reflection.options[:as]}_type = '#{base_class.name}'" if reflection.options[:as] dependent_conditions << sanitize_sql(reflection.options[:conditions]) if reflection.options[:conditions] + dependent_conditions << extra_conditions if extra_conditions dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ") case reflection.options[:dependent] @@ -1443,9 +1455,24 @@ module ActiveRecord end before_destroy method_name when :delete_all - module_eval "before_destroy { |record| #{reflection.class_name}.delete_all(%(#{dependent_conditions})) }" + module_eval %Q{ + before_destroy do |record| + delete_all_has_many_dependencies(record, + "#{reflection.name}", + #{reflection.class_name}, + "#{dependent_conditions}") + end + } when :nullify - module_eval "before_destroy { |record| #{reflection.class_name}.update_all(%(#{reflection.primary_key_name} = NULL), %(#{dependent_conditions})) }" + module_eval %Q{ + before_destroy do |record| + nullify_has_many_dependencies(record, + "#{reflection.name}", + #{reflection.class_name}, + "#{reflection.primary_key_name}", + "#{dependent_conditions}") + end + } else raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, or :nullify (#{reflection.options[:dependent].inspect})" end @@ -1472,7 +1499,7 @@ module ActiveRecord # with foreign keys pointing to this object, and we only want # to delete the correct one, not all of them. association = send(reflection.name) - association.class.delete(association.id) unless association.nil? + association.delete unless association.nil? end before_destroy method_name when :nullify @@ -1502,7 +1529,7 @@ module ActiveRecord method_name = "belongs_to_dependent_delete_for_#{reflection.name}".to_sym define_method(method_name) do association = send(reflection.name) - association.class.delete(association.id) unless association.nil? + association.delete unless association.nil? end before_destroy method_name else @@ -1511,6 +1538,14 @@ module ActiveRecord end end + def delete_all_has_many_dependencies(record, reflection_name, association_class, dependent_conditions) + association_class.delete_all(dependent_conditions) + end + + def nullify_has_many_dependencies(record, reflection_name, association_class, primary_key_name, dependent_conditions) + association_class.update_all("#{primary_key_name} = NULL", dependent_conditions) + end + mattr_accessor :valid_keys_for_has_many_association @@valid_keys_for_has_many_association = [ :class_name, :table_name, :foreign_key, :primary_key, @@ -1757,12 +1792,12 @@ module ActiveRecord def create_extension_modules(association_id, block_extension, extensions) if block_extension - extension_module_name = "#{self.to_s}#{association_id.to_s.camelize}AssociationExtension" + extension_module_name = "#{self.to_s.demodulize}#{association_id.to_s.camelize}AssociationExtension" silence_warnings do - Object.const_set(extension_module_name, Module.new(&block_extension)) + self.parent.const_set(extension_module_name, Module.new(&block_extension)) end - Array(extensions).push(extension_module_name.constantize) + Array(extensions).push("#{self.parent}::#{extension_module_name}".constantize) else Array(extensions) end diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 8de528f638..463de9d819 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -63,7 +63,7 @@ module ActiveRecord # Fetches the first one using SQL if possible. def first(*args) - if fetch_first_or_last_using_find? args + if fetch_first_or_last_using_find?(args) find(:first, *args) else load_target unless loaded? @@ -73,7 +73,7 @@ module ActiveRecord # Fetches the last one using SQL if possible. def last(*args) - if fetch_first_or_last_using_find? args + if fetch_first_or_last_using_find?(args) find(:last, *args) else load_target unless loaded? @@ -108,7 +108,7 @@ module ActiveRecord result = true load_target if @owner.new_record? - @owner.transaction do + transaction do flatten_deeper(records).each do |record| raise_on_type_mismatch(record) add_record_to_target_with_callbacks(record) do |r| @@ -123,6 +123,21 @@ module ActiveRecord alias_method :push, :<< alias_method :concat, :<< + # Starts a transaction in the association class's database connection. + # + # class Author < ActiveRecord::Base + # has_many :books + # end + # + # Author.find(:first).books.transaction do + # # same effect as calling Book.transaction + # end + def transaction(*args) + @reflection.klass.transaction(*args) do + yield + end + end + # Remove all records from this association def delete_all load_target @@ -173,7 +188,7 @@ module ActiveRecord records = flatten_deeper(records) records.each { |record| raise_on_type_mismatch(record) } - @owner.transaction do + transaction do records.each { |record| callback(:before_remove, record) } old_records = records.reject {|r| r.new_record? } @@ -200,7 +215,7 @@ module ActiveRecord end def destroy_all - @owner.transaction do + transaction do each { |record| record.destroy } end @@ -238,6 +253,8 @@ module ActiveRecord def size if @owner.new_record? || (loaded? && !@reflection.options[:uniq]) @target.size + elsif !loaded? && @reflection.options[:group] + load_target.size elsif !loaded? && !@reflection.options[:uniq] && @target.is_a?(Array) unsaved_records = @target.select { |r| r.new_record? } unsaved_records.size + count_records @@ -290,7 +307,7 @@ module ActiveRecord other = other_array.size < 100 ? other_array : other_array.to_set current = @target.size < 100 ? @target : @target.to_set - @owner.transaction do + transaction do delete(@target.select { |v| !other.include?(v) }) concat(other_array.select { |v| !current.include?(v) }) end @@ -418,7 +435,8 @@ module ActiveRecord end def fetch_first_or_last_using_find?(args) - args.first.kind_of?(Hash) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] || !@target.blank? || args.first.kind_of?(Integer)) + args.first.kind_of?(Hash) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] || + @target.any? { |record| record.new_record? } || args.first.kind_of?(Integer)) end end end diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index dda22668c6..3b2f306637 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -17,7 +17,10 @@ module ActiveRecord # Returns the number of records in this collection. # # If the association has a counter cache it gets that value. Otherwise - # a count via SQL is performed, bounded to <tt>:limit</tt> if there's one. + # it will attempt to do a count via SQL, bounded to <tt>:limit</tt> if + # there's one. Some configuration options like :group make it impossible + # to do a SQL count, in those cases the array count will be used. + # # That does not depend on whether the collection has already been loaded # or not. The +size+ method is the one that takes the loaded flag into # account and delegates to +count_records+ if needed. 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 ebd2bf768c..3171665e19 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -9,14 +9,14 @@ module ActiveRecord alias_method :new, :build def create!(attrs = nil) - @reflection.klass.transaction do + transaction do self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association! } : @reflection.create_association!) object end end def create(attrs = nil) - @reflection.klass.transaction do + transaction do self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association } : @reflection.create_association) object end diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 0a1baff87d..e5486738f0 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -10,7 +10,7 @@ module ActiveRecord base.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT base.cattr_accessor :time_zone_aware_attributes, :instance_writer => false base.time_zone_aware_attributes = false - base.cattr_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false + base.class_inheritable_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false base.skip_time_zone_conversion_for_attributes = [] end @@ -232,6 +232,10 @@ module ActiveRecord def method_missing(method_id, *args, &block) method_name = method_id.to_s + if self.class.private_method_defined?(method_name) + raise NoMethodError("Attempt to call private method", method_name, args) + end + # If we haven't generated any methods yet, generate them, then # see if we've created the method we're looking for. if !self.class.generated_methods? @@ -334,10 +338,12 @@ module ActiveRecord # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt> # which will all return +true+. alias :respond_to_without_attributes? :respond_to? - def respond_to?(method, include_priv = false) + def respond_to?(method, include_private_methods = false) method_name = method.to_s if super return true + elsif self.private_methods.include?(method_name) && !include_private_methods + return false elsif !self.class.generated_methods? self.class.define_attribute_methods if self.class.generated_methods.include?(method_name) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index c0c9b8a9b3..ac15eed408 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1632,19 +1632,19 @@ module ActiveRecord #:nodoc: (safe_to_array(first) + safe_to_array(second)).uniq end - def merge_joins(first, second) - if first.is_a?(String) && second.is_a?(String) - "#{first} #{second}" - elsif first.is_a?(String) || second.is_a?(String) - if first.is_a?(String) - join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, second, nil) - "#{first} #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join}" - else - join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, first, nil) - "#{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} #{second}" + def merge_joins(*joins) + if joins.any?{|j| j.is_a?(String) || array_of_strings?(j) } + joins = joins.collect do |join| + join = [join] if join.is_a?(String) + unless array_of_strings?(join) + join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, join, nil) + join = join_dependency.join_associations.collect { |assoc| assoc.association_join } + end + join end + joins.flatten.uniq else - (safe_to_array(first) + safe_to_array(second)).uniq + joins.collect{|j| safe_to_array(j)}.flatten.uniq end end @@ -1660,6 +1660,10 @@ module ActiveRecord #:nodoc: end end + def array_of_strings?(o) + o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)} + end + def add_order!(sql, order, scope = :auto) scope = scope(:find) if :auto == scope scoped_order = scope[:order] if scope @@ -1708,8 +1712,12 @@ module ActiveRecord #:nodoc: merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins]) case merged_joins when Symbol, Hash, Array - join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil) - sql << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} " + if array_of_strings?(merged_joins) + sql << merged_joins.join(' ') + " " + else + join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil) + sql << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} " + end when String sql << " #{merged_joins} " end @@ -2387,8 +2395,18 @@ module ActiveRecord #:nodoc: # Deletes the record in the database and freezes this instance to reflect that no changes should # be made (since they can't be persisted). # + # Unlike #destroy, this method doesn't run any +before_delete+ and +after_delete+ + # callbacks, nor will it enforce any association +:dependent+ rules. + # # In addition to deleting this record, any defined +before_delete+ and +after_delete+ # callbacks are run, and +:dependent+ rules defined on associations are run. + def delete + self.class.delete(id) unless new_record? + freeze + end + + # Deletes the record in the database and freezes this instance to reflect that no changes should + # be made (since they can't be persisted). def destroy unless new_record? connection.delete <<-end_sql, "#{self.class.name} Destroy" @@ -2828,7 +2846,7 @@ module ActiveRecord #:nodoc: end def instantiate_time_object(name, values) - if self.class.time_zone_aware_attributes && !self.class.skip_time_zone_conversion_for_attributes.include?(name.to_sym) + if self.class.send(:create_time_zone_conversion_attribute?, name, column_for_attribute(name)) Time.zone.local(*values) else Time.time_with_datetime_fallback(@@default_timezone, *values) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 22304edfc9..58992f91da 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -40,6 +40,10 @@ module ActiveRecord type == :integer || type == :float || type == :decimal end + def has_default? + !default.nil? + end + # Returns the Ruby class that corresponds to the abstract data type. def klass case type diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index c2a0fb72bf..a26fd02b90 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -80,7 +80,7 @@ module ActiveRecord def extract_default(default) if type == :binary || type == :text if default.blank? - nil + return null ? nil : '' else raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}" end @@ -91,6 +91,11 @@ module ActiveRecord end end + def has_default? + return false if type == :binary || type == :text #mysql forbids defaults on blob and text columns + super + end + private def simplified_type(field_type) return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)") diff --git a/activerecord/lib/active_record/i18n_interpolation_deprecation.rb b/activerecord/lib/active_record/i18n_interpolation_deprecation.rb new file mode 100644 index 0000000000..cd634e1b8d --- /dev/null +++ b/activerecord/lib/active_record/i18n_interpolation_deprecation.rb @@ -0,0 +1,26 @@ +# Deprecates the use of the former message interpolation syntax in activerecord +# as in "must have %d characters". The new syntax uses explicit variable names +# as in "{{value}} must have {{count}} characters". + +require 'i18n/backend/simple' +module I18n + module Backend + class Simple + DEPRECATED_INTERPOLATORS = { '%d' => '{{count}}', '%s' => '{{value}}' } + + protected + def interpolate_with_deprecated_syntax(locale, string, values = {}) + return string unless string.is_a?(String) + + string = string.gsub(/%d|%s/) do |s| + instead = DEPRECATED_INTERPOLATORS[s] + ActiveSupport::Deprecation.warn "using #{s} in messages is deprecated; use #{instead} instead." + instead + end + + interpolate_without_deprecated_syntax(locale, string, values) + end + alias_method_chain :interpolate, :deprecated_syntax + end + end +end
\ No newline at end of file diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index b90ed88c6b..4f96e225c1 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -102,7 +102,7 @@ HEADER spec[:precision] = column.precision.inspect if !column.precision.nil? spec[:scale] = column.scale.inspect if !column.scale.nil? spec[:null] = 'false' if !column.null - spec[:default] = default_string(column.default) if !column.default.nil? + spec[:default] = default_string(column.default) if column.has_default? (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")} spec end.compact diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 8481706074..9220eae4d1 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -472,7 +472,7 @@ module ActiveRecord db_cols = begin column_names - rescue ActiveRecord::StatementInvalid + rescue Exception # To ignore both statement and connection errors [] end names = attr_names.reject { |name| db_cols.include?(name.to_s) } @@ -738,7 +738,7 @@ module ActiveRecord condition_params = [value] else condition_sql = "LOWER(#{sql_attribute}) #{comparison_operator}" - condition_params = [value.chars.downcase] + condition_params = [value.mb_chars.downcase] end if scope = configuration[:scope] diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb index aaadef9979..2479b75789 100644 --- a/activerecord/lib/active_record/version.rb +++ b/activerecord/lib/active_record/version.rb @@ -1,7 +1,7 @@ module ActiveRecord module VERSION #:nodoc: MAJOR = 2 - MINOR = 1 + MINOR = 2 TINY = 0 STRING = [MAJOR, MINOR, TINY].join('.') |