aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
authorHongli Lai (Phusion) <hongli@phusion.nl>2008-08-27 11:39:06 +0200
committerHongli Lai (Phusion) <hongli@phusion.nl>2008-08-27 11:39:06 +0200
commit08704c442d15b16511214731dd94108b737ef407 (patch)
treed92d53f39245256dea6aa38a11500938adccf777 /activerecord/lib
parent920ad94598a9b90d048cb7cb19a34d7cb9e80392 (diff)
parent5db2f199aba9aa8d00adefa8237922ad684aca03 (diff)
downloadrails-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.rb15
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb38
-rw-r--r--activerecord/lib/active_record/associations/association_proxy.rb65
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb14
-rwxr-xr-xactiverecord/lib/active_record/base.rb76
-rw-r--r--activerecord/lib/active_record/calculations.rb2
-rw-r--r--activerecord/lib/active_record/callbacks.rb23
-rw-r--r--activerecord/lib/active_record/dirty.rb4
-rw-r--r--activerecord/lib/active_record/migration.rb32
-rw-r--r--activerecord/lib/active_record/named_scope.rb6
-rw-r--r--activerecord/lib/active_record/validations.rb2
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