aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb52
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb14
-rw-r--r--activerecord/lib/active_record/base.rb4
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb6
4 files changed, 47 insertions, 29 deletions
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index ac65ef94f9..bc7e9d7a94 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -10,6 +10,26 @@ module ActiveRecord
self.serialized_attributes = {}
end
+ class Attribute < Struct.new(:coder, :value, :state)
+ def unserialized_value
+ state == :serialized ? unserialize : value
+ end
+
+ def serialized_value
+ state == :unserialized ? serialize : value
+ end
+
+ def unserialize
+ self.state = :unserialized
+ self.value = coder.load(value)
+ end
+
+ def serialize
+ self.state = :serialized
+ self.value = coder.dump(value)
+ end
+ end
+
module ClassMethods
# 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.
@@ -42,7 +62,7 @@ module ActiveRecord
if serialized_attributes.include?(attr_name)
generated_attribute_methods.module_eval(<<-CODE, __FILE__, __LINE__)
def _#{attr_name}
- @attributes_cache['#{attr_name}'] ||= @attributes['#{attr_name}']
+ @attributes['#{attr_name}'].unserialized_value
end
alias #{attr_name} _#{attr_name}
CODE
@@ -50,31 +70,27 @@ module ActiveRecord
super
end
end
-
- def cacheable_column?(column)
- serialized_attributes.include?(column.name) || super
- end
end
def set_serialized_attributes
- sattrs = self.class.serialized_attributes
-
- sattrs.each do |key, coder|
- @attributes[key] = coder.load @attributes[key] if @attributes.key?(key)
+ self.class.serialized_attributes.each do |key, coder|
+ if @attributes.key?(key)
+ @attributes[key] = Attribute.new(coder, @attributes[key], :serialized)
+ end
end
end
def type_cast_attribute(column)
- coder = self.class.serialized_attributes[column.name]
-
- if column.text? && coder
- unserialized_object = coder.load(@attributes[column.name])
+ if column.text? && self.class.serialized_attributes.include?(column.name)
+ @attributes[column.name].unserialized_value
+ else
+ super
+ end
+ end
- if @attributes.frozen?
- unserialized_object
- else
- @attributes[column.name] = unserialized_object
- end
+ def type_cast_attribute_for_write(column, attr_name, value)
+ if column && coder = self.class.serialized_attributes[column.name]
+ Attribute.new(coder, value, :unserialized)
else
super
end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index b605c09889..650156f3cf 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -28,10 +28,8 @@ module ActiveRecord
@attributes_cache.delete(attr_name)
column = column_for_attribute(attr_name)
- if column && column.number?
- @attributes[attr_name] = convert_number_column_value(value)
- elsif column || @attributes.has_key?(attr_name)
- @attributes[attr_name] = value
+ if column || @attributes.has_key?(attr_name)
+ @attributes[attr_name] = type_cast_attribute_for_write(column, attr_name, value)
else
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
end
@@ -43,6 +41,14 @@ module ActiveRecord
def attribute=(attribute_name, value)
write_attribute(attribute_name, value)
end
+
+ def type_cast_attribute_for_write(column, attr_name, value)
+ if column && column.number?
+ convert_number_column_value(value)
+ else
+ value
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 2c7cb09d7a..ee2833c5dc 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -2003,8 +2003,8 @@ MSG
if include_readonly_attributes || (!include_readonly_attributes && !self.class.readonly_attributes.include?(name))
- value = if coder = klass.serialized_attributes[name]
- coder.dump @attributes[name]
+ value = if klass.serialized_attributes.include?(name)
+ @attributes[name].serialized_value
else
# FIXME: we need @attributes to be used consistently.
# If the values stored in @attributes were already type
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 0327e5aea8..12e5715710 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -686,17 +686,13 @@ class AttributeMethodsTest < ActiveRecord::TestCase
private
def cached_columns
- @cached_columns ||= (time_related_columns_on_topic + serialized_columns_on_topic).map(&:name)
+ @cached_columns ||= time_related_columns_on_topic.map(&:name)
end
def time_related_columns_on_topic
Topic.columns.select { |c| c.type.in?([:time, :date, :datetime, :timestamp]) }
end
- def serialized_columns_on_topic
- Topic.columns.select { |c| Topic.serialized_attributes.include?(c.name) }
- end
-
def in_time_zone(zone)
old_zone = Time.zone
old_tz = ActiveRecord::Base.time_zone_aware_attributes