diff options
Diffstat (limited to 'activerecord/lib/active_record/attribute_methods')
5 files changed, 106 insertions, 67 deletions
diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb index 4365f5a1a1..d057f0941a 100644 --- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb +++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb @@ -43,7 +43,9 @@ module ActiveRecord # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21" # task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21" def read_attribute_before_type_cast(attr_name) - @raw_attributes[attr_name.to_s] + if attr = @attributes[attr_name.to_s] + attr.value_before_type_cast + end end # Returns a hash of attributes before typecasting and deserialization. @@ -57,7 +59,7 @@ module ActiveRecord # task.attributes_before_type_cast # # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil} def attributes_before_type_cast - @raw_attributes + @attributes.each_with_object({}) { |(k, v), h| h[k] = v.value_before_type_cast } end private diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index be438aba95..6a5c057384 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -38,12 +38,39 @@ module ActiveRecord end end - def initialize_dup(other) # :nodoc: - super - init_changed_attributes - end + def initialize_dup(other) # :nodoc: + super + init_changed_attributes + end + + def changed? + super || changed_in_place.any? + end + + def changed + super | changed_in_place + end + + def attribute_changed?(attr_name, options = {}) + result = super + # We can't change "from" something in place. Only setters can define + # "from" and "to" + result ||= changed_in_place?(attr_name) unless options.key?(:from) + result + end + + def changes_applied + super + store_original_raw_attributes + end + + def reset_changes + super + original_raw_attributes.clear + end + + private - private def initialize_internals_callback super init_changed_attributes @@ -65,11 +92,20 @@ module ActiveRecord old_value = old_attribute_value(attr) - result = super(attr, value) + result = super + store_original_raw_attribute(attr) save_changed_attribute(attr, old_value) result end + def raw_write_attribute(attr, value) + attr = attr.to_s + + result = super + original_raw_attributes[attr] = value + result + end + def save_changed_attribute(attr, old_value) if attribute_changed?(attr) changed_attributes.delete(attr) unless _field_changed?(attr, old_value) @@ -105,6 +141,41 @@ module ActiveRecord raw_value = read_attribute_before_type_cast(attr) column_for_attribute(attr).changed?(old_value, new_value, raw_value) end + + def changed_in_place + self.class.attribute_names.select do |attr_name| + changed_in_place?(attr_name) + end + end + + def changed_in_place?(attr_name) + type = type_for_attribute(attr_name) + old_value = original_raw_attribute(attr_name) + value = read_attribute(attr_name) + type.changed_in_place?(old_value, value) + end + + def original_raw_attribute(attr_name) + original_raw_attributes.fetch(attr_name) do + read_attribute_before_type_cast(attr_name) + end + end + + def original_raw_attributes + @original_raw_attributes ||= {} + end + + def store_original_raw_attribute(attr_name) + type = type_for_attribute(attr_name) + value = type.type_cast_for_database(read_attribute(attr_name)) + original_raw_attributes[attr_name] = value + end + + def store_original_raw_attributes + attribute_names.each do |attr| + store_original_raw_attribute(attr) + end + end end end end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 7b7e37884b..8c1cc128f7 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -82,25 +82,16 @@ module ActiveRecord # it has been typecast (for example, "2004-12-12" in a date column is cast # to a date object, like Date.new(2004, 12, 12)). def read_attribute(attr_name) - # If it's cached, just return it - # We use #[] first as a perf optimization for non-nil values. See https://gist.github.com/jonleighton/3552829. name = attr_name.to_s - @attributes[name] || @attributes.fetch(name) { - column = @column_types_override[name] if @column_types_override - column ||= @column_types[name] - - return @raw_attributes.fetch(name) { - if name == 'id' && self.class.primary_key != name - read_attribute(self.class.primary_key) - end - } unless column - - value = @raw_attributes.fetch(name) { - return block_given? ? yield(name) : nil - } - - @attributes[name] = column.type_cast_from_database(value) - } + @attributes.fetch(name) { + if name == 'id' + return read_attribute(self.class.primary_key) + elsif block_given? && self.class.columns_hash.key?(name) + return yield(name) + else + return nil + end + }.value end private diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 60debb7d18..734d94865a 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -3,20 +3,7 @@ module ActiveRecord module Serialization extend ActiveSupport::Concern - included do - # Returns a hash of all the attributes that have been specified for - # serialization as keys and their class restriction as values. - class_attribute :serialized_attributes, instance_accessor: false - self.serialized_attributes = {} - end - module ClassMethods - ## - # :method: serialized_attributes - # - # Returns a hash of all the attributes that have been specified for - # serialization as keys and their class restriction as values. - # 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 @@ -50,8 +37,6 @@ module ActiveRecord # serialize :preferences, Hash # end def serialize(attr_name, class_name_or_coder = Object) - include Behavior - coder = if [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) } class_name_or_coder else @@ -61,25 +46,18 @@ module ActiveRecord decorate_attribute_type(attr_name, :serialize) do |type| Type::Serialized.new(type, coder) end - - # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy - # has its own hash of own serialized attributes - self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder) - end - end - - - # This is only added to the model when serialize is called, which - # ensures we do not make things slower when serialization is not used. - module Behavior # :nodoc: - extend ActiveSupport::Concern - - def should_record_timestamps? - super || (self.record_timestamps && (attributes.keys & self.class.serialized_attributes.keys).present?) end - def keys_for_partial_write - super | (attributes.keys & self.class.serialized_attributes.keys) + def serialized_attributes + ActiveSupport::Deprecation.warn(<<-WARNING.strip_heredoc) + `serialized_attributes` is deprecated without replacement, and will + be removed in Rails 5.0. + WARNING + @serialized_attributes ||= Hash[ + columns.select { |t| t.cast_type.is_a?(Type::Serialized) }.map { |c| + [c.name, c.cast_type.coder] + } + ] end end end diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index b72a6219b0..246a2cd8ba 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -69,22 +69,19 @@ module ActiveRecord def write_attribute_with_type_cast(attr_name, value, should_type_cast) attr_name = attr_name.to_s attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key - @attributes.delete(attr_name) - column = type_for_attribute(attr_name) + type = type_for_attribute(attr_name) unless has_attribute?(attr_name) || self.class.columns_hash.key?(attr_name) raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'" end - # If we're dealing with a binary column, write the data to the cache - # so we don't attempt to typecast multiple times. - if column.binary? - @attributes[attr_name] = value - elsif should_type_cast - @attributes[attr_name] = column.type_cast_from_user(value) + if should_type_cast + @attributes[attr_name] = Attribute.from_user(value, type) + else + @attributes[attr_name] = Attribute.from_database(value, type) end - @raw_attributes[attr_name] = value + value end end end |