aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel/test/cases/attribute_set_test.rb
diff options
context:
space:
mode:
authorLisa Ugray <lisa.ugray@shopify.com>2017-10-19 12:45:07 -0400
committerLisa Ugray <lisa.ugray@shopify.com>2017-11-09 14:29:39 -0500
commitc3675f50d2e59b7fc173d7b332860c4b1a24a726 (patch)
tree736119c8ea9b683ac465c07e6a640d7e14bbc1b0 /activemodel/test/cases/attribute_set_test.rb
parentdac7c8844b4d9944eaa0fca98b45ee478cdb7201 (diff)
downloadrails-c3675f50d2e59b7fc173d7b332860c4b1a24a726.tar.gz
rails-c3675f50d2e59b7fc173d7b332860c4b1a24a726.tar.bz2
rails-c3675f50d2e59b7fc173d7b332860c4b1a24a726.zip
Move Attribute and AttributeSet to ActiveModel
Use these to back the attributes API. Stop automatically including ActiveModel::Dirty in ActiveModel::Attributes, and make it optional.
Diffstat (limited to 'activemodel/test/cases/attribute_set_test.rb')
-rw-r--r--activemodel/test/cases/attribute_set_test.rb255
1 files changed, 255 insertions, 0 deletions
diff --git a/activemodel/test/cases/attribute_set_test.rb b/activemodel/test/cases/attribute_set_test.rb
new file mode 100644
index 0000000000..d50e6cfa7a
--- /dev/null
+++ b/activemodel/test/cases/attribute_set_test.rb
@@ -0,0 +1,255 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+
+module ActiveModel
+ class AttributeSetTest < ActiveModel::TestCase
+ test "building a new set from raw attributes" do
+ builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
+ attributes = builder.build_from_database(foo: "1.1", bar: "2.2")
+
+ assert_equal 1, attributes[:foo].value
+ assert_equal 2.2, attributes[:bar].value
+ assert_equal :foo, attributes[:foo].name
+ assert_equal :bar, attributes[:bar].name
+ end
+
+ test "building with custom types" do
+ builder = AttributeSet::Builder.new(foo: Type::Float.new)
+ attributes = builder.build_from_database({ foo: "3.3", bar: "4.4" }, { bar: Type::Integer.new })
+
+ assert_equal 3.3, attributes[:foo].value
+ assert_equal 4, attributes[:bar].value
+ end
+
+ test "[] returns a null object" do
+ builder = AttributeSet::Builder.new(foo: Type::Float.new)
+ attributes = builder.build_from_database(foo: "3.3")
+
+ assert_equal "3.3", attributes[:foo].value_before_type_cast
+ assert_nil attributes[:bar].value_before_type_cast
+ assert_equal :bar, attributes[:bar].name
+ end
+
+ test "duping creates a new hash, but does not dup the attributes" do
+ builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::String.new)
+ attributes = builder.build_from_database(foo: 1, bar: "foo")
+
+ # Ensure the type cast value is cached
+ attributes[:foo].value
+ attributes[:bar].value
+
+ duped = attributes.dup
+ duped.write_from_database(:foo, 2)
+ duped[:bar].value << "bar"
+
+ assert_equal 1, attributes[:foo].value
+ assert_equal 2, duped[:foo].value
+ assert_equal "foobar", attributes[:bar].value
+ assert_equal "foobar", duped[:bar].value
+ end
+
+ test "deep_duping creates a new hash and dups each attribute" do
+ builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::String.new)
+ attributes = builder.build_from_database(foo: 1, bar: "foo")
+
+ # Ensure the type cast value is cached
+ attributes[:foo].value
+ attributes[:bar].value
+
+ duped = attributes.deep_dup
+ duped.write_from_database(:foo, 2)
+ duped[:bar].value << "bar"
+
+ assert_equal 1, attributes[:foo].value
+ assert_equal 2, duped[:foo].value
+ assert_equal "foo", attributes[:bar].value
+ assert_equal "foobar", duped[:bar].value
+ end
+
+ test "freezing cloned set does not freeze original" do
+ attributes = AttributeSet.new({})
+ clone = attributes.clone
+
+ clone.freeze
+
+ assert clone.frozen?
+ assert_not attributes.frozen?
+ end
+
+ test "to_hash returns a hash of the type cast values" do
+ builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
+ attributes = builder.build_from_database(foo: "1.1", bar: "2.2")
+
+ assert_equal({ foo: 1, bar: 2.2 }, attributes.to_hash)
+ assert_equal({ foo: 1, bar: 2.2 }, attributes.to_h)
+ end
+
+ test "to_hash maintains order" do
+ builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
+ attributes = builder.build_from_database(foo: "2.2", bar: "3.3")
+
+ attributes[:bar]
+ hash = attributes.to_h
+
+ assert_equal [[:foo, 2], [:bar, 3.3]], hash.to_a
+ end
+
+ test "values_before_type_cast" do
+ builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Integer.new)
+ attributes = builder.build_from_database(foo: "1.1", bar: "2.2")
+
+ assert_equal({ foo: "1.1", bar: "2.2" }, attributes.values_before_type_cast)
+ end
+
+ test "known columns are built with uninitialized attributes" do
+ attributes = attributes_with_uninitialized_key
+ assert attributes[:foo].initialized?
+ assert_not attributes[:bar].initialized?
+ end
+
+ test "uninitialized attributes are not included in the attributes hash" do
+ attributes = attributes_with_uninitialized_key
+ assert_equal({ foo: 1 }, attributes.to_hash)
+ end
+
+ test "uninitialized attributes are not included in keys" do
+ attributes = attributes_with_uninitialized_key
+ assert_equal [:foo], attributes.keys
+ end
+
+ test "uninitialized attributes return false for key?" do
+ attributes = attributes_with_uninitialized_key
+ assert attributes.key?(:foo)
+ assert_not attributes.key?(:bar)
+ end
+
+ test "unknown attributes return false for key?" do
+ attributes = attributes_with_uninitialized_key
+ assert_not attributes.key?(:wibble)
+ end
+
+ test "fetch_value returns the value for the given initialized attribute" do
+ builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
+ attributes = builder.build_from_database(foo: "1.1", bar: "2.2")
+
+ assert_equal 1, attributes.fetch_value(:foo)
+ assert_equal 2.2, attributes.fetch_value(:bar)
+ end
+
+ test "fetch_value returns nil for unknown attributes" do
+ attributes = attributes_with_uninitialized_key
+ assert_nil attributes.fetch_value(:wibble) { "hello" }
+ end
+
+ test "fetch_value returns nil for unknown attributes when types has a default" do
+ types = Hash.new(Type::Value.new)
+ builder = AttributeSet::Builder.new(types)
+ attributes = builder.build_from_database
+
+ assert_nil attributes.fetch_value(:wibble) { "hello" }
+ end
+
+ test "fetch_value uses the given block for uninitialized attributes" do
+ attributes = attributes_with_uninitialized_key
+ value = attributes.fetch_value(:bar) { |n| n.to_s + "!" }
+ assert_equal "bar!", value
+ end
+
+ test "fetch_value returns nil for uninitialized attributes if no block is given" do
+ attributes = attributes_with_uninitialized_key
+ assert_nil attributes.fetch_value(:bar)
+ end
+
+ test "the primary_key is always initialized" do
+ builder = AttributeSet::Builder.new({ foo: Type::Integer.new }, :foo)
+ attributes = builder.build_from_database
+
+ assert attributes.key?(:foo)
+ assert_equal [:foo], attributes.keys
+ assert attributes[:foo].initialized?
+ end
+
+ class MyType
+ def cast(value)
+ return if value.nil?
+ value + " from user"
+ end
+
+ def deserialize(value)
+ return if value.nil?
+ value + " from database"
+ end
+
+ def assert_valid_value(*)
+ end
+ end
+
+ test "write_from_database sets the attribute with database typecasting" do
+ builder = AttributeSet::Builder.new(foo: MyType.new)
+ attributes = builder.build_from_database
+
+ assert_nil attributes.fetch_value(:foo)
+
+ attributes.write_from_database(:foo, "value")
+
+ assert_equal "value from database", attributes.fetch_value(:foo)
+ end
+
+ test "write_from_user sets the attribute with user typecasting" do
+ builder = AttributeSet::Builder.new(foo: MyType.new)
+ attributes = builder.build_from_database
+
+ assert_nil attributes.fetch_value(:foo)
+
+ attributes.write_from_user(:foo, "value")
+
+ assert_equal "value from user", attributes.fetch_value(:foo)
+ end
+
+ def attributes_with_uninitialized_key
+ builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
+ builder.build_from_database(foo: "1.1")
+ end
+
+ test "freezing doesn't prevent the set from materializing" do
+ builder = AttributeSet::Builder.new(foo: Type::String.new)
+ attributes = builder.build_from_database(foo: "1")
+
+ attributes.freeze
+ assert_equal({ foo: "1" }, attributes.to_hash)
+ end
+
+ test "#accessed_attributes returns only attributes which have been read" do
+ builder = AttributeSet::Builder.new(foo: Type::Value.new, bar: Type::Value.new)
+ attributes = builder.build_from_database(foo: "1", bar: "2")
+
+ assert_equal [], attributes.accessed
+
+ attributes.fetch_value(:foo)
+
+ assert_equal [:foo], attributes.accessed
+ end
+
+ test "#map returns a new attribute set with the changes applied" do
+ builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Integer.new)
+ attributes = builder.build_from_database(foo: "1", bar: "2")
+ new_attributes = attributes.map do |attr|
+ attr.with_cast_value(attr.value + 1)
+ end
+
+ assert_equal 2, new_attributes.fetch_value(:foo)
+ assert_equal 3, new_attributes.fetch_value(:bar)
+ end
+
+ test "comparison for equality is correctly implemented" do
+ builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Integer.new)
+ attributes = builder.build_from_database(foo: "1", bar: "2")
+ attributes2 = builder.build_from_database(foo: "1", bar: "2")
+ attributes3 = builder.build_from_database(foo: "2", bar: "2")
+
+ assert_equal attributes, attributes2
+ assert_not_equal attributes2, attributes3
+ end
+ end
+end