aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/README.rdoc6
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb4
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb5
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb51
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb4
-rw-r--r--activerecord/lib/active_record/core.rb2
-rw-r--r--activerecord/lib/active_record/properties.rb10
-rw-r--r--activerecord/lib/active_record/type/serialized.rb35
-rw-r--r--activerecord/lib/active_record/type/value.rb1
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb7
-rw-r--r--activerecord/test/cases/adapters/postgresql/composite_test.rb1
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb2
-rw-r--r--activerecord/test/cases/custom_properties_test.rb20
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb11
-rw-r--r--activerecord/test/cases/store_test.rb14
-rw-r--r--activerecord/test/cases/yaml_serialization_test.rb19
18 files changed, 74 insertions, 132 deletions
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
index c813b22f3b..969e97913d 100644
--- a/activerecord/README.rdoc
+++ b/activerecord/README.rdoc
@@ -19,9 +19,9 @@ A short rundown of some of the major features:
class Product < ActiveRecord::Base
end
-
+
{Learn more}[link:classes/ActiveRecord/Base.html]
-
+
The Product class is automatically mapped to the table named "products",
which might look like this:
@@ -33,7 +33,7 @@ which might look like this:
This would also define the following accessors: `Product#name` and
`Product#name=(new_name)`.
-
+
* Associations between objects defined by simple class methods.
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index 816fb51942..c4cf084a04 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -13,9 +13,9 @@ module ActiveRecord
# exception is raised.
#
# cat = Cat.new(name: "Gorby", status: "yawning")
- # cat.attributes # => { "name" => "Gorby", "status" => "yawning" }
+ # cat.attributes # => { "name" => "Gorby", "status" => "yawning", "created_at" => nil, "updated_at" => nil}
# cat.assign_attributes(status: "sleeping")
- # cat.attributes # => { "name" => "Gorby", "status" => "sleeping" }
+ # cat.attributes # => { "name" => "Gorby", "status" => "sleeping", "created_at" => nil, "updated_at" => nil }
#
# New attributes will be persisted in the database when the object is saved.
#
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index fb6473b7c7..b6520b9b3d 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -287,11 +287,6 @@ module ActiveRecord
}
end
- # Placeholder so it can be overriden when needed by serialization
- def attributes_for_coder # :nodoc:
- attributes
- end
-
# Returns an <tt>#inspect</tt>-like string for the value of the
# attribute +attr_name+. String attributes are truncated upto 50
# characters, Date and Time attributes are returned in the
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 99b95b1fc4..ae3785638a 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -94,7 +94,7 @@ module ActiveRecord
def cacheable_column?(column)
if attribute_types_cached_by_default == ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
- ! serialized_attributes.include? column.name
+ true
else
attribute_types_cached_by_default.include?(column.type)
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index b247bf8a45..425c33f2c6 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -76,21 +76,6 @@ module ActiveRecord
module Behavior # :nodoc:
extend ActiveSupport::Concern
- module ClassMethods # :nodoc:
- def initialize_attributes(attributes, options = {})
- serialized = (options.delete(:serialized) { true }) ? :serialized : :unserialized
- super(attributes, options)
-
- serialized_attributes.each do |key, coder|
- if attributes.key?(key)
- attributes[key] = Type::Serialized::Attribute.new(coder, attributes[key], serialized)
- end
- end
-
- attributes
- end
- end
-
def should_record_timestamps?
super || (self.record_timestamps && (attributes.keys & self.class.serialized_attributes.keys).present?)
end
@@ -106,42 +91,6 @@ module ActiveRecord
super
end
end
-
- def read_attribute_before_type_cast(attr_name)
- if self.class.serialized_attributes.include?(attr_name)
- super.unserialized_value
- else
- super
- end
- end
-
- def attributes_before_type_cast
- super.dup.tap do |attributes|
- self.class.serialized_attributes.each_key do |key|
- if attributes.key?(key)
- attributes[key] = attributes[key].unserialized_value
- end
- end
- end
- end
-
- def typecasted_attribute_value(name)
- if self.class.serialized_attributes.include?(name)
- @raw_attributes[name].serialized_value
- else
- super
- end
- end
-
- def attributes_for_coder
- attribute_names.each_with_object({}) do |name, attrs|
- attrs[name] = if self.class.serialized_attributes.include?(name)
- @raw_attributes[name].serialized_value
- else
- read_attribute(name)
- end
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index 3b9b9c81e8..c3e601a208 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -53,11 +53,11 @@ module ActiveRecord
# specified +value+. Empty strings for fixnum and float columns are
# turned into +nil+.
def write_attribute(attr_name, value)
- write_attribute_with_type_cast(attr_name, value, :type_cast_for_write)
+ write_attribute_with_type_cast(attr_name, value, true)
end
def raw_write_attribute(attr_name, value)
- write_attribute_with_type_cast(attr_name, value, :raw_type_cast_for_write)
+ write_attribute_with_type_cast(attr_name, value, false)
end
private
@@ -66,7 +66,7 @@ module ActiveRecord
write_attribute(attribute_name, value)
end
- def write_attribute_with_type_cast(attr_name, value, type_cast_method)
+ 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)
@@ -78,9 +78,9 @@ module ActiveRecord
@attributes[attr_name] = value
end
- if column
- @raw_attributes[attr_name] = column.public_send(type_cast_method, value)
- elsif @raw_attributes.has_key?(attr_name)
+ if column && should_type_cast
+ @raw_attributes[attr_name] = column.type_cast_for_write(value)
+ elsif !should_type_cast || @raw_attributes.has_key?(attr_name)
@raw_attributes[attr_name] = value
else
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 4fbc55a07e..60da541e3d 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -17,7 +17,7 @@ module ActiveRecord
delegate :type, :precision, :scale, :limit, :klass, :accessor,
:text?, :number?, :binary?, :serialized?,
- :type_cast, :type_cast_for_write, :raw_type_cast_for_write, :type_cast_for_database,
+ :type_cast, :type_cast_for_write, :type_cast_for_database,
:type_cast_for_schema,
to: :cast_type
@@ -52,7 +52,7 @@ module ActiveRecord
end
def extract_default(default)
- type_cast(default)
+ type_cast_for_write(type_cast(default))
end
end
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index e99e235fe8..205cae9b2a 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -353,7 +353,7 @@ module ActiveRecord
# Post.new.encode_with(coder)
# coder # => {"attributes" => {"id" => nil, ... }}
def encode_with(coder)
- coder['attributes'] = attributes_for_coder
+ coder['attributes'] = @raw_attributes
end
# Returns true if +comparison_object+ is the same exact object, or +comparison_object+
diff --git a/activerecord/lib/active_record/properties.rb b/activerecord/lib/active_record/properties.rb
index 21ff906fec..e70f84808d 100644
--- a/activerecord/lib/active_record/properties.rb
+++ b/activerecord/lib/active_record/properties.rb
@@ -75,7 +75,7 @@ module ActiveRecord
# store_listing.price_in_cents # => 1000
def property(name, cast_type, options = {})
name = name.to_s
- clear_properties_cache
+ clear_caches_calculated_from_columns
# Assign a new hash to ensure that subclasses do not share a hash
self.user_provided_columns = user_provided_columns.merge(name => connection.new_column(name, options[:default], cast_type))
end
@@ -92,7 +92,7 @@ module ActiveRecord
def reset_column_information # :nodoc:
super
- clear_properties_cache
+ clear_caches_calculated_from_columns
end
private
@@ -108,9 +108,13 @@ module ActiveRecord
existing_columns + new_columns
end
- def clear_properties_cache
+ def clear_caches_calculated_from_columns
@columns = nil
@columns_hash = nil
+ @column_types = nil
+ @column_defaults = nil
+ @column_names = nil
+ @content_columns = nil
end
end
end
diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb
index 4052ac0fa0..eac31f6cc3 100644
--- a/activerecord/lib/active_record/type/serialized.rb
+++ b/activerecord/lib/active_record/type/serialized.rb
@@ -10,20 +10,21 @@ module ActiveRecord
end
def type_cast(value)
- if value.respond_to?(:unserialized_value)
- value.unserialized_value(super(value.value))
+ if is_default_value?(value)
+ value
else
- super
+ coder.load(super)
end
end
def type_cast_for_write(value)
- Attribute.new(coder, value, :unserialized)
+ return if value.nil?
+ unless is_default_value?(value)
+ coder.dump(value)
+ end
end
- def raw_type_cast_for_write(value)
- Attribute.new(coder, value, :serialized)
- end
+ alias type_cast_for_database type_cast_for_write
def serialized?
true
@@ -33,24 +34,10 @@ module ActiveRecord
ActiveRecord::Store::IndifferentHashAccessor
end
- class Attribute < Struct.new(:coder, :value, :state) # :nodoc:
- def unserialized_value(v = value)
- state == :serialized ? unserialize(v) : value
- end
-
- def serialized_value
- state == :unserialized ? serialize : value
- end
-
- def unserialize(v)
- self.state = :unserialized
- self.value = coder.load(v)
- end
+ private
- def serialize
- self.state = :serialized
- self.value = coder.dump(value)
- end
+ def is_default_value?(value)
+ value == coder.load(nil)
end
end
end
diff --git a/activerecord/lib/active_record/type/value.rb b/activerecord/lib/active_record/type/value.rb
index 9c1e9dc01e..8cd99df2b4 100644
--- a/activerecord/lib/active_record/type/value.rb
+++ b/activerecord/lib/active_record/type/value.rb
@@ -54,7 +54,6 @@ module ActiveRecord
def type_cast_for_write(value) # :nodoc:
value
end
- alias_method :raw_type_cast_for_write, :type_cast_for_write # :internal:
private
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index b6fccc9b94..2e7b1d7206 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -14,7 +14,6 @@ module ActiveRecord
finder_class = find_finder_class_for(record)
table = finder_class.arel_table
value = map_enum_attribute(finder_class, attribute, value)
- value = deserialize_attribute(record, attribute, value)
relation = build_relation(finder_class, table, attribute, value)
relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.id)) if record.persisted?
@@ -86,12 +85,6 @@ module ActiveRecord
relation
end
- def deserialize_attribute(record, attribute, value)
- coder = record.class.serialized_attributes[attribute.to_s]
- value = coder.dump value if value && coder
- value
- end
-
def map_enum_attribute(klass, attribute, value)
mapping = klass.defined_enums[attribute.to_s]
value = mapping[value] if value && mapping
diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb
index ecccbf10e6..d804d1fa97 100644
--- a/activerecord/test/cases/adapters/postgresql/composite_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb
@@ -93,6 +93,7 @@ class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::TestCase
end
def type_cast_for_write(value)
+ return if value.nil?
"(#{value.city},#{value.street})"
end
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index da2876170e..139fe9c04b 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -870,7 +870,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
def cached_columns
- Topic.columns.map(&:name) - Topic.serialized_attributes.keys
+ Topic.columns.map(&:name)
end
def time_related_columns_on_topic
diff --git a/activerecord/test/cases/custom_properties_test.rb b/activerecord/test/cases/custom_properties_test.rb
index a406704114..9ba1e83df6 100644
--- a/activerecord/test/cases/custom_properties_test.rb
+++ b/activerecord/test/cases/custom_properties_test.rb
@@ -87,5 +87,25 @@ module ActiveRecord
column_names = OverloadedType.column_names
assert_equal %w(id overloaded_float unoverloaded_float overloaded_string_with_limit string_with_default non_existent_decimal), column_names
end
+
+ def test_caches_are_cleared
+ klass = Class.new(OverloadedType)
+
+ assert_equal 6, klass.columns.length
+ assert_not klass.columns_hash.key?('wibble')
+ assert_equal 6, klass.column_types.length
+ assert_equal 6, klass.column_defaults.length
+ assert_not klass.column_names.include?('wibble')
+ assert_equal 5, klass.content_columns.length
+
+ klass.property :wibble, Type::Value.new
+
+ assert_equal 7, klass.columns.length
+ assert klass.columns_hash.key?('wibble')
+ assert_equal 7, klass.column_types.length
+ assert_equal 7, klass.column_defaults.length
+ assert klass.column_names.include?('wibble')
+ assert_equal 6, klass.content_columns.length
+ end
end
end
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index eaa28148f4..5ea62c9f59 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -59,8 +59,9 @@ class SerializedAttributeTest < ActiveRecord::TestCase
def test_serialized_attribute_calling_dup_method
Topic.serialize :content, JSON
- t = Topic.new(:content => { :foo => :bar }).dup
- assert_equal({ :foo => :bar }, t.content_before_type_cast)
+ orig = Topic.new(content: { foo: :bar })
+ clone = orig.dup
+ assert_equal(orig.content, clone.content)
end
def test_serialized_attribute_declared_in_subclass
@@ -103,8 +104,10 @@ class SerializedAttributeTest < ActiveRecord::TestCase
def test_serialized_attribute_should_raise_exception_on_save_with_wrong_type
Topic.serialize(:content, Hash)
- topic = Topic.new(:content => "string")
- assert_raise(ActiveRecord::SerializationTypeMismatch) { topic.save }
+ assert_raise(ActiveRecord::SerializationTypeMismatch) do
+ topic = Topic.new(content: 'string')
+ topic.save
+ end
end
def test_should_raise_exception_on_serialized_attribute_with_type_mismatch
diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb
index 6a34c55011..f841b1c983 100644
--- a/activerecord/test/cases/store_test.rb
+++ b/activerecord/test/cases/store_test.rb
@@ -183,20 +183,6 @@ class StoreTest < ActiveRecord::TestCase
assert_equal({}, @john.params)
end
- test "attributes_for_coder should return stored fields already serialized" do
- attributes = {
- "id" => @john.id,
- "name"=> @john.name,
- "settings" => "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\ncolor: black\n",
- "preferences" => "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nremember_login: true\n",
- "json_data" => "{\"height\":\"tall\"}", "json_data_empty"=>"{\"is_a_good_guy\":true}",
- "params" => "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess {}\n",
- "account_id"=> @john.account_id
- }
-
- assert_equal attributes, @john.attributes_for_coder
- end
-
test "dump, load and dump again a model" do
dumped = YAML.dump(@john)
loaded = YAML.load(dumped)
diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb
index 15815d56e4..f7af9a35cd 100644
--- a/activerecord/test/cases/yaml_serialization_test.rb
+++ b/activerecord/test/cases/yaml_serialization_test.rb
@@ -23,13 +23,6 @@ class YamlSerializationTest < ActiveRecord::TestCase
assert_equal({:omg=>:lol}, YAML.load(YAML.dump(topic)).content)
end
- def test_encode_with_coder
- topic = Topic.first
- coder = {}
- topic.encode_with coder
- assert_equal({'attributes' => topic.attributes}, coder)
- end
-
def test_psych_roundtrip
topic = Topic.first
assert topic
@@ -47,4 +40,16 @@ class YamlSerializationTest < ActiveRecord::TestCase
def test_active_record_relation_serialization
[Topic.all].to_yaml
end
+
+ def test_raw_types_are_not_changed_on_round_trip
+ topic = Topic.new(parent_id: "123")
+ assert_equal "123", topic.parent_id_before_type_cast
+ assert_equal "123", YAML.load(YAML.dump(topic)).parent_id_before_type_cast
+ end
+
+ def test_cast_types_are_not_changed_on_round_trip
+ topic = Topic.new(parent_id: "123")
+ assert_equal 123, topic.parent_id
+ assert_equal 123, YAML.load(YAML.dump(topic)).parent_id
+ end
end