2017-07-09 13:41:28 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2016-08-06 12:26:20 -04:00
|
|
|
require "cases/helper"
|
2017-11-10 00:25:16 -05:00
|
|
|
require "active_model/attribute_set"
|
2014-06-19 13:13:19 -04:00
|
|
|
|
2017-10-19 12:45:07 -04:00
|
|
|
module ActiveModel
|
2019-08-02 00:25:13 -04:00
|
|
|
class AttributeSetTest < ActiveModel::TestCase
|
2014-06-19 13:13:19 -04:00
|
|
|
test "building a new set from raw attributes" do
|
|
|
|
builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
|
2016-08-06 12:26:20 -04:00
|
|
|
attributes = builder.build_from_database(foo: "1.1", bar: "2.2")
|
2014-06-19 13:13:19 -04:00
|
|
|
|
|
|
|
assert_equal 1, attributes[:foo].value
|
|
|
|
assert_equal 2.2, attributes[:bar].value
|
2014-06-22 18:57:40 -04:00
|
|
|
assert_equal :foo, attributes[:foo].name
|
|
|
|
assert_equal :bar, attributes[:bar].name
|
2014-06-19 13:13:19 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
test "building with custom types" do
|
|
|
|
builder = AttributeSet::Builder.new(foo: Type::Float.new)
|
2017-08-12 07:31:46 -04:00
|
|
|
attributes = builder.build_from_database({ foo: "3.3", bar: "4.4" }, { bar: Type::Integer.new })
|
2014-06-19 13:13:19 -04:00
|
|
|
|
|
|
|
assert_equal 3.3, attributes[:foo].value
|
|
|
|
assert_equal 4, attributes[:bar].value
|
|
|
|
end
|
|
|
|
|
2014-06-20 13:36:23 -04:00
|
|
|
test "[] returns a null object" do
|
|
|
|
builder = AttributeSet::Builder.new(foo: Type::Float.new)
|
2016-08-06 12:26:20 -04:00
|
|
|
attributes = builder.build_from_database(foo: "3.3")
|
2014-06-20 13:36:23 -04:00
|
|
|
|
2016-08-06 12:26:20 -04:00
|
|
|
assert_equal "3.3", attributes[:foo].value_before_type_cast
|
2016-12-24 12:29:52 -05:00
|
|
|
assert_nil attributes[:bar].value_before_type_cast
|
2014-06-22 18:57:40 -04:00
|
|
|
assert_equal :bar, attributes[:bar].name
|
2014-06-20 13:36:23 -04:00
|
|
|
end
|
|
|
|
|
2015-09-28 16:17:08 -04:00
|
|
|
test "duping creates a new hash, but does not dup the attributes" do
|
2014-06-19 13:13:19 -04:00
|
|
|
builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::String.new)
|
2016-08-06 12:26:20 -04:00
|
|
|
attributes = builder.build_from_database(foo: 1, bar: "foo")
|
2014-06-19 13:13:19 -04:00
|
|
|
|
|
|
|
# Ensure the type cast value is cached
|
|
|
|
attributes[:foo].value
|
|
|
|
attributes[:bar].value
|
|
|
|
|
|
|
|
duped = attributes.dup
|
2014-06-22 18:25:40 -04:00
|
|
|
duped.write_from_database(:foo, 2)
|
2016-08-06 12:26:20 -04:00
|
|
|
duped[:bar].value << "bar"
|
2015-09-28 16:17:08 -04:00
|
|
|
|
|
|
|
assert_equal 1, attributes[:foo].value
|
|
|
|
assert_equal 2, duped[:foo].value
|
2016-08-06 12:26:20 -04:00
|
|
|
assert_equal "foobar", attributes[:bar].value
|
|
|
|
assert_equal "foobar", duped[:bar].value
|
2015-09-28 16:17:08 -04:00
|
|
|
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)
|
2016-08-06 12:26:20 -04:00
|
|
|
attributes = builder.build_from_database(foo: 1, bar: "foo")
|
2015-09-28 16:17:08 -04:00
|
|
|
|
|
|
|
# Ensure the type cast value is cached
|
|
|
|
attributes[:foo].value
|
|
|
|
attributes[:bar].value
|
|
|
|
|
|
|
|
duped = attributes.deep_dup
|
|
|
|
duped.write_from_database(:foo, 2)
|
2016-08-06 12:26:20 -04:00
|
|
|
duped[:bar].value << "bar"
|
2014-06-19 13:13:19 -04:00
|
|
|
|
|
|
|
assert_equal 1, attributes[:foo].value
|
|
|
|
assert_equal 2, duped[:foo].value
|
2016-08-06 12:26:20 -04:00
|
|
|
assert_equal "foo", attributes[:bar].value
|
|
|
|
assert_equal "foobar", duped[:bar].value
|
2014-06-19 13:13:19 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
test "freezing cloned set does not freeze original" do
|
|
|
|
attributes = AttributeSet.new({})
|
|
|
|
clone = attributes.clone
|
|
|
|
|
|
|
|
clone.freeze
|
|
|
|
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate clone, :frozen?
|
|
|
|
assert_not_predicate attributes, :frozen?
|
2014-06-19 13:13:19 -04:00
|
|
|
end
|
2014-06-21 15:09:48 -04:00
|
|
|
|
|
|
|
test "to_hash returns a hash of the type cast values" do
|
|
|
|
builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
|
2016-08-06 12:26:20 -04:00
|
|
|
attributes = builder.build_from_database(foo: "1.1", bar: "2.2")
|
2014-06-21 15:09:48 -04:00
|
|
|
|
|
|
|
assert_equal({ foo: 1, bar: 2.2 }, attributes.to_hash)
|
|
|
|
assert_equal({ foo: 1, bar: 2.2 }, attributes.to_h)
|
|
|
|
end
|
2014-06-26 05:26:26 -04:00
|
|
|
|
2015-02-10 10:20:54 -05:00
|
|
|
test "to_hash maintains order" do
|
|
|
|
builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
|
2016-08-06 12:26:20 -04:00
|
|
|
attributes = builder.build_from_database(foo: "2.2", bar: "3.3")
|
2015-02-10 10:20:54 -05:00
|
|
|
|
|
|
|
attributes[:bar]
|
|
|
|
hash = attributes.to_h
|
|
|
|
|
|
|
|
assert_equal [[:foo, 2], [:bar, 3.3]], hash.to_a
|
|
|
|
end
|
|
|
|
|
2014-06-21 12:52:19 -04:00
|
|
|
test "values_before_type_cast" do
|
|
|
|
builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Integer.new)
|
2016-08-06 12:26:20 -04:00
|
|
|
attributes = builder.build_from_database(foo: "1.1", bar: "2.2")
|
2014-06-21 12:52:19 -04:00
|
|
|
|
2016-08-06 12:26:20 -04:00
|
|
|
assert_equal({ foo: "1.1", bar: "2.2" }, attributes.values_before_type_cast)
|
2014-06-21 12:52:19 -04:00
|
|
|
end
|
2014-06-26 05:40:08 -04:00
|
|
|
|
2014-06-22 17:38:55 -04:00
|
|
|
test "known columns are built with uninitialized attributes" do
|
|
|
|
attributes = attributes_with_uninitialized_key
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate attributes[:foo], :initialized?
|
|
|
|
assert_not_predicate attributes[:bar], :initialized?
|
2014-06-22 17:38:55 -04:00
|
|
|
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
|
|
|
|
|
2014-07-11 09:50:16 -04:00
|
|
|
test "uninitialized attributes return false for key?" do
|
2014-06-22 17:38:55 -04:00
|
|
|
attributes = attributes_with_uninitialized_key
|
2014-07-11 09:50:16 -04:00
|
|
|
assert attributes.key?(:foo)
|
|
|
|
assert_not attributes.key?(:bar)
|
2014-06-22 17:38:55 -04:00
|
|
|
end
|
|
|
|
|
2014-07-11 09:50:16 -04:00
|
|
|
test "unknown attributes return false for key?" do
|
2014-06-22 17:38:55 -04:00
|
|
|
attributes = attributes_with_uninitialized_key
|
2014-07-11 09:50:16 -04:00
|
|
|
assert_not attributes.key?(:wibble)
|
2014-06-22 17:38:55 -04:00
|
|
|
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)
|
2016-08-06 12:26:20 -04:00
|
|
|
attributes = builder.build_from_database(foo: "1.1", bar: "2.2")
|
2014-06-22 17:38:55 -04:00
|
|
|
|
|
|
|
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
|
2014-11-14 13:58:35 -05:00
|
|
|
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" }
|
2014-06-22 17:38:55 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
test "fetch_value uses the given block for uninitialized attributes" do
|
|
|
|
attributes = attributes_with_uninitialized_key
|
2016-08-06 12:26:20 -04:00
|
|
|
value = attributes.fetch_value(:bar) { |n| n.to_s + "!" }
|
|
|
|
assert_equal "bar!", value
|
2014-06-22 17:38:55 -04:00
|
|
|
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
|
|
|
|
|
2014-11-14 13:20:28 -05:00
|
|
|
test "the primary_key is always initialized" do
|
2017-11-27 16:06:51 -05:00
|
|
|
defaults = { foo: Attribute.from_user(:foo, nil, nil) }
|
|
|
|
builder = AttributeSet::Builder.new({ foo: Type::Integer.new }, defaults)
|
2014-11-14 13:20:28 -05:00
|
|
|
attributes = builder.build_from_database
|
|
|
|
|
|
|
|
assert attributes.key?(:foo)
|
|
|
|
assert_equal [:foo], attributes.keys
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate attributes[:foo], :initialized?
|
2014-11-14 13:20:28 -05:00
|
|
|
end
|
|
|
|
|
2014-06-22 18:25:40 -04:00
|
|
|
class MyType
|
2015-02-17 15:39:42 -05:00
|
|
|
def cast(value)
|
2014-06-22 18:25:40 -04:00
|
|
|
return if value.nil?
|
|
|
|
value + " from user"
|
|
|
|
end
|
|
|
|
|
2015-02-17 13:29:51 -05:00
|
|
|
def deserialize(value)
|
2014-06-22 18:25:40 -04:00
|
|
|
return if value.nil?
|
|
|
|
value + " from database"
|
|
|
|
end
|
2015-09-24 13:50:11 -04:00
|
|
|
|
|
|
|
def assert_valid_value(*)
|
|
|
|
end
|
2014-06-22 18:25:40 -04:00
|
|
|
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
|
|
|
|
|
2021-06-06 23:49:43 -04:00
|
|
|
class MySerializedType < ::ActiveModel::Type::Value
|
|
|
|
def serialize(value)
|
|
|
|
value + " serialized"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
test "values_for_database" do
|
|
|
|
builder = AttributeSet::Builder.new(foo: MySerializedType.new)
|
|
|
|
attributes = builder.build_from_database
|
|
|
|
|
|
|
|
attributes.write_from_user(:foo, "value")
|
|
|
|
|
|
|
|
assert_equal({ foo: "value serialized" }, attributes.values_for_database)
|
|
|
|
end
|
|
|
|
|
2014-12-08 14:45:21 -05:00
|
|
|
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
|
2015-01-20 16:09:53 -05:00
|
|
|
|
2018-10-02 04:09:40 -04:00
|
|
|
test "marshalling dump/load legacy materialized attribute hash" do
|
2018-02-01 17:52:33 -05:00
|
|
|
builder = AttributeSet::Builder.new(foo: Type::String.new)
|
2020-06-14 19:18:33 -04:00
|
|
|
|
|
|
|
def builder.build_from_database(values = {}, additional_types = {})
|
|
|
|
attributes = LazyAttributeHash.new(types, values, additional_types, default_attributes)
|
|
|
|
AttributeSet.new(attributes)
|
|
|
|
end
|
|
|
|
|
2018-02-01 17:52:33 -05:00
|
|
|
attributes = builder.build_from_database(foo: "1")
|
|
|
|
|
|
|
|
attributes.instance_variable_get(:@attributes).instance_eval do
|
|
|
|
class << self
|
|
|
|
def marshal_dump
|
2020-05-02 02:33:43 -04:00
|
|
|
materialize # legacy marshal format before Rails 5.1
|
2018-02-01 17:52:33 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-05-02 02:33:43 -04:00
|
|
|
data = Marshal.dump(attributes)
|
|
|
|
attributes = assert_deprecated { Marshal.load(data) }
|
2018-02-01 17:52:33 -05:00
|
|
|
assert_equal({ foo: "1" }, attributes.to_hash)
|
|
|
|
end
|
|
|
|
|
2015-01-20 16:09:53 -05:00
|
|
|
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
|
2015-09-24 13:50:11 -04:00
|
|
|
|
|
|
|
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
|
2015-10-06 10:51:14 -04:00
|
|
|
|
|
|
|
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
|
2018-01-29 20:15:01 -05:00
|
|
|
|
|
|
|
private
|
|
|
|
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
|
2014-06-19 13:13:19 -04:00
|
|
|
end
|
|
|
|
end
|