virtus2 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +39 -0
  3. data/.rspec +2 -0
  4. data/.yardopts +1 -0
  5. data/CONTRIBUTING.md +18 -0
  6. data/Changelog.md +258 -0
  7. data/Gemfile +10 -0
  8. data/Guardfile +19 -0
  9. data/LICENSE +20 -0
  10. data/README.md +630 -0
  11. data/Rakefile +15 -0
  12. data/TODO.md +6 -0
  13. data/lib/virtus/attribute/accessor.rb +103 -0
  14. data/lib/virtus/attribute/boolean.rb +55 -0
  15. data/lib/virtus/attribute/builder.rb +182 -0
  16. data/lib/virtus/attribute/coercer.rb +45 -0
  17. data/lib/virtus/attribute/coercible.rb +20 -0
  18. data/lib/virtus/attribute/collection.rb +103 -0
  19. data/lib/virtus/attribute/default_value/from_callable.rb +35 -0
  20. data/lib/virtus/attribute/default_value/from_clonable.rb +35 -0
  21. data/lib/virtus/attribute/default_value/from_symbol.rb +35 -0
  22. data/lib/virtus/attribute/default_value.rb +51 -0
  23. data/lib/virtus/attribute/embedded_value.rb +67 -0
  24. data/lib/virtus/attribute/enum.rb +45 -0
  25. data/lib/virtus/attribute/hash.rb +130 -0
  26. data/lib/virtus/attribute/lazy_default.rb +18 -0
  27. data/lib/virtus/attribute/nullify_blank.rb +24 -0
  28. data/lib/virtus/attribute/strict.rb +26 -0
  29. data/lib/virtus/attribute.rb +245 -0
  30. data/lib/virtus/attribute_set.rb +240 -0
  31. data/lib/virtus/builder/hook_context.rb +51 -0
  32. data/lib/virtus/builder.rb +133 -0
  33. data/lib/virtus/class_inclusions.rb +48 -0
  34. data/lib/virtus/class_methods.rb +90 -0
  35. data/lib/virtus/coercer.rb +41 -0
  36. data/lib/virtus/configuration.rb +72 -0
  37. data/lib/virtus/const_missing_extensions.rb +18 -0
  38. data/lib/virtus/extensions.rb +105 -0
  39. data/lib/virtus/instance_methods.rb +218 -0
  40. data/lib/virtus/model.rb +68 -0
  41. data/lib/virtus/module_extensions.rb +88 -0
  42. data/lib/virtus/support/equalizer.rb +128 -0
  43. data/lib/virtus/support/options.rb +113 -0
  44. data/lib/virtus/support/type_lookup.rb +109 -0
  45. data/lib/virtus/value_object.rb +150 -0
  46. data/lib/virtus/version.rb +3 -0
  47. data/lib/virtus.rb +310 -0
  48. data/spec/integration/attributes_attribute_spec.rb +28 -0
  49. data/spec/integration/building_module_spec.rb +90 -0
  50. data/spec/integration/collection_member_coercion_spec.rb +96 -0
  51. data/spec/integration/custom_attributes_spec.rb +42 -0
  52. data/spec/integration/custom_collection_attributes_spec.rb +101 -0
  53. data/spec/integration/default_values_spec.rb +87 -0
  54. data/spec/integration/defining_attributes_spec.rb +86 -0
  55. data/spec/integration/embedded_value_spec.rb +50 -0
  56. data/spec/integration/extending_objects_spec.rb +35 -0
  57. data/spec/integration/hash_attributes_coercion_spec.rb +54 -0
  58. data/spec/integration/inheritance_spec.rb +42 -0
  59. data/spec/integration/injectible_coercers_spec.rb +48 -0
  60. data/spec/integration/mass_assignment_with_accessors_spec.rb +44 -0
  61. data/spec/integration/overriding_virtus_spec.rb +46 -0
  62. data/spec/integration/required_attributes_spec.rb +25 -0
  63. data/spec/integration/struct_as_embedded_value_spec.rb +28 -0
  64. data/spec/integration/using_modules_spec.rb +55 -0
  65. data/spec/integration/value_object_with_custom_constructor_spec.rb +42 -0
  66. data/spec/integration/virtus/instance_level_attributes_spec.rb +23 -0
  67. data/spec/integration/virtus/value_object_spec.rb +99 -0
  68. data/spec/shared/constants_helpers.rb +9 -0
  69. data/spec/shared/freeze_method_behavior.rb +40 -0
  70. data/spec/shared/idempotent_method_behaviour.rb +5 -0
  71. data/spec/shared/options_class_method.rb +19 -0
  72. data/spec/spec_helper.rb +41 -0
  73. data/spec/unit/virtus/attribute/boolean/coerce_spec.rb +43 -0
  74. data/spec/unit/virtus/attribute/boolean/value_coerced_predicate_spec.rb +25 -0
  75. data/spec/unit/virtus/attribute/class_methods/build_spec.rb +180 -0
  76. data/spec/unit/virtus/attribute/class_methods/coerce_spec.rb +32 -0
  77. data/spec/unit/virtus/attribute/coerce_spec.rb +129 -0
  78. data/spec/unit/virtus/attribute/coercible_predicate_spec.rb +20 -0
  79. data/spec/unit/virtus/attribute/collection/class_methods/build_spec.rb +105 -0
  80. data/spec/unit/virtus/attribute/collection/coerce_spec.rb +74 -0
  81. data/spec/unit/virtus/attribute/collection/value_coerced_predicate_spec.rb +31 -0
  82. data/spec/unit/virtus/attribute/comparison_spec.rb +20 -0
  83. data/spec/unit/virtus/attribute/custom_collection_spec.rb +29 -0
  84. data/spec/unit/virtus/attribute/defined_spec.rb +20 -0
  85. data/spec/unit/virtus/attribute/embedded_value/class_methods/build_spec.rb +70 -0
  86. data/spec/unit/virtus/attribute/embedded_value/coerce_spec.rb +91 -0
  87. data/spec/unit/virtus/attribute/get_spec.rb +32 -0
  88. data/spec/unit/virtus/attribute/hash/class_methods/build_spec.rb +106 -0
  89. data/spec/unit/virtus/attribute/hash/coerce_spec.rb +92 -0
  90. data/spec/unit/virtus/attribute/lazy_predicate_spec.rb +20 -0
  91. data/spec/unit/virtus/attribute/rename_spec.rb +16 -0
  92. data/spec/unit/virtus/attribute/required_predicate_spec.rb +19 -0
  93. data/spec/unit/virtus/attribute/set_default_value_spec.rb +107 -0
  94. data/spec/unit/virtus/attribute/set_spec.rb +29 -0
  95. data/spec/unit/virtus/attribute/value_coerced_predicate_spec.rb +19 -0
  96. data/spec/unit/virtus/attribute_set/append_spec.rb +47 -0
  97. data/spec/unit/virtus/attribute_set/define_reader_method_spec.rb +36 -0
  98. data/spec/unit/virtus/attribute_set/define_writer_method_spec.rb +36 -0
  99. data/spec/unit/virtus/attribute_set/each_spec.rb +65 -0
  100. data/spec/unit/virtus/attribute_set/element_reference_spec.rb +17 -0
  101. data/spec/unit/virtus/attribute_set/element_set_spec.rb +64 -0
  102. data/spec/unit/virtus/attribute_set/merge_spec.rb +34 -0
  103. data/spec/unit/virtus/attribute_set/reset_spec.rb +71 -0
  104. data/spec/unit/virtus/attribute_spec.rb +229 -0
  105. data/spec/unit/virtus/attributes_reader_spec.rb +41 -0
  106. data/spec/unit/virtus/attributes_writer_spec.rb +51 -0
  107. data/spec/unit/virtus/class_methods/finalize_spec.rb +67 -0
  108. data/spec/unit/virtus/class_methods/new_spec.rb +39 -0
  109. data/spec/unit/virtus/config_spec.rb +13 -0
  110. data/spec/unit/virtus/element_reader_spec.rb +21 -0
  111. data/spec/unit/virtus/element_writer_spec.rb +19 -0
  112. data/spec/unit/virtus/freeze_spec.rb +41 -0
  113. data/spec/unit/virtus/model_spec.rb +197 -0
  114. data/spec/unit/virtus/module_spec.rb +174 -0
  115. data/spec/unit/virtus/set_default_attributes_spec.rb +32 -0
  116. data/spec/unit/virtus/value_object_spec.rb +138 -0
  117. data/virtus2.gemspec +26 -0
  118. metadata +225 -0
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attribute, '#coercible?' do
4
+ subject { object.coercible? }
5
+
6
+ let(:object) { described_class.build(String, options) }
7
+ let(:options) { Hash[:coerce => coerce] }
8
+
9
+ context 'when :coerce is set to true' do
10
+ let(:coerce) { true }
11
+
12
+ it { is_expected.to be(true) }
13
+ end
14
+
15
+ context 'when :coerce is set to false' do
16
+ let(:coerce) { false }
17
+
18
+ it { is_expected.to be(false) }
19
+ end
20
+ end
@@ -0,0 +1,105 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attribute, '.build' do
4
+ subject { described_class.build(type, options) }
5
+
6
+ let(:options) { {} }
7
+
8
+ shared_examples_for 'a valid collection attribute instance' do
9
+ it { is_expected.to be_instance_of(Virtus::Attribute::Collection) }
10
+
11
+ it { is_expected.to be_frozen }
12
+ end
13
+
14
+ context 'when type is Array' do
15
+ let(:type) { Array }
16
+
17
+ it_behaves_like 'a valid collection attribute instance'
18
+
19
+ it 'sets default member type' do
20
+ expect(subject.type.member_type).to be(Axiom::Types::Object)
21
+ end
22
+ end
23
+
24
+ context 'when type is Array[Virtus::Attribute::Boolean]' do
25
+ let(:type) { Array[Virtus::Attribute::Boolean] }
26
+
27
+ it_behaves_like 'a valid collection attribute instance'
28
+
29
+ it 'sets member type' do
30
+ expect(subject.type.member_type).to be(Axiom::Types::Boolean)
31
+ end
32
+ end
33
+
34
+ context 'when type is Array[Float]' do
35
+ let(:type) { Array[Float] }
36
+
37
+ it_behaves_like 'a valid collection attribute instance'
38
+
39
+ it 'sets member type' do
40
+ expect(subject.type.member_type).to be(Axiom::Types::Float)
41
+ end
42
+ end
43
+
44
+ context 'when type is Array[String, Integer]' do
45
+ let(:type) { Array[String, Integer] }
46
+
47
+ specify do
48
+ expect { subject }.to raise_error(
49
+ NotImplementedError,
50
+ "build SumType from list of types (#{type.inspect})"
51
+ )
52
+ end
53
+ end
54
+
55
+ context 'when type is Set' do
56
+ let(:type) { Set }
57
+
58
+ it_behaves_like 'a valid collection attribute instance'
59
+
60
+ it 'sets default member type' do
61
+ expect(subject.type.member_type).to be(Axiom::Types::Object)
62
+ end
63
+ end
64
+
65
+ context 'when type is Set[Float]' do
66
+ let(:type) { Set[Float] }
67
+
68
+ it_behaves_like 'a valid collection attribute instance'
69
+
70
+ it 'sets member type' do
71
+ expect(subject.type.member_type).to be(Axiom::Types::Float)
72
+ end
73
+ end
74
+
75
+ context 'when type is an Enumerable' do
76
+ let(:type) { Class.new { include Enumerable } }
77
+
78
+ it_behaves_like 'a valid collection attribute instance'
79
+ end
80
+
81
+ context 'when type is Array subclass' do
82
+ let(:type) { Class.new(Array) }
83
+
84
+ it_behaves_like 'a valid collection attribute instance'
85
+ end
86
+
87
+ context 'when type is a custom collection instance' do
88
+ let(:type) { Class.new(Array)[String] }
89
+
90
+ it_behaves_like 'a valid collection attribute instance'
91
+
92
+ it 'sets member type' do
93
+ expect(subject.type.member_type).to be(Axiom::Types::String)
94
+ end
95
+ end
96
+
97
+ context 'when strict mode is used' do
98
+ let(:type) { Array[String] }
99
+ let(:options) { { strict: true } }
100
+
101
+ it 'sets strict mode for member type' do
102
+ expect(subject.member_type).to be_strict
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attribute::Collection, '#coerce' do
4
+ subject { object.coerce(input) }
5
+
6
+ context 'when input is an array' do
7
+ context 'when member type is a primitive' do
8
+ fake(:coercer) { Virtus::Attribute::Coercer }
9
+ fake(:member_type) { Virtus::Attribute }
10
+
11
+ let(:member_primitive) { Integer }
12
+ let(:input) { ['1', '2'] }
13
+
14
+ let(:object) {
15
+ described_class.build(Array[member_primitive], :coercer => coercer, :member_type => member_type)
16
+ }
17
+
18
+ it 'uses coercer to coerce members' do
19
+ mock(coercer).call(input) { input }
20
+ mock(member_type).finalize { member_type }
21
+ mock(member_type).coerce('1') { 1 }
22
+ mock(member_type).coerce('2') { 2 }
23
+
24
+ expect(subject).to eq([1, 2])
25
+
26
+ expect(member_type).to have_received.coerce('1')
27
+ expect(member_type).to have_received.coerce('2')
28
+ end
29
+ end
30
+
31
+ context 'when member type is an EV' do
32
+ let(:member_primitive) { Struct.new(:id) }
33
+ let(:input) { [1, 2] }
34
+ let(:object) { described_class.build(Array[member_primitive]) }
35
+
36
+ it 'coerces members' do
37
+ expect(subject).to eq([member_primitive.new(1), member_primitive.new(2)])
38
+ end
39
+ end
40
+
41
+ context 'when member type is a hash with key/value coercion' do
42
+ let(:member_primitive) { Hash[String => Integer] }
43
+ let(:member_attribute) { Virtus::Attribute.build(member_primitive) }
44
+ let(:input) { [{:one => '1'}, {:two => '2'}] }
45
+ let(:output) { [member_attribute.coerce(input.first), member_attribute.coerce(input.last)] }
46
+ let(:object) { described_class.build(Array[member_primitive]) }
47
+
48
+ it 'coerces members' do
49
+ expect(subject).to eq(output)
50
+ end
51
+ end
52
+ end
53
+
54
+ context 'when input is nil' do
55
+ let(:input) { nil }
56
+
57
+ fake(:coercer) { Virtus::Attribute::Coercer }
58
+ fake(:member_type) { Virtus::Attribute }
59
+
60
+ let(:member_primitive) { Integer }
61
+
62
+ let(:object) {
63
+ described_class.build(
64
+ Array[member_primitive], coercer: coercer, member_type: member_type
65
+ )
66
+ }
67
+
68
+ it 'returns nil' do
69
+ mock(coercer).call(input) { input }
70
+
71
+ expect(subject).to be(input)
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+ require 'set'
3
+
4
+ describe Virtus::Attribute::Collection, '#value_coerced?' do
5
+ subject { object.value_coerced?(input) }
6
+
7
+ let(:object) { described_class.build(Array[Integer]) }
8
+
9
+ context 'when input has correctly typed members' do
10
+ let(:input) { [1, 2, 3] }
11
+
12
+ it { is_expected.to be(true) }
13
+ end
14
+
15
+ context 'when input has incorrectly typed members' do
16
+ let(:input) { [1, 2, '3'] }
17
+
18
+ it { is_expected.to be(false) }
19
+ end
20
+
21
+ context 'when the collection type is incorrect' do
22
+ let(:input) { Set[1, 2, 3] }
23
+
24
+ it { is_expected.to be(false) }
25
+ end
26
+
27
+ context 'when the input is empty' do
28
+ let(:input) { [] }
29
+ it { is_expected.to be(true) }
30
+ end
31
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attribute, '#== (defined by including Virtus::Equalizer)' do
4
+ let(:attribute) { described_class.build(String, :name => :name) }
5
+
6
+ it 'returns true when attributes have same type and options' do
7
+ equal_attribute = described_class.build(String, :name => :name)
8
+ expect(attribute == equal_attribute).to be_truthy
9
+ end
10
+
11
+ it 'returns false when attributes have different type' do
12
+ different_attribute = described_class.build(Integer, :name => :name)
13
+ expect(attribute == different_attribute).to be_falsey
14
+ end
15
+
16
+ it 'returns false when attributes have different options' do
17
+ different_attribute = described_class.build(Integer, :name => :name_two)
18
+ expect(attribute == different_attribute).to be_falsey
19
+ end
20
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attribute::Collection, 'custom subclass' do
4
+ subject { attribute_class.build(primitive) }
5
+
6
+ let(:primitive) { Class.new { include Enumerable } }
7
+
8
+ after do
9
+ described_class.descendants.delete(attribute_class)
10
+ end
11
+
12
+ context 'when primitive is set on the attribute subclass' do
13
+ let(:attribute_class) { Class.new(described_class).primitive(primitive) }
14
+
15
+ describe '#primitive' do
16
+ subject { super().primitive }
17
+ it { is_expected.to be(primitive) }
18
+ end
19
+ end
20
+
21
+ context 'when primitive is not set on the attribute subclass' do
22
+ let(:attribute_class) { Class.new(described_class) }
23
+
24
+ describe '#primitive' do
25
+ subject { super().primitive }
26
+ it { is_expected.to be(primitive) }
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attribute, '#defined?' do
4
+ subject { object.defined?(instance) }
5
+
6
+ let(:object) { described_class.build(String, :name => name) }
7
+
8
+ let(:model) { Class.new { attr_accessor :test } }
9
+ let(:name) { :test }
10
+ let(:instance) { model.new }
11
+
12
+ context 'when the attribute value has not been defined' do
13
+ it { is_expected.to be(false) }
14
+ end
15
+
16
+ context 'when the attribute value has been defined' do
17
+ before { instance.test = nil }
18
+ it { is_expected.to be(true) }
19
+ end
20
+ end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attribute::EmbeddedValue, '.build' do
4
+ subject { described_class.build(type) }
5
+
6
+ context 'when type is a Virtus.model' do
7
+ let(:type) { Class.new { include Virtus.model } }
8
+
9
+ it { is_expected.to be_frozen }
10
+
11
+ it { is_expected.to be_instance_of(Virtus::Attribute::EmbeddedValue) }
12
+
13
+ describe '#coercer' do
14
+ subject { super().coercer }
15
+ it { is_expected.to be_instance_of(described_class::FromOpenStruct) }
16
+ end
17
+ end
18
+
19
+ context 'when type includes Virtus' do
20
+ let(:type) { Class.new { include Virtus } }
21
+
22
+ it { is_expected.to be_frozen }
23
+
24
+ it { is_expected.to be_instance_of(Virtus::Attribute::EmbeddedValue) }
25
+
26
+ describe '#coercer' do
27
+ subject { super().coercer }
28
+ it { is_expected.to be_instance_of(described_class::FromOpenStruct) }
29
+ end
30
+ end
31
+
32
+ context 'when type is an OpenStruct subclass' do
33
+ let(:type) { Class.new(OpenStruct) }
34
+
35
+ it { is_expected.to be_frozen }
36
+
37
+ it { is_expected.to be_instance_of(Virtus::Attribute::EmbeddedValue) }
38
+
39
+ describe '#coercer' do
40
+ subject { super().coercer }
41
+ it { is_expected.to be_instance_of(described_class::FromOpenStruct) }
42
+ end
43
+ end
44
+
45
+ context 'when type is OpenStruct' do
46
+ let(:type) { OpenStruct }
47
+
48
+ it { is_expected.to be_frozen }
49
+
50
+ it { is_expected.to be_instance_of(Virtus::Attribute::EmbeddedValue) }
51
+
52
+ describe '#coercer' do
53
+ subject { super().coercer }
54
+ it { is_expected.to be_instance_of(described_class::FromOpenStruct) }
55
+ end
56
+ end
57
+
58
+ context 'when type is Struct' do
59
+ let(:type) { Struct.new(:test) }
60
+
61
+ it { is_expected.to be_frozen }
62
+
63
+ it { is_expected.to be_instance_of(Virtus::Attribute::EmbeddedValue) }
64
+
65
+ describe '#coercer' do
66
+ subject { super().coercer }
67
+ it { is_expected.to be_instance_of(described_class::FromStruct) }
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,91 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attribute::EmbeddedValue, '#coerce' do
4
+ subject { object.coerce(input) }
5
+
6
+ let(:object) { described_class.build(model, options) }
7
+ let(:options) { {} }
8
+
9
+ context 'when primitive is OpenStruct' do
10
+ let(:model) { OpenStruct }
11
+
12
+ context 'when input is an attribute hash' do
13
+ let(:input) { Hash[name: 'Piotr', age: 30] }
14
+
15
+ it { is_expected.to be_instance_of(model) }
16
+
17
+ describe '#name' do
18
+ subject { super().name }
19
+ it { is_expected.to eql('Piotr') }
20
+ end
21
+
22
+ describe '#age' do
23
+ subject { super().age }
24
+ it { is_expected.to eql(30) }
25
+ end
26
+ end
27
+
28
+ context 'when input is nil' do
29
+ let(:input) { nil }
30
+
31
+ it { is_expected.to be(nil) }
32
+ end
33
+
34
+ context 'when input is a model instance' do
35
+ let(:input) { OpenStruct.new }
36
+
37
+ it { is_expected.to be(input) }
38
+ end
39
+ end
40
+
41
+ context 'when primitive is Struct' do
42
+ let(:model) { Struct.new(:name, :age) }
43
+
44
+ context 'when input is an attribute hash' do
45
+ let(:input) { ['Piotr', 30] }
46
+
47
+ it { is_expected.to be_instance_of(model) }
48
+
49
+ describe '#name' do
50
+ subject { super().name }
51
+ it { is_expected.to eql('Piotr') }
52
+ end
53
+
54
+ describe '#age' do
55
+ subject { super().age }
56
+ it { is_expected.to eql(30) }
57
+ end
58
+ end
59
+
60
+ context 'when input is nil' do
61
+ let(:input) { nil }
62
+
63
+ it { is_expected.to be(nil) }
64
+ end
65
+
66
+ context 'when input is a model instance' do
67
+ let(:input) { model.new('Piotr', 30) }
68
+
69
+ it { is_expected.to be(input) }
70
+ end
71
+ end
72
+
73
+ context 'when :strict mode is enabled' do
74
+ let(:model) { Struct.new(:name) }
75
+ let(:options) { { :strict => true } }
76
+
77
+ context 'when input is coercible' do
78
+ let(:input) { ['Piotr'] }
79
+
80
+ it { is_expected.to eql(model.new('Piotr')) }
81
+ end
82
+
83
+ context 'when input is not coercible' do
84
+ let(:input) { nil }
85
+
86
+ it 'raises error' do
87
+ expect { subject }.to raise_error(Virtus::CoercionError)
88
+ end
89
+ end
90
+ end
91
+ end