diff options
author | Gonçalo Silva <goncalossilva@gmail.com> | 2010-07-10 17:36:10 +0100 |
---|---|---|
committer | Gonçalo Silva <goncalossilva@gmail.com> | 2010-07-10 17:36:10 +0100 |
commit | cd2bbed9846d84a1230a1b9e52843eedca17b28d (patch) | |
tree | 5214b7855f3d102e4c22239b9d62bc5717cb3547 /activerecord/lib | |
parent | d2c633ba0bfb7baacdee89a46d7d036d24c68817 (diff) | |
parent | 80e47d7b88dcc732ebeb5290faab6e529829dac6 (diff) | |
download | rails-cd2bbed9846d84a1230a1b9e52843eedca17b28d.tar.gz rails-cd2bbed9846d84a1230a1b9e52843eedca17b28d.tar.bz2 rails-cd2bbed9846d84a1230a1b9e52843eedca17b28d.zip |
Merge branch 'master' of http://github.com/rails/rails
Diffstat (limited to 'activerecord/lib')
15 files changed, 126 insertions, 216 deletions
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index f13c250ca4..cbec5789fd 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -282,7 +282,12 @@ module ActiveRecord end end else - records.first.class.preload_associations(records, through_association) + options = {} + options[:include] = reflection.options[:include] || reflection.options[:source] if reflection.options[:conditions] + options[:order] = reflection.options[:order] + options[:conditions] = reflection.options[:conditions] + records.first.class.preload_associations(records, through_association, options) + records.each do |record| through_records.concat Array.wrap(record.send(through_association)) end diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 4caa434fc0..65daa8ffbe 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -763,20 +763,27 @@ module ActiveRecord # An empty array is returned if none are found. # [collection<<(object, ...)] # Adds one or more objects to the collection by setting their foreign keys to the collection's primary key. + # Note that this operation instantly fires update sql without waiting for the save or update call on the + # parent object. # [collection.delete(object, ...)] # Removes one or more objects from the collection by setting their foreign keys to +NULL+. # Objects will be in addition destroyed if they're associated with <tt>:dependent => :destroy</tt>, # and deleted if they're associated with <tt>:dependent => :delete_all</tt>. # [collection=objects] - # Replaces the collections content by deleting and adding objects as appropriate. + # Replaces the collections content by deleting and adding objects as appropriate. If the <tt>:through</tt> + # option is true callbacks in the join models are triggered except destroy callbacks, since deletion is + # direct. # [collection_singular_ids] # Returns an array of the associated objects' ids # [collection_singular_ids=ids] - # Replace the collection with the objects identified by the primary keys in +ids+ + # Replace the collection with the objects identified by the primary keys in +ids+. This + # method loads the models and calls <tt>collection=</tt>. See above. # [collection.clear] # Removes every object from the collection. This destroys the associated objects if they # are associated with <tt>:dependent => :destroy</tt>, deletes them directly from the # database if <tt>:dependent => :delete_all</tt>, otherwise sets their foreign keys to +NULL+. + # If the <tt>:through</tt> option is true no destroy callbacks are invoked on the join models. + # Join models are directly deleted. # [collection.empty?] # Returns +true+ if there are no associated objects. # [collection.size] @@ -869,9 +876,11 @@ module ActiveRecord # [:as] # Specifies a polymorphic interface (See <tt>belongs_to</tt>). # [:through] - # Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt> + # Specifies a join model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt> # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt> - # <tt>has_one</tt> or <tt>has_many</tt> association on the join model. + # <tt>has_one</tt> or <tt>has_many</tt> association on the join model. The collection of join models can be managed via the collection + # API. For example, new join models are created for newly associated objects, and if some are gone their rows are deleted (directly, + # no destroy callbacks are triggered). # [:source] # Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be # inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or @@ -1186,6 +1195,8 @@ module ActiveRecord # [collection<<(object, ...)] # Adds one or more objects to the collection by creating associations in the join table # (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method). + # Note that this operation instantly fires update sql without waiting for the save or update call on the + # parent object. # [collection.delete(object, ...)] # Removes one or more objects from the collection by removing their associations from the join table. # This does not destroy the objects. diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index ddf4ce4058..615b7d2719 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -253,9 +253,10 @@ module ActiveRecord # See destroy for more info. def destroy_all load_target - destroy(@target) - reset_target! - reset_named_scopes_cache! + destroy(@target).tap do + reset_target! + reset_named_scopes_cache! + end end def create(attrs = {}) @@ -393,7 +394,12 @@ module ActiveRecord @target = find_target.map do |f| i = @target.index(f) t = @target.delete_at(i) if i - (t && t.changed?) ? t : f + if t && t.changed? + t + else + f.mark_for_destruction if t && t.marked_for_destruction? + f + end end + @target else @target = find_target diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index c8795e4496..c78060c956 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -24,7 +24,7 @@ require 'active_record/errors' require 'active_record/log_subscriber' module ActiveRecord #:nodoc: - # = Active Record + # = Active Record # # Active Record objects don't specify their attributes directly, but rather infer them from the table definition with # which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change @@ -476,112 +476,16 @@ module ActiveRecord #:nodoc: connection.select_value(sql, "#{name} Count").to_i end - # Attributes named in this macro are protected from mass-assignment, - # such as <tt>new(attributes)</tt>, - # <tt>update_attributes(attributes)</tt>, or - # <tt>attributes=(attributes)</tt>. - # - # Mass-assignment to these attributes will simply be ignored, to assign - # to them you can use direct writer methods. This is meant to protect - # sensitive attributes from being overwritten by malicious users - # tampering with URLs or forms. - # - # class Customer < ActiveRecord::Base - # attr_protected :credit_rating - # end - # - # customer = Customer.new("name" => David, "credit_rating" => "Excellent") - # customer.credit_rating # => nil - # customer.attributes = { "description" => "Jolly fellow", "credit_rating" => "Superb" } - # customer.credit_rating # => nil - # - # customer.credit_rating = "Average" - # customer.credit_rating # => "Average" - # - # To start from an all-closed default and enable attributes as needed, - # have a look at +attr_accessible+. - # - # If the access logic of your application is richer you can use <tt>Hash#except</tt> - # or <tt>Hash#slice</tt> to sanitize the hash of parameters before they are - # passed to Active Record. - # - # For example, it could be the case that the list of protected attributes - # for a given model depends on the role of the user: - # - # # Assumes plan_id is not protected because it depends on the role. - # params[:account] = params[:account].except(:plan_id) unless admin? - # @account.update_attributes(params[:account]) - # - # Note that +attr_protected+ is still applied to the received hash. Thus, - # with this technique you can at most _extend_ the list of protected - # attributes for a particular mass-assignment call. - def attr_protected(*attributes) - write_inheritable_attribute(:attr_protected, Set.new(attributes.map {|a| a.to_s}) + (protected_attributes || [])) + # Attributes listed as readonly can be set for a new record, but will be ignored in database updates afterwards. + def attr_readonly(*attributes) + write_inheritable_attribute(:attr_readonly, Set.new(attributes.map(&:to_s)) + (readonly_attributes || [])) end - # Returns an array of all the attributes that have been protected from mass-assignment. - def protected_attributes # :nodoc: - read_inheritable_attribute(:attr_protected) + # Returns an array of all the attributes that have been specified as readonly. + def readonly_attributes + read_inheritable_attribute(:attr_readonly) || [] end - # Specifies a white list of model attributes that can be set via - # mass-assignment, such as <tt>new(attributes)</tt>, - # <tt>update_attributes(attributes)</tt>, or - # <tt>attributes=(attributes)</tt> - # - # This is the opposite of the +attr_protected+ macro: Mass-assignment - # will only set attributes in this list, to assign to the rest of - # attributes you can use direct writer methods. This is meant to protect - # sensitive attributes from being overwritten by malicious users - # tampering with URLs or forms. If you'd rather start from an all-open - # default and restrict attributes as needed, have a look at - # +attr_protected+. - # - # class Customer < ActiveRecord::Base - # attr_accessible :name, :nickname - # end - # - # customer = Customer.new(:name => "David", :nickname => "Dave", :credit_rating => "Excellent") - # customer.credit_rating # => nil - # customer.attributes = { :name => "Jolly fellow", :credit_rating => "Superb" } - # customer.credit_rating # => nil - # - # customer.credit_rating = "Average" - # customer.credit_rating # => "Average" - # - # If the access logic of your application is richer you can use <tt>Hash#except</tt> - # or <tt>Hash#slice</tt> to sanitize the hash of parameters before they are - # passed to Active Record. - # - # For example, it could be the case that the list of accessible attributes - # for a given model depends on the role of the user: - # - # # Assumes plan_id is accessible because it depends on the role. - # params[:account] = params[:account].except(:plan_id) unless admin? - # @account.update_attributes(params[:account]) - # - # Note that +attr_accessible+ is still applied to the received hash. Thus, - # with this technique you can at most _narrow_ the list of accessible - # attributes for a particular mass-assignment call. - def attr_accessible(*attributes) - write_inheritable_attribute(:attr_accessible, Set.new(attributes.map(&:to_s)) + (accessible_attributes || [])) - end - - # Returns an array of all the attributes that have been made accessible to mass-assignment. - def accessible_attributes # :nodoc: - read_inheritable_attribute(:attr_accessible) - end - - # Attributes listed as readonly can be set for a new record, but will be ignored in database updates afterwards. - def attr_readonly(*attributes) - write_inheritable_attribute(:attr_readonly, Set.new(attributes.map(&:to_s)) + (readonly_attributes || [])) - end - - # Returns an array of all the attributes that have been specified as readonly. - def readonly_attributes - read_inheritable_attribute(:attr_readonly) || [] - end - # If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object, # then specify the name of that attribute using this method and it will be handled automatically. # The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that @@ -869,6 +773,9 @@ module ActiveRecord #:nodoc: # Returns the base AR subclass that this class descends from. If A # extends AR::Base, A.base_class will return A. If B descends from A # through some arbitrarily deep hierarchy, B.base_class will return A. + # + # If B < A and C < B and if A is an abstract_class then both B.base_class + # and C.base_class would return B as the answer since A is an abstract_class. def base_class class_of_active_record_descendant(self) end @@ -876,8 +783,7 @@ module ActiveRecord #:nodoc: # Set this to true if this is an abstract class (see <tt>abstract_class?</tt>). attr_accessor :abstract_class - # Returns whether this class is a base AR class. If A is a base class and - # B descends from A, then B.base_class will return B. + # Returns whether this class is an abstract class or not. def abstract_class? defined?(@abstract_class) && @abstract_class == true end @@ -896,11 +802,6 @@ module ActiveRecord #:nodoc: store_full_sti_class ? name : name.demodulize end - def relation - @relation ||= Relation.new(self, arel_table) - finder_needs_type_condition? ? @relation.where(type_condition) : @relation - end - def arel_table @arel_table ||= Arel::Table.new(table_name, :engine => arel_engine) end @@ -941,6 +842,12 @@ module ActiveRecord #:nodoc: end private + + def relation #:nodoc: + @relation ||= Relation.new(self, arel_table) + finder_needs_type_condition? ? @relation.where(type_condition) : @relation + end + # Finder methods must instantiate through this method to work with the # single-table inheritance model that makes it possible to create # objects of different types from the same table. @@ -1245,11 +1152,6 @@ MSG end end - # Returns the name of the class descending directly from Active Record in the inheritance hierarchy. - def class_name_of_active_record_descendant(klass) #:nodoc: - klass.base_class.name - end - # Accepts an array, hash, or string of SQL conditions and sanitizes # them into a valid SQL fragment for a WHERE clause. # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'" @@ -1569,11 +1471,11 @@ MSG # user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false) # user.is_admin? # => true def attributes=(new_attributes, guard_protected_attributes = true) - return if new_attributes.nil? + return unless new_attributes.is_a? Hash attributes = new_attributes.stringify_keys multi_parameter_attributes = [] - attributes = remove_attributes_protected_from_mass_assignment(attributes) if guard_protected_attributes + attributes = sanitize_for_mass_assignment(attributes) if guard_protected_attributes attributes.each do |k, v| if k.include?("(") @@ -1713,46 +1615,10 @@ MSG end end - def remove_attributes_protected_from_mass_assignment(attributes) - safe_attributes = - if self.class.accessible_attributes.nil? && self.class.protected_attributes.nil? - attributes.reject { |key, value| attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) } - elsif self.class.protected_attributes.nil? - attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.gsub(/\(.+/, "")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) } - elsif self.class.accessible_attributes.nil? - attributes.reject { |key, value| self.class.protected_attributes.include?(key.gsub(/\(.+/,"")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) } - else - raise "Declare either attr_protected or attr_accessible for #{self.class}, but not both." - end - - removed_attributes = attributes.keys - safe_attributes.keys - - if removed_attributes.any? - log_protected_attribute_removal(removed_attributes) - end - - safe_attributes - end - - # Removes attributes which have been marked as readonly. - def remove_readonly_attributes(attributes) - unless self.class.readonly_attributes.nil? - attributes.delete_if { |key, value| self.class.readonly_attributes.include?(key.gsub(/\(.+/,"")) } - else - attributes - end - end - - def log_protected_attribute_removal(*attributes) - if logger - logger.debug "WARNING: Can't mass-assign these protected attributes: #{attributes.join(', ')}" - end - 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 ] - default << 'id' unless self.class.primary_key.eql? 'id' + def self.attributes_protected_by_default + default = [ primary_key, inheritance_column ] + default << 'id' unless primary_key.eql? 'id' default end @@ -1917,6 +1783,7 @@ MSG include AttributeMethods::PrimaryKey include AttributeMethods::TimeZoneConversion include AttributeMethods::Dirty + include ActiveModel::MassAssignmentSecurity include Callbacks, ActiveModel::Observing, Timestamp include Associations, AssociationPreload, NamedScope diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 76b65bf219..ffc3847a31 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -103,10 +103,15 @@ module ActiveRecord # The +options+ hash can include the following keys: # [<tt>:id</tt>] # Whether to automatically add a primary key column. Defaults to true. - # Join tables for +has_and_belongs_to_many+ should set <tt>:id => false</tt>. + # Join tables for +has_and_belongs_to_many+ should set it to false. # [<tt>:primary_key</tt>] # The name of the primary key, if one is to be added automatically. - # Defaults to +id+. + # Defaults to +id+. If <tt>:id</tt> is false this option is ignored. + # + # Also note that this just sets the primary key in the table. You additionally + # need to configure the primary key in the model via the +set_primary_key+ macro. + # Models do NOT auto-detect the primary key from their table definition. + # # [<tt>:options</tt>] # Any extra options you want appended to the table definition. # [<tt>:temporary</tt>] diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 0d9a86a1ea..e5e92f2b1c 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -4,7 +4,17 @@ module ActiveRecord class Base # sqlite3 adapter reuses sqlite_connection. def self.sqlite3_connection(config) # :nodoc: - parse_sqlite_config!(config) + # Require database. + unless config[:database] + raise ArgumentError, "No database file specified. Missing argument: database" + end + + # Allow database path relative to Rails.root, but only if + # the database path is not the special path that tells + # Sqlite to build a database only in memory. + if defined?(Rails.root) && ':memory:' != config[:database] + config[:database] = File.expand_path(config[:database], Rails.root) + end unless 'sqlite3' == config[:adapter] raise ArgumentError, 'adapter name should be "sqlite3"' @@ -16,8 +26,7 @@ module ActiveRecord db = SQLite3::Database.new( config[:database], - :results_as_hash => true, - :type_translation => false + :results_as_hash => true ) db.busy_timeout(config[:timeout]) unless config[:timeout].nil? diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 1927585c49..117cf447df 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -2,25 +2,6 @@ require 'active_record/connection_adapters/abstract_adapter' require 'active_support/core_ext/kernel/requires' module ActiveRecord - class Base - class << self - private - def parse_sqlite_config!(config) - # Require database. - unless config[:database] - raise ArgumentError, "No database file specified. Missing argument: database" - end - - # Allow database path relative to Rails.root, but only if - # the database path is not the special path that tells - # Sqlite to build a database only in memory. - if defined?(Rails.root) && ':memory:' != config[:database] - config[:database] = File.expand_path(config[:database], Rails.root) - end - end - end - end - module ConnectionAdapters #:nodoc: class SQLiteColumn < Column #:nodoc: class << self diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index 999322129a..237cd56683 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -17,9 +17,18 @@ module ActiveRecord def reset_counters(id, *counters) object = find(id) counters.each do |association| - child_class = reflect_on_association(association.to_sym).klass - belongs_name = self.name.demodulize.underscore.to_sym - counter_name = child_class.reflect_on_association(belongs_name).counter_cache_column + has_many_association = reflect_on_association(association.to_sym) + + expected_name = if has_many_association.options[:as] + has_many_association.options[:as].to_s.classify + else + self.name + end + + child_class = has_many_association.klass + belongs_to = child_class.reflect_on_all_associations(:belongs_to) + reflection = belongs_to.find { |e| e.class_name == expected_name } + counter_name = reflection.counter_cache_column self.unscoped.where(arel_table[self.primary_key].eq(object.id)).arel.update({ arel_table[counter_name] => object.send(association).count @@ -103,4 +112,4 @@ module ActiveRecord update_counters(id, counter_name => -1) end end -end
\ No newline at end of file +end diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index c010dac64e..849ec9c884 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -29,7 +29,7 @@ module ActiveRecord if options.present? scoped.apply_finder_options(options) else - current_scoped_methods ? unscoped.merge(current_scoped_methods) : unscoped.clone + current_scoped_methods ? relation.merge(current_scoped_methods) : relation.clone end end diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb index 5f80bd86df..d2ed643f35 100644 --- a/activerecord/lib/active_record/observer.rb +++ b/activerecord/lib/active_record/observer.rb @@ -94,7 +94,7 @@ module ActiveRecord def initialize super - observed_subclasses.each { |klass| add_observer!(klass) } + observed_descendants.each { |klass| add_observer!(klass) } end def self.method_added(method) @@ -108,8 +108,8 @@ module ActiveRecord protected - def observed_subclasses - observed_classes.sum([]) { |klass| klass.send(:descendants) } + def observed_descendants + observed_classes.sum([]) { |klass| klass.descendants } end def observe_callbacks? diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 50166c4385..828a8b41b6 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -102,12 +102,21 @@ module ActiveRecord 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. + # Updates a single attribute and saves the record without going through the normal validation procedure + # or callbacks. This is especially useful for boolean flags on existing records. def update_attribute(name, value) send("#{name}=", value) - save(:validate => false) + hash = { name => read_attribute(name) } + + if record_update_timestamps + timestamp_attributes_for_update_in_model.each do |column| + hash[column] = read_attribute(column) + end + end + + @changed_attributes.delete(name.to_s) + primary_key = self.class.primary_key + self.class.update_all(hash, { primary_key => self[primary_key] }) == 1 end # Updates all the attributes from the passed-in Hash and saves the record. @@ -234,4 +243,4 @@ module ActiveRecord end end end -end
\ No newline at end of file +end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 7882f05d07..5024787c3c 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -1,7 +1,7 @@ namespace :db do task :load_config => :rails_env do require 'active_record' - ActiveRecord::Base.configurations = Rails::Application.config.database_configuration + ActiveRecord::Base.configurations = Rails.application.config.database_configuration end namespace :create do diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index bc708b573f..d9fc1b4940 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -213,8 +213,7 @@ module ActiveRecord if conditions where(conditions).destroy_all else - to_a.each {|object| object.destroy} - reset + to_a.each {|object| object.destroy }.tap { reset } end end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index f39951e16c..3bf4c5bdd1 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -339,7 +339,7 @@ module ActiveRecord end def using_limitable_reflections?(reflections) - reflections.collect(&:collection?).length.zero? + reflections.none?(&:collection?) end end diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index ffd12d2082..1075a60f07 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -35,8 +35,7 @@ module ActiveRecord if attribute write_attribute(attribute, current_time) else - write_attribute('updated_at', current_time) if respond_to?(:updated_at) - write_attribute('updated_on', current_time) if respond_to?(:updated_on) + timestamp_attributes_for_update_in_model.each { |column| write_attribute(column.to_s, current_time) } end save! @@ -50,26 +49,36 @@ module ActiveRecord 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? + timestamp_attributes_for_update_in_model.each do |column| + write_attribute(column.to_s, current_time) if self.send(column).nil? + end end super end def update(*args) #:nodoc: + record_update_timestamps + super + end + + def record_update_timestamps if record_timestamps && (!partial_updates? || changed?) current_time = current_time_from_proper_timezone - - write_attribute('updated_at', current_time) if respond_to?(:updated_at) - write_attribute('updated_on', current_time) if respond_to?(:updated_on) + timestamp_attributes_for_update_in_model.each { |column| write_attribute(column.to_s, current_time) } + true + else + false end + end - super + def timestamp_attributes_for_update_in_model #:nodoc: + [:updated_at, :updated_on].select { |elem| respond_to?(elem) } end - def current_time_from_proper_timezone + def current_time_from_proper_timezone #:nodoc: self.class.default_timezone == :utc ? Time.now.utc : Time.now end end -end
\ No newline at end of file +end + |