diff options
author | Hongli Lai (Phusion) <hongli@phusion.nl> | 2008-08-27 11:39:06 +0200 |
---|---|---|
committer | Hongli Lai (Phusion) <hongli@phusion.nl> | 2008-08-27 11:39:06 +0200 |
commit | 08704c442d15b16511214731dd94108b737ef407 (patch) | |
tree | d92d53f39245256dea6aa38a11500938adccf777 /activerecord/lib | |
parent | 920ad94598a9b90d048cb7cb19a34d7cb9e80392 (diff) | |
parent | 5db2f199aba9aa8d00adefa8237922ad684aca03 (diff) | |
download | rails-08704c442d15b16511214731dd94108b737ef407.tar.gz rails-08704c442d15b16511214731dd94108b737ef407.tar.bz2 rails-08704c442d15b16511214731dd94108b737ef407.zip |
Merge branch 'master' of git@github.com:lifo/docrails
Diffstat (limited to 'activerecord/lib')
-rw-r--r-- | activerecord/lib/active_record/associations.rb | 15 | ||||
-rw-r--r-- | activerecord/lib/active_record/associations/association_collection.rb | 38 | ||||
-rw-r--r-- | activerecord/lib/active_record/associations/association_proxy.rb | 65 | ||||
-rw-r--r-- | activerecord/lib/active_record/associations/has_many_association.rb | 14 | ||||
-rwxr-xr-x | activerecord/lib/active_record/base.rb | 76 | ||||
-rw-r--r-- | activerecord/lib/active_record/calculations.rb | 2 | ||||
-rw-r--r-- | activerecord/lib/active_record/callbacks.rb | 23 | ||||
-rw-r--r-- | activerecord/lib/active_record/dirty.rb | 4 | ||||
-rw-r--r-- | activerecord/lib/active_record/migration.rb | 32 | ||||
-rw-r--r-- | activerecord/lib/active_record/named_scope.rb | 6 | ||||
-rw-r--r-- | activerecord/lib/active_record/validations.rb | 2 |
11 files changed, 220 insertions, 57 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 4e33dfe69f..eb1281901b 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -660,8 +660,8 @@ module ActiveRecord # # === Example # - # A Firm class declares <tt>has_many :clients</tt>, which will add: - # * <tt>Firm#clients</tt> (similar to <tt>Clients.find :all, :conditions => "firm_id = #{id}"</tt>) + # Example: A Firm class declares <tt>has_many :clients</tt>, which will add: + # * <tt>Firm#clients</tt> (similar to <tt>Clients.find :all, :conditions => ["firm_id = ?", id]</tt>) # * <tt>Firm#clients<<</tt> # * <tt>Firm#clients.delete</tt> # * <tt>Firm#clients=</tt> @@ -1220,12 +1220,11 @@ module ActiveRecord end private - # Generate a join table name from two provided tables names. - # The order of names in join name is determined by lexical precedence. - # join_table_name("members", "clubs") - # => "clubs_members" - # join_table_name("members", "special_clubs") - # => "members_special_clubs" + # Generates a join table name from two provided table names. + # The names in the join table namesme end up in lexicographic order. + # + # join_table_name("members", "clubs") # => "clubs_members" + # join_table_name("members", "special_clubs") # => "members_special_clubs" def join_table_name(first_table_name, second_table_name) if first_table_name < second_table_name join_table = "#{first_table_name}_#{second_table_name}" diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index a28be9eed1..a12d4face4 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -2,6 +2,19 @@ require 'set' module ActiveRecord module Associations + # AssociationCollection is an abstract class that provides common stuff to + # ease the implementation of association proxies that represent + # collections. See the class hierarchy in AssociationProxy. + # + # You need to be careful with assumptions regarding the target: The proxy + # does not fetch records from the database until it needs them, but new + # ones created with +build+ are added to the target. So, the target may be + # non-empty and still lack children waiting to be read from the database. + # If you look directly to the database you cannot assume that's the entire + # collection because new records may have beed added to the target, etc. + # + # If you need to work on all current children, new and existing records, + # +load_target+ and the +loaded+ flag are your friends. class AssociationCollection < AssociationProxy #:nodoc: def initialize(owner, reflection) super @@ -185,9 +198,16 @@ module ActiveRecord end end - # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and - # calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero - # and you need to fetch that collection afterwards, it'll take one less SELECT query if you use length. + # Returns the size of the collection by executing a SELECT COUNT(*) + # query if the collection hasn't been loaded, and calling + # <tt>collection.size</tt> if it has. + # + # If the collection has been already loaded +size+ and +length+ are + # equivalent. If not and you are going to need the records anyway + # +length+ will take one less query. Otherwise +size+ is more efficient. + # + # This method is abstract in the sense that it relies on + # +count_records+, which is a method descendants have to provide. def size if @owner.new_record? || (loaded? && !@reflection.options[:uniq]) @target.size @@ -199,12 +219,18 @@ module ActiveRecord end end - # Returns the size of the collection by loading it and calling size on the array. If you want to use this method to check - # whether the collection is empty, use collection.length.zero? instead of collection.empty? + # Returns the size of the collection calling +size+ on the target. + # + # If the collection has been already loaded +length+ and +size+ are + # equivalent. If not and you are going to need the records anyway this + # method will take one less query. Otherwise +size+ is more efficient. def length load_target.size end + # Equivalent to <tt>collection.size.zero?</tt>. If the collection has + # not been already loaded and you are going to fetch the records anyway + # it is better to check <tt>collection.length.zero?</tt>. def empty? size.zero? end @@ -344,7 +370,7 @@ module ActiveRecord callback(:before_add, record) yield(record) if block_given? @target ||= [] unless loaded? - @target << record + @target << record unless @reflection.options[:uniq] && @target.include?(record) callback(:after_add, record) record end diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 77fc827e11..78b4c137a7 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -39,7 +39,7 @@ module ActiveRecord # though the object behind <tt>blog.posts</tt> is not an Array, but an # ActiveRecord::Associations::HasManyAssociation. # - # The <tt>@target</tt> object is not loaded until needed. For example, + # The <tt>@target</tt> object is not \loaded until needed. For example, # # blog.posts.count # @@ -57,92 +57,127 @@ module ActiveRecord reset end + # Returns the owner of the proxy. def proxy_owner @owner end + # Returns the reflection object that represents the association handled + # by the proxy. def proxy_reflection @reflection end + # Returns the \target of the proxy, same as +target+. def proxy_target @target end + # Does the proxy or its \target respond to +symbol+? def respond_to?(symbol, include_priv = false) proxy_respond_to?(symbol, include_priv) || (load_target && @target.respond_to?(symbol, include_priv)) end - # Explicitly proxy === because the instance method removal above - # doesn't catch it. + # Forwards <tt>===</tt> explicitly to the \target because the instance method + # removal above doesn't catch it. Loads the \target if needed. def ===(other) load_target other === @target end + # Returns the name of the table of the related class: + # + # post.comments.aliased_table_name # => "comments" + # def aliased_table_name @reflection.klass.table_name end + # Returns the SQL string that corresponds to the <tt>:conditions</tt> + # option of the macro, if given, or +nil+ otherwise. def conditions @conditions ||= interpolate_sql(@reflection.sanitized_conditions) if @reflection.sanitized_conditions end alias :sql_conditions :conditions + # Resets the \loaded flag to +false+ and sets the \target to +nil+. def reset @loaded = false @target = nil end + # Reloads the \target and returns +self+ on success. def reload reset load_target self unless @target.nil? end + # Has the \target been already \loaded? def loaded? @loaded end + # Asserts the \target has been loaded setting the \loaded flag to +true+. def loaded @loaded = true end + # Returns the target of this proxy, same as +proxy_target+. def target @target end + # Sets the target of this proxy to <tt>\target</tt>, and the \loaded flag to +true+. def target=(target) @target = target loaded end + # Forwards the call to the target. Loads the \target if needed. def inspect load_target @target.inspect end protected + # Does the association have a <tt>:dependent</tt> option? def dependent? @reflection.options[:dependent] end + # Returns a string with the IDs of +records+ joined with a comma, quoted + # if needed. The result is ready to be inserted into a SQL IN clause. + # + # quoted_record_ids(records) # => "23,56,58,67" + # def quoted_record_ids(records) records.map { |record| record.quoted_id }.join(',') end + # Interpolates the SQL in <tt>options[key]</tt> and assigns the result + # back, for any +key+ in +keys+ that's present in +options+. + # + # Meant to be used like this: + # + # interpolate_sql_options!(@reflection.options, :finder_sql) + # def interpolate_sql_options!(options, *keys) keys.each { |key| options[key] &&= interpolate_sql(options[key]) } end + # Forwards the call to the owner. def interpolate_sql(sql, record = nil) @owner.send(:interpolate_sql, sql, record) end + # Forwards the call to the reflection class. def sanitize_sql(sql) @reflection.klass.send(:sanitize_sql, sql) end + # Assigns the ID of the owner to the corresponding foreign key in +record+. + # If the association is polymorphic the type of the owner is also set. def set_belongs_to_association_for(record) if @reflection.options[:as] record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record? @@ -152,6 +187,7 @@ module ActiveRecord end end + # Merges into +options+ the ones coming from the reflection. def merge_options_from_reflection!(options) options.reverse_merge!( :group => @reflection.options[:group], @@ -164,11 +200,13 @@ module ActiveRecord ) end + # Forwards +with_scope+ to the reflection. def with_scope(*args, &block) @reflection.klass.send :with_scope, *args, &block end private + # Forwards any missing method call to the \target. def method_missing(method, *args) if load_target if block_given? @@ -179,16 +217,16 @@ module ActiveRecord end end - # Loads the target if needed and returns it. + # Loads the \target if needed and returns it. # # This method is abstract in the sense that it relies on +find_target+, # which is expected to be provided by descendants. # - # If the target is already loaded it is just returned. Thus, you can call - # +load_target+ unconditionally to get the target. + # If the \target is already \loaded it is just returned. Thus, you can call + # +load_target+ unconditionally to get the \target. # # ActiveRecord::RecordNotFound is rescued within the method, and it is - # not reraised. The proxy is reset and +nil+ is the return value. + # not reraised. The proxy is \reset and +nil+ is the return value. def load_target return nil unless defined?(@loaded) @@ -202,12 +240,17 @@ module ActiveRecord reset end - # Can be overwritten by associations that might have the foreign key available for an association without - # having the object itself (and still being a new record). Currently, only belongs_to presents this scenario. + # Can be overwritten by associations that might have the foreign key + # available for an association without having the object itself (and + # still being a new record). Currently, only +belongs_to+ presents + # this scenario (both vanilla and polymorphic). def foreign_key_present false end + # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of + # 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) message = "#{@reflection.class_name}(##{@reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})" @@ -215,11 +258,13 @@ module ActiveRecord end end - # Array#flatten has problems with recursive arrays. Going one level deeper solves the majority of the problems. + # Array#flatten has problems with recursive arrays. Going one level + # deeper solves the majority of the problems. def flatten_deeper(array) array.collect { |element| element.respond_to?(:flatten) ? element.flatten : element }.flatten end + # Returns the ID of the owner, quoted if needed. def owner_quoted_id @owner.quoted_id end diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index e6fa15c173..f06e69aba3 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -1,5 +1,9 @@ module ActiveRecord module Associations + # This is the proxy that handles a has many association. + # + # If the association has a <tt>:through</tt> option further specialization + # is provided by its child HasManyThroughAssociation. class HasManyAssociation < AssociationCollection #:nodoc: # Count the number of associated records. All arguments are optional. def count(*args) @@ -27,6 +31,16 @@ module ActiveRecord end end + # 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. + # 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. + # + # If the collection is empty the target is set to an empty array and + # the loaded flag is set to true as well. def count_records count = if has_cached_counter? @owner.send(:read_attribute, cached_counter_attribute_name) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 1838287616..b282ea931e 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -752,13 +752,15 @@ module ActiveRecord #:nodoc: end # Updates all records with details given if they match a set of conditions supplied, limits and order can - # also be supplied. + # also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the + # database. It does not instantiate the involved models and it does not trigger Active Record callbacks. # # ==== Attributes # - # * +updates+ - A String of column and value pairs that will be set on any records that match conditions. + # * +updates+ - A string of column and value pairs that will be set on any records that match conditions. + # What goes into the SET clause. # * +conditions+ - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro for more info. - # * +options+ - Additional options are <tt>:limit</tt> and/or <tt>:order</tt>, see the examples for usage. + # * +options+ - Additional options are <tt>:limit</tt> and <tt>:order</tt>, see the examples for usage. # # ==== Examples # @@ -780,8 +782,8 @@ module ActiveRecord #:nodoc: connection.update(sql, "#{name} Update") end - # Destroys the records matching +conditions+ by instantiating each record and calling the destroy method. - # This means at least 2*N database queries to destroy N records, so avoid destroy_all if you are deleting + # Destroys the records matching +conditions+ by instantiating each record and calling their +destroy+ method. + # This means at least 2*N database queries to destroy N records, so avoid +destroy_all+ if you are deleting # many records. If you want to simply delete records without worrying about dependent associations or # callbacks, use the much faster +delete_all+ method instead. # @@ -800,8 +802,9 @@ module ActiveRecord #:nodoc: end # Deletes the records matching +conditions+ without instantiating the records first, and hence not - # calling the destroy method and invoking callbacks. This is a single SQL query, much more efficient - # than destroy_all. + # calling the +destroy+ method nor invoking callbacks. This is a single SQL DELETE statement that + # goes straight to the database, much more efficient than +destroy_all+. Careful with relations + # though, in particular <tt>:dependent</tt> is not taken into account. # # ==== Attributes # @@ -811,8 +814,8 @@ module ActiveRecord #:nodoc: # # Post.delete_all "person_id = 5 AND (category = 'Something' OR category = 'Else')" # - # This deletes the affected posts all at once with a single DELETE query. If you need to destroy dependent - # associations or call your before_ or after_destroy callbacks, use the +destroy_all+ method instead. + # This deletes the affected posts all at once with a single DELETE statement. If you need to destroy dependent + # associations or call your <tt>before_*</tt> or +after_destroy+ callbacks, use the +destroy_all+ method instead. def delete_all(conditions = nil) sql = "DELETE FROM #{quoted_table_name} " add_conditions!(sql, conditions, scope(:find)) @@ -2234,20 +2237,40 @@ module ActiveRecord #:nodoc: defined?(@new_record) && @new_record end - # * No record exists: Creates a new record with values matching those of the object attributes. - # * A record does exist: Updates the record with values matching those of the object attributes. + # :call-seq: + # save(perform_validation = true) # - # Note: If your model specifies any validations then the method declaration dynamically - # changes to: - # save(perform_validation=true) - # Calling save(false) saves the model without running validations. - # See ActiveRecord::Validations for more information. + # Saves the model. + # + # If the model is new a record gets created in the database, otherwise + # the existing record gets updated. + # + # If +perform_validation+ is true validations run. If any of them fail + # the action is cancelled and +save+ returns +false+. If the flag is + # false validations are bypassed altogether. See + # ActiveRecord::Validations for more information. + # + # There's a series of callbacks associated with +save+. If any of the + # <tt>before_*</tt> callbacks return +false+ the action is cancelled and + # +save+ returns +false+. See ActiveRecord::Callbacks for further + # details. def save create_or_update end - # Attempts to save the record, but instead of just returning false if it couldn't happen, it raises a - # RecordNotSaved exception + # Saves the model. + # + # If the model is new a record gets created in the database, otherwise + # the existing record gets updated. + # + # With <tt>save!</tt> validations always run. If any of them fail + # ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations + # for more information. + # + # There's a series of callbacks associated with <tt>save!</tt>. If any of + # the <tt>before_*</tt> callbacks return +false+ the action is cancelled + # and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See + # ActiveRecord::Callbacks for further details. def save! create_or_update || raise(RecordNotSaved) end @@ -2594,7 +2617,7 @@ module ActiveRecord #:nodoc: removed_attributes = attributes.keys - safe_attributes.keys if removed_attributes.any? - logger.debug "WARNING: Can't mass-assign these protected attributes: #{removed_attributes.join(', ')}" + log_protected_attribute_removal(removed_attributes) end safe_attributes @@ -2609,6 +2632,10 @@ module ActiveRecord #:nodoc: end end + def log_protected_attribute_removal(*attributes) + logger.debug "WARNING: Can't mass-assign these protected attributes: #{attributes.join(', ')}" + end + # The primary key and inheritance column can never be set by mass-assignment for security reasons. def attributes_protected_by_default default = [ self.class.primary_key, self.class.inheritance_column ] @@ -2622,8 +2649,15 @@ module ActiveRecord #:nodoc: quoted = {} connection = self.class.connection attribute_names.each do |name| - if column = column_for_attribute(name) - quoted[name] = connection.quote(read_attribute(name), column) unless !include_primary_key && column.primary + if (column = column_for_attribute(name)) && (include_primary_key || !column.primary) + value = read_attribute(name) + + # We need explicit to_yaml because quote() does not properly convert Time/Date fields to YAML. + if value && self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time)) + value = value.to_yaml + end + + quoted[name] = connection.quote(value, column) end end include_readonly_attributes ? quoted : remove_readonly_attributes(quoted) diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index 34ffc9a5e5..e765b46cc2 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -211,7 +211,7 @@ module ActiveRecord sql << " ORDER BY #{options[:order]} " if options[:order] add_limit!(sql, options, scope) - sql << ')' if use_workaround + sql << ') AS #{aggregate_alias}_subquery' if use_workaround sql end diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index be2621fdb6..d99e183f9e 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -3,7 +3,7 @@ require 'observer' module ActiveRecord # Callbacks are hooks into the lifecycle of an Active Record object that allow you to trigger logic # before or after an alteration of the object state. This can be used to make sure that associated and - # dependent objects are deleted when destroy is called (by overwriting +before_destroy+) or to massage attributes + # dependent objects are deleted when +destroy+ is called (by overwriting +before_destroy+) or to massage attributes # before they're validated (by overwriting +before_validation+). As an example of the callbacks initiated, consider # the <tt>Base#save</tt> call: # @@ -161,7 +161,7 @@ module ActiveRecord # == <tt>before_validation*</tt> returning statements # # If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be aborted and <tt>Base#save</tt> will return +false+. - # If Base#save! is called it will raise a RecordNotSaved exception. + # If Base#save! is called it will raise a ActiveRecord::RecordInvalid exception. # Nothing will be appended to the errors object. # # == Canceling callbacks @@ -169,6 +169,19 @@ module ActiveRecord # If a <tt>before_*</tt> callback returns +false+, all the later callbacks and the associated action are cancelled. If an <tt>after_*</tt> callback returns # +false+, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks # defined as methods on the model, which are called last. + # + # == Transactions + # + # The entire callback chain for +save+ and +destroy+ runs within their transaction, including + # the <tt>after_*</tt> hooks. Cancellation does not trigger a rollback. To rollback + # the transaction just raise an exception the same way you do for regular transactions. + # + # Note though that such an exception bypasses the regular call chain in Active Record: + # If ActiveRecord::Rollback is raised both +save+ and +destroy+ return +nil+. On the other + # hand <tt>save!</tt> does *not* raise ActiveRecord::RecordNotSaved, and does not raise + # anything else for that matter, <tt>save!</tt> just returns +nil+ in that case. + # If any other exception is raised it goes up until it reaches the caller, no matter + # which one of the three actions was being performed. module Callbacks CALLBACKS = %w( after_find after_initialize before_save after_save before_create after_create before_update after_update before_validation @@ -197,6 +210,8 @@ module ActiveRecord def before_save() end # Is called _after_ <tt>Base.save</tt> (regardless of whether it's a +create+ or +update+ save). + # Note that this callback is still wrapped in the transaction around +save+. For example, if you + # invoke an external indexer at this point it won't see the changes in the database. # # class Contact < ActiveRecord::Base # after_save { logger.info( 'New contact saved!' ) } @@ -214,6 +229,8 @@ module ActiveRecord def before_create() end # Is called _after_ <tt>Base.save</tt> on new objects that haven't been saved yet (no record exists). + # Note that this callback is still wrapped in the transaction around +save+. For example, if you + # invoke an external indexer at this point it won't see the changes in the database. def after_create() end def create_with_callbacks #:nodoc: return false if callback(:before_create) == false @@ -227,6 +244,8 @@ module ActiveRecord def before_update() end # Is called _after_ <tt>Base.save</tt> on existing objects that have a record. + # Note that this callback is still wrapped in the transaction around +save+. For example, if you + # invoke an external indexer at this point it won't see the changes in the database. def after_update() end def update_with_callbacks(*args) #:nodoc: diff --git a/activerecord/lib/active_record/dirty.rb b/activerecord/lib/active_record/dirty.rb index 4ce0356457..63bf8c8f5b 100644 --- a/activerecord/lib/active_record/dirty.rb +++ b/activerecord/lib/active_record/dirty.rb @@ -134,7 +134,9 @@ module ActiveRecord def update_with_dirty if partial_updates? - update_without_dirty(changed) + # Serialized attributes should always be written in case they've been + # changed in place. + update_without_dirty(changed | self.class.serialized_attributes.keys) else update_without_dirty end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 731a350854..fd77f27b77 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -349,6 +349,27 @@ module ActiveRecord end end + # MigrationProxy is used to defer loading of the actual migration classes + # until they are needed + class MigrationProxy + + attr_accessor :name, :version, :filename + + delegate :migrate, :announce, :write, :to=>:migration + + private + + def migration + @migration ||= load_migration + end + + def load_migration + load(filename) + name.constantize + end + + end + class Migrator#:nodoc: class << self def migrate(migrations_path, target_version = nil) @@ -437,7 +458,7 @@ module ActiveRecord runnable.pop if down? && !target.nil? runnable.each do |migration| - Base.logger.info "Migrating to #{migration} (#{migration.version})" + Base.logger.info "Migrating to #{migration.name} (#{migration.version})" # On our way up, we skip migrating the ones we've already migrated # On our way down, we skip reverting the ones we've never migrated @@ -470,11 +491,10 @@ module ActiveRecord raise DuplicateMigrationNameError.new(name.camelize) end - load(file) - - klasses << returning(name.camelize.constantize) do |klass| - class << klass; attr_accessor :version end - klass.version = version + klasses << returning(MigrationProxy.new) do |migration| + migration.name = name.camelize + migration.version = version + migration.filename = file end end diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 7f274543b6..eb887ee550 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -103,7 +103,7 @@ module ActiveRecord attr_reader :proxy_scope, :proxy_options [].methods.each do |m| - unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|find|count|sum|average|maximum|minimum|paginate|first|last|empty?|any?)/ + unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|find|count|sum|average|maximum|minimum|paginate|first|last|empty?|any?|respond_to?)/ delegate m, :to => :proxy_found end end @@ -140,6 +140,10 @@ module ActiveRecord @found ? @found.empty? : count.zero? end + def respond_to?(method) + super || @proxy_scope.respond_to?(method) + end + def any? if block_given? proxy_found.any? { |*block_args| yield(*block_args) } diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index e7a9676394..b8b695e529 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -597,7 +597,7 @@ module ActiveRecord # Configuration options: # * <tt>:message</tt> - Specifies a custom error message (default is: "has already been taken"). # * <tt>:scope</tt> - One or more columns by which to limit the scope of the uniqueness constraint. - # * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (+false+ by default). + # * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (+true+ by default). # * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+). # * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+). # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should |