diff options
| -rw-r--r-- | activerecord/lib/active_record.rb | 2 | ||||
| -rw-r--r-- | activerecord/lib/active_record/aggregations.rb | 1 | ||||
| -rwxr-xr-x | activerecord/lib/active_record/associations.rb | 16 | ||||
| -rw-r--r-- | activerecord/lib/active_record/attribute_methods.rb | 11 | ||||
| -rw-r--r-- | activerecord/lib/active_record/attribute_methods/dirty.rb | 100 | ||||
| -rw-r--r-- | activerecord/lib/active_record/autosave_association.rb | 6 | ||||
| -rwxr-xr-x | activerecord/lib/active_record/base.rb | 331 | ||||
| -rw-r--r-- | activerecord/lib/active_record/callbacks.rb | 48 | ||||
| -rw-r--r-- | activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb | 27 | ||||
| -rw-r--r-- | activerecord/lib/active_record/counter_cache.rb | 107 | ||||
| -rw-r--r-- | activerecord/lib/active_record/locking/optimistic.rb | 27 | ||||
| -rw-r--r-- | activerecord/lib/active_record/persistence.rb | 230 | ||||
| -rw-r--r-- | activerecord/lib/active_record/timestamp.rb | 48 | ||||
| -rw-r--r-- | activerecord/lib/active_record/transactions.rb | 22 | ||||
| -rw-r--r-- | activerecord/lib/active_record/validations.rb | 71 | ||||
| -rw-r--r-- | activerecord/test/cases/locking_test.rb | 9 | 
16 files changed, 514 insertions, 542 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 6a6485f35e..e2f2508ae8 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -61,6 +61,7 @@ module ActiveRecord      autoload :Base      autoload :Callbacks +    autoload :CounterCache      autoload :DynamicFinderMatch      autoload :DynamicScopeMatch      autoload :Migration @@ -68,6 +69,7 @@ module ActiveRecord      autoload :NamedScope      autoload :NestedAttributes      autoload :Observer +    autoload :Persistence      autoload :QueryCache      autoload :Reflection      autoload :Schema diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index 08389907ef..45aaea062d 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -253,6 +253,7 @@ module ActiveRecord                        raise ArgumentError, 'Converter must be a symbol denoting the converter method to call or a Proc to be invoked.'                      end                  end +                  mapping.each { |pair| self[pair.first] = part.send(pair.last) }                  instance_variable_set("@#{name}", part.freeze)                end diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 6c64210c92..0a3c7c6a60 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1304,14 +1304,14 @@ module ActiveRecord          # Don't use a before_destroy callback since users' before_destroy          # callbacks will be executed after the association is wiped out. -        old_method = "destroy_without_habtm_shim_for_#{reflection.name}" -        class_eval <<-end_eval unless method_defined?(old_method) -          alias_method :#{old_method}, :destroy_without_callbacks  # alias_method :destroy_without_habtm_shim_for_posts, :destroy_without_callbacks -          def destroy_without_callbacks                            # def destroy_without_callbacks -            #{reflection.name}.clear                               #   posts.clear -            #{old_method}                                          #   destroy_without_habtm_shim_for_posts -          end                                                      # end -        end_eval +        include Module.new { +          class_eval <<-RUBY, __FILE__, __LINE__ + 1 +            def destroy                     # def destroy +              super                         #   super +              #{reflection.name}.clear      #   posts.clear +            end                             # end +          RUBY +        }          add_association_callbacks(reflection.name, options)        end diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 3a9a67e3a2..c117271c71 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -18,10 +18,19 @@ module ActiveRecord        def instance_method_already_implemented?(method_name)          method_name = method_name.to_s          @_defined_class_methods         ||= ancestors.first(ancestors.index(ActiveRecord::Base)).sum([]) { |m| m.public_instance_methods(false) | m.private_instance_methods(false) | m.protected_instance_methods(false) }.map {|m| m.to_s }.to_set -        @@_defined_activerecord_methods ||= (ActiveRecord::Base.public_instance_methods(false) | ActiveRecord::Base.private_instance_methods(false) | ActiveRecord::Base.protected_instance_methods(false)).map{|m| m.to_s }.to_set +        @@_defined_activerecord_methods ||= defined_activerecord_methods          raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name)          @_defined_class_methods.include?(method_name)        end + +      def defined_activerecord_methods +        active_record = ActiveRecord::Base +        super_klass   = ActiveRecord::Base.superclass +        methods =  active_record.public_instance_methods - super_klass.public_instance_methods +        methods += active_record.private_instance_methods - super_klass.private_instance_methods +        methods += active_record.protected_instance_methods - super_klass.protected_instance_methods +        methods.map {|m| m.to_s }.to_set +      end      end      def method_missing(method_id, *args, &block) diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 36f2a9777c..dd44bd8d51 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -5,20 +5,20 @@ module ActiveRecord      module Dirty        extend ActiveSupport::Concern        include ActiveModel::Dirty +      include AttributeMethods::Write        included do -        alias_method_chain :save,   :dirty -        alias_method_chain :save!,  :dirty -        alias_method_chain :update, :dirty -        alias_method_chain :reload, :dirty +        if self < Timestamp +          raise "You cannot include Dirty after Timestamp" +        end          superclass_delegating_accessor :partial_updates          self.partial_updates = true        end        # Attempts to +save+ the record and clears changed attributes if successful. -      def save_with_dirty(*args) #:nodoc: -        if status = save_without_dirty(*args) +      def save(*) #:nodoc: +        if status = super            @previously_changed = changes            @changed_attributes.clear          end @@ -26,70 +26,70 @@ module ActiveRecord        end        # Attempts to <tt>save!</tt> the record and clears changed attributes if successful. -      def save_with_dirty!(*args) #:nodoc: -        save_without_dirty!(*args).tap do +      def save!(*) #:nodoc: +        super.tap do            @previously_changed = changes            @changed_attributes.clear          end        end        # <tt>reload</tt> the record and clears changed attributes. -      def reload_with_dirty(*args) #:nodoc: -        reload_without_dirty(*args).tap do +      def reload(*) #:nodoc: +        super.tap do            @previously_changed.clear            @changed_attributes.clear          end        end -      private -        # Wrap write_attribute to remember original attribute value. -        def write_attribute(attr, value) -          attr = attr.to_s +    private +      # Wrap write_attribute to remember original attribute value. +      def write_attribute(attr, value) +        attr = attr.to_s -          # The attribute already has an unsaved change. -          if attribute_changed?(attr) -            old = @changed_attributes[attr] -            @changed_attributes.delete(attr) unless field_changed?(attr, old, value) -          else -            old = clone_attribute_value(:read_attribute, attr) -            # Save Time objects as TimeWithZone if time_zone_aware_attributes == true -            old = old.in_time_zone if clone_with_time_zone_conversion_attribute?(attr, old) -            @changed_attributes[attr] = old if field_changed?(attr, old, value) -          end +        # The attribute already has an unsaved change. +        if attribute_changed?(attr) +          old = @changed_attributes[attr] +          @changed_attributes.delete(attr) unless field_changed?(attr, old, value) +        else +          old = clone_attribute_value(:read_attribute, attr) +          # Save Time objects as TimeWithZone if time_zone_aware_attributes == true +          old = old.in_time_zone if clone_with_time_zone_conversion_attribute?(attr, old) +          @changed_attributes[attr] = old if field_changed?(attr, old, value) +        end -          # Carry on. -          super(attr, value) +        # Carry on. +        super(attr, value) +      end + +      def update(*) +        if partial_updates? +          # Serialized attributes should always be written in case they've been +          # changed in place. +          super(changed | (attributes.keys & self.class.serialized_attributes.keys)) +        else +          super          end +      end -        def update_with_dirty -          if partial_updates? -            # Serialized attributes should always be written in case they've been -            # changed in place. -            update_without_dirty(changed | (attributes.keys & self.class.serialized_attributes.keys)) +      def field_changed?(attr, old, value) +        if column = column_for_attribute(attr) +          if column.number? && column.null && (old.nil? || old == 0) && value.blank? +            # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values. +            # Hence we don't record it as a change if the value changes from nil to ''. +            # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll +            # be typecast back to 0 (''.to_i => 0) +            value = nil            else -            update_without_dirty +            value = column.type_cast(value)            end          end -        def field_changed?(attr, old, value) -          if column = column_for_attribute(attr) -            if column.number? && column.null && (old.nil? || old == 0) && value.blank? -              # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values. -              # Hence we don't record it as a change if the value changes from nil to ''. -              # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll -              # be typecast back to 0 (''.to_i => 0) -              value = nil -            else -              value = column.type_cast(value) -            end -          end - -          old != value -        end +        old != value +      end -        def clone_with_time_zone_conversion_attribute?(attr, old) -          old.class.name == "Time" && time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(attr.to_sym) -        end +      def clone_with_time_zone_conversion_attribute?(attr, old) +        old.class.name == "Time" && time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(attr.to_sym) +      end      end    end  end diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 325a8aa7ec..fd1082a268 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -130,8 +130,6 @@ module ActiveRecord      ASSOCIATION_TYPES = %w{ has_one belongs_to has_many has_and_belongs_to_many }      included do -      alias_method_chain :reload, :autosave_associations -        ASSOCIATION_TYPES.each do |type|          send("valid_keys_for_#{type}_association") << :autosave        end @@ -196,9 +194,9 @@ module ActiveRecord      end      # Reloads the attributes of the object as usual and removes a mark for destruction. -    def reload_with_autosave_associations(options = nil) +    def reload(options = nil)        @marked_for_destruction = false -      reload_without_autosave_associations(options) +      super      end      # Marks this record to be destroyed as part of the parents save transaction. diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 9ed53cc4af..650a91b385 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -480,110 +480,6 @@ module ActiveRecord #:nodoc:          connection.select_value(sql, "#{name} Count").to_i        end -      # Resets one or more counter caches to their correct value using an SQL -      # count query.  This is useful when adding new counter caches, or if the -      # counter has been corrupted or modified directly by SQL. -      # -      # ==== Parameters -      # -      # * +id+ - The id of the object you wish to reset a counter on. -      # * +counters+ - One or more counter names to reset -      # -      # ==== Examples -      # -      #   # For Post with id #1 records reset the comments_count -      #   Post.reset_counters(1, :comments) -      def reset_counters(id, *counters) -        object = find(id) -        counters.each do |association| -          child_class = reflect_on_association(association).klass -          counter_name = child_class.reflect_on_association(self.name.downcase.to_sym).counter_cache_column - -          connection.update("UPDATE #{quoted_table_name} SET #{connection.quote_column_name(counter_name)} = #{object.send(association).count} WHERE #{connection.quote_column_name(primary_key)} = #{quote_value(object.id)}", "#{name} UPDATE") -        end -      end - -      # A generic "counter updater" implementation, intended primarily to be -      # used by increment_counter and decrement_counter, but which may also -      # be useful on its own. It simply does a direct SQL update for the record -      # with the given ID, altering the given hash of counters by the amount -      # given by the corresponding value: -      # -      # ==== Parameters -      # -      # * +id+ - The id of the object you wish to update a counter on or an Array of ids. -      # * +counters+ - An Array of Hashes containing the names of the fields -      #   to update as keys and the amount to update the field by as values. -      # -      # ==== Examples -      # -      #   # For the Post with id of 5, decrement the comment_count by 1, and -      #   # increment the action_count by 1 -      #   Post.update_counters 5, :comment_count => -1, :action_count => 1 -      #   # Executes the following SQL: -      #   # UPDATE posts -      #   #    SET comment_count = comment_count - 1, -      #   #        action_count = action_count + 1 -      #   #  WHERE id = 5 -      # -      #   # For the Posts with id of 10 and 15, increment the comment_count by 1 -      #   Post.update_counters [10, 15], :comment_count => 1 -      #   # Executes the following SQL: -      #   # UPDATE posts -      #   #    SET comment_count = comment_count + 1, -      #   #  WHERE id IN (10, 15) -      def update_counters(id, counters) -        updates = counters.inject([]) { |list, (counter_name, increment)| -          sign = increment < 0 ? "-" : "+" -          list << "#{connection.quote_column_name(counter_name)} = COALESCE(#{connection.quote_column_name(counter_name)}, 0) #{sign} #{increment.abs}" -        }.join(", ") - -        if id.is_a?(Array) -          ids_list = id.map {|i| quote_value(i)}.join(', ') -          condition = "IN  (#{ids_list})" -        else -          condition = "= #{quote_value(id)}" -        end - -        update_all(updates, "#{connection.quote_column_name(primary_key)} #{condition}") -      end - -      # Increment a number field by one, usually representing a count. -      # -      # This is used for caching aggregate values, so that they don't need to be computed every time. -      # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is -      # shown it would have to run an SQL query to find how many posts and comments there are. -      # -      # ==== Parameters -      # -      # * +counter_name+ - The name of the field that should be incremented. -      # * +id+ - The id of the object that should be incremented. -      # -      # ==== Examples -      # -      #   # Increment the post_count column for the record with an id of 5 -      #   DiscussionBoard.increment_counter(:post_count, 5) -      def increment_counter(counter_name, id) -        update_counters(id, counter_name => 1) -      end - -      # Decrement a number field by one, usually representing a count. -      # -      # This works the same as increment_counter but reduces the column value by 1 instead of increasing it. -      # -      # ==== Parameters -      # -      # * +counter_name+ - The name of the field that should be decremented. -      # * +id+ - The id of the object that should be decremented. -      # -      # ==== Examples -      # -      #   # Decrement the post_count column for the record with an id of 5 -      #   DiscussionBoard.decrement_counter(:post_count, 5) -      def decrement_counter(counter_name, id) -        update_counters(id, counter_name => -1) -      end -        # Attributes named in this macro are protected from mass-assignment,        # such as <tt>new(attributes)</tt>,        # <tt>update_attributes(attributes)</tt>, or @@ -1623,186 +1519,6 @@ module ActiveRecord #:nodoc:          quote_value(id, column_for_attribute(self.class.primary_key))        end -      # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false. -      def new_record? -        @new_record -      end - -      # Returns true if this object has been destroyed, otherwise returns false. -      def destroyed? -        @destroyed -      end - -      # Returns if the record is persisted, i.e. it's not a new record and it was not destroyed. -      def persisted? -        !(new_record? || destroyed?) -      end - -      # :call-seq: -      #   save(options) -      # -      # Saves the model. -      # -      # If the model is new a record gets created in the database, otherwise -      # the existing record gets updated. -      # -      # By default, save always run validations. If any of them fail the action -      # is cancelled and +save+ returns +false+. However, if you supply -      # :validate => 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 - -      # 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 - -      # Deletes the record in the database and freezes this instance to -      # reflect that no changes should be made (since they can't be -      # persisted). Returns the frozen instance. -      # -      # The row is simply removed with a SQL +DELETE+ statement on the -      # record's primary key, and no callbacks are executed. -      # -      # To enforce the object's +before_destroy+ and +after_destroy+ -      # callbacks, Observer methods, or any <tt>:dependent</tt> association -      # options, use <tt>#destroy</tt>. -      def delete -        self.class.delete(id) if persisted? -        @destroyed = true -        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 -        if persisted? -          self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all -        end - -        @destroyed = true -        freeze -      end - -      # Returns an instance of the specified +klass+ with the attributes of the current record. This is mostly useful in relation to -      # single-table inheritance structures where you want a subclass to appear as the superclass. This can be used along with record -      # identification in Action Pack to allow, say, <tt>Client < Company</tt> to do something like render <tt>:partial => @client.becomes(Company)</tt> -      # to render that instance using the companies/company partial instead of clients/client. -      # -      # Note: The new instance will share a link to the same attributes as the original class. So any change to the attributes in either -      # instance will affect the other. -      def becomes(klass) -        became = klass.new -        became.instance_variable_set("@attributes", @attributes) -        became.instance_variable_set("@attributes_cache", @attributes_cache) -        became.instance_variable_set("@new_record", new_record?) -        became.instance_variable_set("@destroyed", destroyed?) -        became -      end - -      # Updates a single attribute and saves the record without going through the normal validation procedure. -      # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method -      # in Base is replaced with this when the validations module is mixed in, which it is by default. -      def update_attribute(name, value) -        send("#{name}=", value) -        save(:validate => false) -      end - -      # Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will -      # fail and false will be returned. -      def update_attributes(attributes) -        self.attributes = attributes -        save -      end - -      # Updates an object just like Base.update_attributes but calls save! instead of save so an exception is raised if the record is invalid. -      def update_attributes!(attributes) -        self.attributes = attributes -        save! -      end - -      # Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1). -      # The increment is performed directly on the underlying attribute, no setter is invoked. -      # Only makes sense for number-based attributes. Returns +self+. -      def increment(attribute, by = 1) -        self[attribute] ||= 0 -        self[attribute] += by -        self -      end - -      # Wrapper around +increment+ that saves the record. This method differs from -      # its non-bang version in that it passes through the attribute setter. -      # Saving is not subjected to validation checks. Returns +true+ if the -      # record could be saved. -      def increment!(attribute, by = 1) -        increment(attribute, by).update_attribute(attribute, self[attribute]) -      end - -      # Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1). -      # The decrement is performed directly on the underlying attribute, no setter is invoked. -      # Only makes sense for number-based attributes. Returns +self+. -      def decrement(attribute, by = 1) -        self[attribute] ||= 0 -        self[attribute] -= by -        self -      end - -      # Wrapper around +decrement+ that saves the record. This method differs from -      # its non-bang version in that it passes through the attribute setter. -      # Saving is not subjected to validation checks. Returns +true+ if the -      # record could be saved. -      def decrement!(attribute, by = 1) -        decrement(attribute, by).update_attribute(attribute, self[attribute]) -      end - -      # Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So -      # if the predicate returns +true+ the attribute will become +false+. This -      # method toggles directly the underlying value without calling any setter. -      # Returns +self+. -      def toggle(attribute) -        self[attribute] = !send("#{attribute}?") -        self -      end - -      # Wrapper around +toggle+ that saves the record. This method differs from -      # its non-bang version in that it passes through the attribute setter. -      # Saving is not subjected to validation checks. Returns +true+ if the -      # record could be saved. -      def toggle!(attribute) -        toggle(attribute).update_attribute(attribute, self[attribute]) -      end - -      # Reloads the attributes of this object from the database. -      # The optional options argument is passed to find when reloading so you -      # may do e.g. record.reload(:lock => true) to reload the same record with -      # an exclusive row lock. -      def reload(options = nil) -        clear_aggregation_cache -        clear_association_cache -        @attributes.update(self.class.send(:with_exclusive_scope) { self.class.find(self.id, options) }.instance_variable_get('@attributes')) -        @attributes_cache = {} -        self -      end -        # Returns true if the given attribute is in the attributes hash        def has_attribute?(attr_name)          @attributes.has_key?(attr_name.to_s) @@ -1980,40 +1696,6 @@ module ActiveRecord #:nodoc:        end      private -      def create_or_update -        raise ReadOnlyRecord if readonly? -        result = new_record? ? create : update -        result != false -      end - -      # Updates the associated record with values matching those of the instance attributes. -      # Returns the number of affected rows. -      def update(attribute_names = @attributes.keys) -        attributes_with_values = arel_attributes_values(false, false, attribute_names) -        return 0 if attributes_with_values.empty? -        self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values) -      end - -      # Creates a record with values matching those of the instance attributes -      # and returns its id. -      def create -        if self.id.nil? && connection.prefetch_primary_key?(self.class.table_name) -          self.id = connection.next_sequence_value(self.class.sequence_name) -        end - -        attributes_values = arel_attributes_values - -        new_id = if attributes_values.empty? -          self.class.unscoped.insert connection.empty_insert_statement_value -        else -          self.class.unscoped.insert attributes_values -        end - -        self.id ||= new_id - -        @new_record = false -        id -      end        # Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord::Base descendant.        # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to do Reply.new without having to @@ -2099,17 +1781,6 @@ module ActiveRecord #:nodoc:          instance_eval("%@#{sql.gsub('@', '\@')}@")        end -      # Initializes the attributes array with keys matching the columns from the linked table and -      # the values matching the corresponding default value of that column, so -      # that a new instance, or one populated from a passed-in Hash, still has all the attributes -      # that instances loaded from the database would. -      def attributes_from_column_definition -        self.class.columns.inject({}) do |attributes, column| -          attributes[column.name] = column.default unless column.name == self.class.primary_key -          attributes -        end -      end -        # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done        # by calling new on the column type or aggregation type (through composed_of) object with these parameters.        # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate @@ -2225,12 +1896,14 @@ module ActiveRecord #:nodoc:    end    Base.class_eval do +    include ActiveRecord::Persistence      extend ActiveModel::Naming      extend QueryCache::ClassMethods      extend ActiveSupport::Benchmarkable      include ActiveModel::Conversion      include Validations +    extend CounterCache      include Locking::Optimistic, Locking::Pessimistic      include AttributeMethods      include AttributeMethods::Read, AttributeMethods::Write, AttributeMethods::BeforeTypeCast, AttributeMethods::Query diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 7ebeb6079e..498836aca4 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -233,10 +233,6 @@ module ActiveRecord      ]      included do -      [:create_or_update, :valid?, :create, :update, :destroy].each do |method| -        alias_method_chain method, :callbacks -      end -        extend ActiveModel::Callbacks        define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name] @@ -273,45 +269,33 @@ module ActiveRecord        end      end -    def create_or_update_with_callbacks #:nodoc: -      _run_save_callbacks do -        create_or_update_without_callbacks -      end +    def valid?(*) #:nodoc: +      @_on_validate = new_record? ? :create : :update +      _run_validation_callbacks { super }      end -    private :create_or_update_with_callbacks -    def create_with_callbacks #:nodoc: -      _run_create_callbacks do -        create_without_callbacks -      end +    def destroy #:nodoc: +      _run_destroy_callbacks { super }      end -    private :create_with_callbacks -    def update_with_callbacks(*args) #:nodoc: -      _run_update_callbacks do -        update_without_callbacks(*args) +    def deprecated_callback_method(symbol) #:nodoc: +      if respond_to?(symbol, true) +        ActiveSupport::Deprecation.warn("Overwriting #{symbol} in your models has been deprecated, please use Base##{symbol} :method_name instead") +        send(symbol)        end      end -    private :update_with_callbacks -    def valid_with_callbacks? #:nodoc: -      @_on_validate = new_record? ? :create : :update -      _run_validation_callbacks do -        valid_without_callbacks? -      end +  private +    def create_or_update #:nodoc: +      _run_save_callbacks { super }      end -    def destroy_with_callbacks #:nodoc: -      _run_destroy_callbacks do -        destroy_without_callbacks -      end +    def create #:nodoc: +      _run_create_callbacks { super }      end -    def deprecated_callback_method(symbol) #:nodoc: -      if respond_to?(symbol, true) -        ActiveSupport::Deprecation.warn("Overwriting #{symbol} in your models has been deprecated, please use Base##{symbol} :method_name instead") -        send(symbol) -      end +    def update(*) #:nodoc: +      _run_update_callbacks { super }      end    end  end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index 533a7bb8e6..78fffaff6e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -5,23 +5,16 @@ module ActiveRecord      module QueryCache        class << self          def included(base) -          base.class_eval do -            alias_method_chain :columns, :query_cache -            alias_method_chain :select_all, :query_cache -          end -            dirties_query_cache base, :insert, :update, :delete          end          def dirties_query_cache(base, *method_names)            method_names.each do |method_name|              base.class_eval <<-end_code, __FILE__, __LINE__ + 1 -              def #{method_name}_with_query_dirty(*args)        # def update_with_query_dirty(*args) -                clear_query_cache if @query_cache_enabled       #   clear_query_cache if @query_cache_enabled -                #{method_name}_without_query_dirty(*args)       #   update_without_query_dirty(*args) -              end                                               # end -                                                                # -              alias_method_chain :#{method_name}, :query_dirty  # alias_method_chain :update, :query_dirty +              def #{method_name}(*)                         # def update_with_query_dirty(*args) +                clear_query_cache if @query_cache_enabled   #   clear_query_cache if @query_cache_enabled +                super                                       #   update_without_query_dirty(*args) +              end                                           # end              end_code            end          end @@ -56,19 +49,19 @@ module ActiveRecord          @query_cache.clear        end -      def select_all_with_query_cache(*args) +      def select_all(*args)          if @query_cache_enabled -          cache_sql(args.first) { select_all_without_query_cache(*args) } +          cache_sql(args.first) { super }          else -          select_all_without_query_cache(*args) +          super          end        end -      def columns_with_query_cache(*args) +      def columns(*)          if @query_cache_enabled -          @query_cache["SHOW FIELDS FROM #{args.first}"] ||= columns_without_query_cache(*args) +          @query_cache["SHOW FIELDS FROM #{args.first}"] ||= super          else -          columns_without_query_cache(*args) +          super          end        end diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb new file mode 100644 index 0000000000..cbebded995 --- /dev/null +++ b/activerecord/lib/active_record/counter_cache.rb @@ -0,0 +1,107 @@ +module ActiveRecord +  module CounterCache +    # Resets one or more counter caches to their correct value using an SQL +    # count query.  This is useful when adding new counter caches, or if the +    # counter has been corrupted or modified directly by SQL. +    # +    # ==== Parameters +    # +    # * +id+ - The id of the object you wish to reset a counter on. +    # * +counters+ - One or more counter names to reset +    # +    # ==== Examples +    # +    #   # For Post with id #1 records reset the comments_count +    #   Post.reset_counters(1, :comments) +    def reset_counters(id, *counters) +      object = find(id) +      counters.each do |association| +        child_class = reflect_on_association(association).klass +        counter_name = child_class.reflect_on_association(self.name.downcase.to_sym).counter_cache_column + +        connection.update("UPDATE #{quoted_table_name} SET #{connection.quote_column_name(counter_name)} = #{object.send(association).count} WHERE #{connection.quote_column_name(primary_key)} = #{quote_value(object.id)}", "#{name} UPDATE") +      end +    end + +    # A generic "counter updater" implementation, intended primarily to be +    # used by increment_counter and decrement_counter, but which may also +    # be useful on its own. It simply does a direct SQL update for the record +    # with the given ID, altering the given hash of counters by the amount +    # given by the corresponding value: +    # +    # ==== Parameters +    # +    # * +id+ - The id of the object you wish to update a counter on or an Array of ids. +    # * +counters+ - An Array of Hashes containing the names of the fields +    #   to update as keys and the amount to update the field by as values. +    # +    # ==== Examples +    # +    #   # For the Post with id of 5, decrement the comment_count by 1, and +    #   # increment the action_count by 1 +    #   Post.update_counters 5, :comment_count => -1, :action_count => 1 +    #   # Executes the following SQL: +    #   # UPDATE posts +    #   #    SET comment_count = comment_count - 1, +    #   #        action_count = action_count + 1 +    #   #  WHERE id = 5 +    # +    #   # For the Posts with id of 10 and 15, increment the comment_count by 1 +    #   Post.update_counters [10, 15], :comment_count => 1 +    #   # Executes the following SQL: +    #   # UPDATE posts +    #   #    SET comment_count = comment_count + 1, +    #   #  WHERE id IN (10, 15) +    def update_counters(id, counters) +      updates = counters.inject([]) { |list, (counter_name, increment)| +        sign = increment < 0 ? "-" : "+" +        list << "#{connection.quote_column_name(counter_name)} = COALESCE(#{connection.quote_column_name(counter_name)}, 0) #{sign} #{increment.abs}" +      }.join(", ") + +      if id.is_a?(Array) +        ids_list = id.map {|i| quote_value(i)}.join(', ') +        condition = "IN  (#{ids_list})" +      else +        condition = "= #{quote_value(id)}" +      end + +      update_all(updates, "#{connection.quote_column_name(primary_key)} #{condition}") +    end + +    # Increment a number field by one, usually representing a count. +    # +    # This is used for caching aggregate values, so that they don't need to be computed every time. +    # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is +    # shown it would have to run an SQL query to find how many posts and comments there are. +    # +    # ==== Parameters +    # +    # * +counter_name+ - The name of the field that should be incremented. +    # * +id+ - The id of the object that should be incremented. +    # +    # ==== Examples +    # +    #   # Increment the post_count column for the record with an id of 5 +    #   DiscussionBoard.increment_counter(:post_count, 5) +    def increment_counter(counter_name, id) +      update_counters(id, counter_name => 1) +    end + +    # Decrement a number field by one, usually representing a count. +    # +    # This works the same as increment_counter but reduces the column value by 1 instead of increasing it. +    # +    # ==== Parameters +    # +    # * +counter_name+ - The name of the field that should be decremented. +    # * +id+ - The id of the object that should be decremented. +    # +    # ==== Examples +    # +    #   # Decrement the post_count column for the record with an id of 5 +    #   DiscussionBoard.decrement_counter(:post_count, 5) +    def decrement_counter(counter_name, id) +      update_counters(id, counter_name => -1) +    end +  end +end
\ No newline at end of file diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 60ad23f38c..71057efa15 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -48,10 +48,6 @@ module ActiveRecord          cattr_accessor :lock_optimistically, :instance_writer => false          self.lock_optimistically = true -        alias_method_chain :update, :lock -        alias_method_chain :destroy, :lock -        alias_method_chain :attributes_from_column_definition, :lock -          class << self            alias_method :locking_column=, :set_locking_column          end @@ -62,8 +58,8 @@ module ActiveRecord        end        private -        def attributes_from_column_definition_with_lock -          result = attributes_from_column_definition_without_lock +        def attributes_from_column_definition +          result = super            # If the locking column has no default value set,            # start the lock version at zero.  Note we can't use @@ -77,8 +73,8 @@ module ActiveRecord            return result          end -        def update_with_lock(attribute_names = @attributes.keys) #:nodoc: -          return update_without_lock(attribute_names) unless locking_enabled? +        def update(attribute_names = @attributes.keys) #:nodoc: +          return super unless locking_enabled?            return 0 if attribute_names.empty?            lock_col = self.class.locking_column @@ -97,7 +93,6 @@ module ActiveRecord                )              ).arel.update(arel_attributes_values(false, false, attribute_names)) -              unless affected_rows == 1                raise ActiveRecord::StaleObjectError, "Attempted to update a stale object: #{self.class.name}"              end @@ -111,8 +106,8 @@ module ActiveRecord            end          end -        def destroy_with_lock #:nodoc: -          return destroy_without_lock unless locking_enabled? +        def destroy #:nodoc: +          return super unless locking_enabled?            unless new_record?              lock_col = self.class.locking_column @@ -136,12 +131,6 @@ module ActiveRecord        module ClassMethods          DEFAULT_LOCKING_COLUMN = 'lock_version' -        def self.extended(base) -          class <<base -            alias_method_chain :update_counters, :lock -          end -        end -          # Is optimistic locking enabled for this table? Returns true if the          # +lock_optimistically+ flag is set to true (which it is, by default)          # and the table includes the +locking_column+ column (defaults to @@ -173,9 +162,9 @@ module ActiveRecord          # Make sure the lock version column gets updated when counters are          # updated. -        def update_counters_with_lock(id, counters) +        def update_counters(id, counters)            counters = counters.merge(locking_column => 1) if locking_enabled? -          update_counters_without_lock(id, counters) +          super          end        end      end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb new file mode 100644 index 0000000000..10788630a5 --- /dev/null +++ b/activerecord/lib/active_record/persistence.rb @@ -0,0 +1,230 @@ +module ActiveRecord +  module Persistence +    # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false. +    def new_record? +      @new_record +    end + +    # Returns true if this object has been destroyed, otherwise returns false. +    def destroyed? +      @destroyed +    end + +    # Returns if the record is persisted, i.e. it's not a new record and it was not destroyed. +    def persisted? +      !(new_record? || destroyed?) +    end + +    # :call-seq: +    #   save(options) +    # +    # Saves the model. +    # +    # If the model is new a record gets created in the database, otherwise +    # the existing record gets updated. +    # +    # By default, save always run validations. If any of them fail the action +    # is cancelled and +save+ returns +false+. However, if you supply +    # :validate => 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 + +    # 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 + +    # Deletes the record in the database and freezes this instance to +    # reflect that no changes should be made (since they can't be +    # persisted). Returns the frozen instance. +    # +    # The row is simply removed with a SQL +DELETE+ statement on the +    # record's primary key, and no callbacks are executed. +    # +    # To enforce the object's +before_destroy+ and +after_destroy+ +    # callbacks, Observer methods, or any <tt>:dependent</tt> association +    # options, use <tt>#destroy</tt>. +    def delete +      self.class.delete(id) if persisted? +      @destroyed = true +      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 +      if persisted? +        self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all +      end + +      @destroyed = true +      freeze +    end + +    # Returns an instance of the specified +klass+ with the attributes of the current record. This is mostly useful in relation to +    # single-table inheritance structures where you want a subclass to appear as the superclass. This can be used along with record +    # identification in Action Pack to allow, say, <tt>Client < Company</tt> to do something like render <tt>:partial => @client.becomes(Company)</tt> +    # to render that instance using the companies/company partial instead of clients/client. +    # +    # Note: The new instance will share a link to the same attributes as the original class. So any change to the attributes in either +    # instance will affect the other. +    def becomes(klass) +      became = klass.new +      became.instance_variable_set("@attributes", @attributes) +      became.instance_variable_set("@attributes_cache", @attributes_cache) +      became.instance_variable_set("@new_record", new_record?) +      became.instance_variable_set("@destroyed", destroyed?) +      became +    end + +    # Updates a single attribute and saves the record without going through the normal validation procedure. +    # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method +    # in Base is replaced with this when the validations module is mixed in, which it is by default. +    def update_attribute(name, value) +      send("#{name}=", value) +      save(:validate => false) +    end + +    # Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will +    # fail and false will be returned. +    def update_attributes(attributes) +      self.attributes = attributes +      save +    end + +    # Updates an object just like Base.update_attributes but calls save! instead of save so an exception is raised if the record is invalid. +    def update_attributes!(attributes) +      self.attributes = attributes +      save! +    end + +    # Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1). +    # The increment is performed directly on the underlying attribute, no setter is invoked. +    # Only makes sense for number-based attributes. Returns +self+. +    def increment(attribute, by = 1) +      self[attribute] ||= 0 +      self[attribute] += by +      self +    end + +    # Wrapper around +increment+ that saves the record. This method differs from +    # its non-bang version in that it passes through the attribute setter. +    # Saving is not subjected to validation checks. Returns +true+ if the +    # record could be saved. +    def increment!(attribute, by = 1) +      increment(attribute, by).update_attribute(attribute, self[attribute]) +    end + +    # Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1). +    # The decrement is performed directly on the underlying attribute, no setter is invoked. +    # Only makes sense for number-based attributes. Returns +self+. +    def decrement(attribute, by = 1) +      self[attribute] ||= 0 +      self[attribute] -= by +      self +    end + +    # Wrapper around +decrement+ that saves the record. This method differs from +    # its non-bang version in that it passes through the attribute setter. +    # Saving is not subjected to validation checks. Returns +true+ if the +    # record could be saved. +    def decrement!(attribute, by = 1) +      decrement(attribute, by).update_attribute(attribute, self[attribute]) +    end + +    # Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So +    # if the predicate returns +true+ the attribute will become +false+. This +    # method toggles directly the underlying value without calling any setter. +    # Returns +self+. +    def toggle(attribute) +      self[attribute] = !send("#{attribute}?") +      self +    end + +    # Wrapper around +toggle+ that saves the record. This method differs from +    # its non-bang version in that it passes through the attribute setter. +    # Saving is not subjected to validation checks. Returns +true+ if the +    # record could be saved. +    def toggle!(attribute) +      toggle(attribute).update_attribute(attribute, self[attribute]) +    end + +    # Reloads the attributes of this object from the database. +    # The optional options argument is passed to find when reloading so you +    # may do e.g. record.reload(:lock => true) to reload the same record with +    # an exclusive row lock. +    def reload(options = nil) +      clear_aggregation_cache +      clear_association_cache +      @attributes.update(self.class.send(:with_exclusive_scope) { self.class.find(self.id, options) }.instance_variable_get('@attributes')) +      @attributes_cache = {} +      self +    end + +  private +    def create_or_update +      raise ReadOnlyRecord if readonly? +      result = new_record? ? create : update +      result != false +    end + +    # Updates the associated record with values matching those of the instance attributes. +    # Returns the number of affected rows. +    def update(attribute_names = @attributes.keys) +      attributes_with_values = arel_attributes_values(false, false, attribute_names) +      return 0 if attributes_with_values.empty? +      self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values) +    end + +    # Creates a record with values matching those of the instance attributes +    # and returns its id. +    def create +      if self.id.nil? && connection.prefetch_primary_key?(self.class.table_name) +        self.id = connection.next_sequence_value(self.class.sequence_name) +      end + +      attributes_values = arel_attributes_values + +      new_id = if attributes_values.empty? +        self.class.unscoped.insert connection.empty_insert_statement_value +      else +        self.class.unscoped.insert attributes_values +      end + +      self.id ||= new_id + +      @new_record = false +      id +    end + +    # Initializes the attributes array with keys matching the columns from the linked table and +    # the values matching the corresponding default value of that column, so +    # that a new instance, or one populated from a passed-in Hash, still has all the attributes +    # that instances loaded from the database would. +    def attributes_from_column_definition +      self.class.columns.inject({}) do |attributes, column| +        attributes[column.name] = column.default unless column.name == self.class.primary_key +        attributes +      end +    end +  end +end
\ No newline at end of file diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index da075dabd3..9fba8f0aca 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -11,9 +11,6 @@ module ActiveRecord      extend ActiveSupport::Concern      included do -      alias_method_chain :create, :timestamps -      alias_method_chain :update, :timestamps -        class_inheritable_accessor :record_timestamps, :instance_writer => false        self.record_timestamps = true      end @@ -39,35 +36,34 @@ module ActiveRecord        save!      end +  private +    def create #:nodoc: +      if record_timestamps +        current_time = current_time_from_proper_timezone -    private -      def create_with_timestamps #:nodoc: -        if record_timestamps -          current_time = current_time_from_proper_timezone - -          write_attribute('created_at', current_time) if respond_to?(:created_at) && created_at.nil? -          write_attribute('created_on', current_time) if respond_to?(:created_on) && created_on.nil? - -          write_attribute('updated_at', current_time) if respond_to?(:updated_at) && updated_at.nil? -          write_attribute('updated_on', current_time) if respond_to?(:updated_on) && updated_on.nil? -        end +        write_attribute('created_at', current_time) if respond_to?(:created_at) && created_at.nil? +        write_attribute('created_on', current_time) if respond_to?(:created_on) && created_on.nil? -        create_without_timestamps +        write_attribute('updated_at', current_time) if respond_to?(:updated_at) && updated_at.nil? +        write_attribute('updated_on', current_time) if respond_to?(:updated_on) && updated_on.nil?        end -      def update_with_timestamps(*args) #:nodoc: -        if record_timestamps && (!partial_updates? || changed?) -          current_time = current_time_from_proper_timezone +      super +    end -          write_attribute('updated_at', current_time) if respond_to?(:updated_at) -          write_attribute('updated_on', current_time) if respond_to?(:updated_on) -        end +    def update(*args) #:nodoc: +      if record_timestamps && (!partial_updates? || changed?) +        current_time = current_time_from_proper_timezone -        update_without_timestamps(*args) -      end -       -      def current_time_from_proper_timezone -        self.class.default_timezone == :utc ? Time.now.utc : Time.now +        write_attribute('updated_at', current_time) if respond_to?(:updated_at) +        write_attribute('updated_on', current_time) if respond_to?(:updated_on)        end + +      super +    end +     +    def current_time_from_proper_timezone +      self.class.default_timezone == :utc ? Time.now.utc : Time.now +    end    end  end
\ No newline at end of file diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 1a195fbb81..5a8e2ce880 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -9,10 +9,6 @@ module ActiveRecord      end      included do -      [:destroy, :save, :save!].each do |method| -        alias_method_chain method, :transactions -      end -        define_model_callbacks :commit, :commit_on_update, :commit_on_create, :commit_on_destroy, :only => :after        define_model_callbacks :rollback, :rollback_on_update, :rollback_on_create, :rollback_on_destroy      end @@ -213,16 +209,18 @@ module ActiveRecord        self.class.transaction(&block)      end -    def destroy_with_transactions #:nodoc: -      with_transaction_returning_status(:destroy_without_transactions) +    def destroy #:nodoc: +      with_transaction_returning_status { super }      end -    def save_with_transactions(*args) #:nodoc: -      rollback_active_record_state! { with_transaction_returning_status(:save_without_transactions, *args) } +    def save(*) #:nodoc: +      rollback_active_record_state! do +        with_transaction_returning_status { super } +      end      end -    def save_with_transactions! #:nodoc: -      with_transaction_returning_status(:save_without_transactions!) +    def save!(*) #:nodoc: +      with_transaction_returning_status { super }      end      # Reset id and @new_record if the transaction rolls back. @@ -279,11 +277,11 @@ module ActiveRecord      #      # This method is available within the context of an ActiveRecord::Base      # instance. -    def with_transaction_returning_status(method, *args) +    def with_transaction_returning_status        status = nil        self.class.transaction do          add_to_transaction -        status = send(method, *args) +        status = yield          raise ActiveRecord::Rollback unless status        end        status diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index b2ee51fa51..55c4236874 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -19,11 +19,6 @@ module ActiveRecord      extend ActiveSupport::Concern      include ActiveModel::Validations -    included do -      alias_method_chain :save, :validation -      alias_method_chain :save!, :validation -    end -      module ClassMethods        # Creates an object just like Base.create but calls save! instead of save        # so an exception is raised if the record is invalid. @@ -39,39 +34,37 @@ module ActiveRecord        end      end -    module InstanceMethods -      # The validation process on save can be skipped by passing false. The regular Base#save method is -      # replaced with this when the validations module is mixed in, which it is by default. -      def save_with_validation(options=nil) -        perform_validation = case options -          when NilClass -            true -          when Hash -            options[:validate] != false -          else -            ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller -            options -        end +    # The validation process on save can be skipped by passing false. The regular Base#save method is +    # replaced with this when the validations module is mixed in, which it is by default. +    def save(options=nil) +      return super if valid?(options) +      false +    end -        if perform_validation && valid? || !perform_validation -          save_without_validation -        else -          false -        end -      end +    def save_without_validation! +      save!(:validate => false) +    end + +    # Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false +    # if the record is not valid. +    def save!(options = nil) +      return super if valid?(options) +      raise RecordInvalid.new(self) +    end -      # Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false -      # if the record is not valid. -      def save_with_validation! -        if valid? -          save_without_validation! +    # Runs all the specified validations and returns true if no errors were added otherwise false. +    def valid?(options = nil) +      perform_validation = case options +        when NilClass +          true +        when Hash +          options[:validate] != false          else -          raise RecordInvalid.new(self) -        end +          ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller +          options        end -      # Runs all the specified validations and returns true if no errors were added otherwise false. -      def valid? +      if perform_validation          errors.clear          self.validation_context = new_record? ? :create : :update @@ -86,16 +79,12 @@ module ActiveRecord          end          errors.empty? -      end - -      def invalid? -        !valid? +      else +        true        end      end    end  end -Dir[File.dirname(__FILE__) + "/validations/*.rb"].sort.each do |path| -  filename = File.basename(path) -  require "active_record/validations/#{filename}" -end +require "active_record/validations/associated" +require "active_record/validations/uniqueness" diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index aa2d9527f9..66874cdad1 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -195,7 +195,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase      assert_raises(ActiveRecord::RecordNotFound) { Person.find(p1.id) }      assert_raises(ActiveRecord::RecordNotFound) { LegacyThing.find(t.id) }    end -   +    def test_quote_table_name      ref = references(:michael_magician)      ref.favourite = !ref.favourite @@ -206,8 +206,11 @@ class OptimisticLockingTest < ActiveRecord::TestCase    # is nothing else being updated.    def test_update_without_attributes_does_not_only_update_lock_version      assert_nothing_raised do -      p1 = Person.new(:first_name => 'anika') -      p1.send(:update_with_lock, []) +      p1 = Person.create!(:first_name => 'anika') +      lock_version = p1.lock_version +      p1.save +      p1.reload +      assert_equal lock_version, p1.lock_version      end    end  | 
