aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
authorJon Leighton <j@jonathanleighton.com>2011-11-30 22:31:52 +0000
committerJon Leighton <j@jonathanleighton.com>2011-11-30 23:18:40 +0000
commit7895182d0fce11131024305f53d0cbb32817e65c (patch)
tree5781756f8fdd6cabd552d32631433c656cc51cc9 /activerecord
parent4f20eb5908895ac1f074cc1e2bc60f58e2d50a03 (diff)
downloadrails-7895182d0fce11131024305f53d0cbb32817e65c.tar.gz
rails-7895182d0fce11131024305f53d0cbb32817e65c.tar.bz2
rails-7895182d0fce11131024305f53d0cbb32817e65c.zip
omg computer science!
Implement a mini state machine for serialized attributes. This means we do not have to deserialize the values upon initialization, which means that if we never actually access the attribute, we never have to deserialize it.
Diffstat (limited to 'activerecord')
-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