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,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attribute, '#get' do
4
+ subject { object.get(instance) }
5
+
6
+ let(:object) { described_class.build(String, options.update(:name => name)) }
7
+
8
+ let(:model) { Class.new { attr_accessor :test } }
9
+ let(:name) { :test }
10
+ let(:instance) { model.new }
11
+ let(:value) { 'Jane Doe' }
12
+ let(:options) { {} }
13
+
14
+ context 'with :lazy is set to false' do
15
+ before do
16
+ instance.test = value
17
+ end
18
+
19
+ it { is_expected.to be(value) }
20
+ end
21
+
22
+ context 'with :lazy is set to true' do
23
+ let(:options) { { :lazy => true, :default => value } }
24
+
25
+ it { is_expected.to eql(value) }
26
+
27
+ it 'sets default only on first access' do
28
+ expect(object.get(instance)).to eql(value)
29
+ expect(object.get(instance)).to be(instance.test)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,106 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attribute::Hash, '.build' do
4
+ subject { described_class.build(type, options) }
5
+
6
+ let(:options) { {} }
7
+
8
+ shared_examples_for 'a valid hash attribute instance' do
9
+ it { is_expected.to be_instance_of(Virtus::Attribute::Hash) }
10
+
11
+ it { is_expected.to be_frozen }
12
+ end
13
+
14
+ context 'when type is Hash' do
15
+ let(:type) { Hash }
16
+
17
+ it { is_expected.to be_instance_of(Virtus::Attribute::Hash) }
18
+
19
+ it 'sets default key type' do
20
+ expect(subject.type.key_type).to be(Axiom::Types::Object)
21
+ end
22
+
23
+ it 'sets default value type' do
24
+ expect(subject.type.value_type).to be(Axiom::Types::Object)
25
+ end
26
+ end
27
+
28
+ context 'when type is Hash[String => Integer]' do
29
+ let(:type) { Hash[String => Integer] }
30
+
31
+ it { is_expected.to be_instance_of(Virtus::Attribute::Hash) }
32
+
33
+ it 'sets key type' do
34
+ expect(subject.type.key_type).to be(Axiom::Types::String)
35
+ end
36
+
37
+ it 'sets value type' do
38
+ expect(subject.type.value_type).to be(Axiom::Types::Integer)
39
+ end
40
+ end
41
+
42
+ context 'when type is Hash[Virtus::Attribute::Hash => Virtus::Attribute::Boolean]' do
43
+ let(:type) { Hash[Virtus::Attribute::Hash => Virtus::Attribute::Boolean] }
44
+
45
+ it { is_expected.to be_instance_of(Virtus::Attribute::Hash) }
46
+
47
+ it 'sets key type' do
48
+ expect(subject.type.key_type).to be(Axiom::Types::Hash)
49
+ end
50
+
51
+ it 'sets value type' do
52
+ expect(subject.type.value_type).to be(Axiom::Types::Boolean)
53
+ end
54
+ end
55
+
56
+ context 'when type is Hash[Struct.new(:id) => Integer]' do
57
+ let(:type) { Hash[key_type => Integer] }
58
+ let(:key_type) { Struct.new(:id) }
59
+
60
+ it { is_expected.to be_instance_of(Virtus::Attribute::Hash) }
61
+
62
+ it 'sets key type' do
63
+ expect(subject.type.key_type).to be(key_type)
64
+ end
65
+
66
+ it 'sets value type' do
67
+ expect(subject.type.value_type).to be(Axiom::Types::Integer)
68
+ end
69
+ end
70
+
71
+ context 'when type is Hash[String => Struct.new(:id)]' do
72
+ let(:type) { Hash[String => value_type] }
73
+ let(:value_type) { Struct.new(:id) }
74
+
75
+ it { is_expected.to be_instance_of(Virtus::Attribute::Hash) }
76
+
77
+ it 'sets key type' do
78
+ expect(subject.type.key_type).to be(Axiom::Types::String)
79
+ end
80
+
81
+ it 'sets value type' do
82
+ expect(subject.type.value_type).to be(value_type)
83
+ end
84
+ end
85
+
86
+ context 'when type is Hash[String => Integer, Integer => String]' do
87
+ let(:type) { Hash[String => Integer, :Integer => :String] }
88
+
89
+ specify do
90
+ expect { subject }.to raise_error(
91
+ ArgumentError,
92
+ "more than one [key => value] pair in `#{type}`"
93
+ )
94
+ end
95
+ end
96
+
97
+ context 'when strict mode is used' do
98
+ let(:type) { Hash[String => Integer] }
99
+ let(:options) { { :strict => true } }
100
+
101
+ it 'sets the strict mode for key/value types' do
102
+ expect(subject.key_type).to be_strict
103
+ expect(subject.value_type).to be_strict
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,92 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attribute::Hash, '#coerce' do
4
+ subject { object.coerce(input) }
5
+
6
+ fake(:coercer) { Virtus::Attribute::Coercer }
7
+ fake(:key_type) { Virtus::Attribute }
8
+ fake(:value_type) { Virtus::Attribute }
9
+
10
+ let(:object) {
11
+ described_class.build(Hash[key_primitive => value_primitive], options)
12
+ }
13
+
14
+ let(:options) { {} }
15
+
16
+ context 'when input is coercible to hash' do
17
+ let(:input) { Class.new { def to_hash; { :hello => 'World' }; end }.new }
18
+ let(:object) { described_class.build(Hash) }
19
+
20
+ it { is_expected.to eq(:hello => 'World') }
21
+ end
22
+
23
+ context 'when input is not coercible to hash' do
24
+ let(:input) { 'not really a hash' }
25
+ let(:object) { described_class.build(Hash) }
26
+
27
+ it { is_expected.to be(input) }
28
+ end
29
+
30
+ context 'when input is a hash' do
31
+ context 'when key/value types are primitives' do
32
+ let(:options) {
33
+ { :coercer => coercer, :key_type => key_type, :value_type => value_type }
34
+ }
35
+
36
+ let(:key_primitive) { String }
37
+ let(:value_primitive) { Integer }
38
+
39
+ let(:input) { Hash[1 => '1', 2 => '2'] }
40
+
41
+ it 'uses coercer to coerce key and value' do
42
+ mock(coercer).call(input) { input }
43
+
44
+ mock(key_type).finalize { key_type }
45
+ mock(key_type).coerce(1) { '1' }
46
+ mock(key_type).coerce(2) { '2' }
47
+
48
+ mock(value_type).finalize { value_type }
49
+ mock(value_type).coerce('1') { 1 }
50
+ mock(value_type).coerce('2') { 2 }
51
+
52
+ expect(subject).to eq(Hash['1' => 1, '2' => 2])
53
+
54
+ expect(key_type).to have_received.coerce(1)
55
+ expect(key_type).to have_received.coerce(2)
56
+
57
+ expect(value_type).to have_received.coerce('1')
58
+ expect(value_type).to have_received.coerce('2')
59
+ end
60
+ end
61
+
62
+ context 'when key/value types are EVs' do
63
+ let(:key_primitive) { OpenStruct }
64
+ let(:value_primitive) { Struct.new(:id) }
65
+
66
+ let(:input) { Hash[{:name => 'Test'} => [1]] }
67
+ let(:output) { Hash[key_primitive.new(:name => 'Test') => value_primitive.new(1)] }
68
+
69
+ it 'coerces keys and values' do
70
+ # FIXME: expect(subject).to eq(output) crashes in rspec
71
+ expect(subject.keys.first).to eq(output.keys.first)
72
+ expect(subject.values.first).to eq(output.values.first)
73
+ expect(subject.size).to be(1)
74
+ end
75
+ end
76
+
77
+ context 'when key type is an array and value type is another hash' do
78
+ let(:key_primitive) { Array[String] }
79
+ let(:value_primitive) { Hash[String => Integer] }
80
+
81
+ let(:key_attribute) { Virtus::Attribute.build(key_primitive) }
82
+ let(:value_attribute) { Virtus::Attribute.build(value_primitive) }
83
+
84
+ let(:input) { Hash[[1, 2], {:one => '1', :two => '2'}] }
85
+ let(:output) { Hash[key_attribute.coerce(input.keys.first) => value_attribute.coerce(input.values.first)] }
86
+
87
+ it 'coerces keys and values' do
88
+ expect(subject).to eq(output)
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attribute, '#lazy?' do
4
+ subject { object.lazy? }
5
+
6
+ let(:object) { described_class.build(String, options) }
7
+ let(:options) { Hash[:lazy => lazy] }
8
+
9
+ context 'when :lazy is set to true' do
10
+ let(:lazy) { true }
11
+
12
+ it { is_expected.to be(true) }
13
+ end
14
+
15
+ context 'when :lazy is set to false' do
16
+ let(:lazy) { false }
17
+
18
+ it { is_expected.to be(false) }
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attribute, '#rename' do
4
+ subject { object.rename(:bar) }
5
+
6
+ let(:object) { described_class.build(String, :name => :foo, :strict => true) }
7
+ let(:other) { described_class.build(String, :name => :bar, :strict => true) }
8
+
9
+ describe '#name' do
10
+ subject { super().name }
11
+ it { is_expected.to be(:bar) }
12
+ end
13
+
14
+ it { is_expected.not_to be(object) }
15
+ it { is_expected.to be_strict }
16
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attribute, '#required?' do
4
+ subject { object.required? }
5
+
6
+ let(:object) { described_class.build(String, :required => required) }
7
+
8
+ context 'when required option is true' do
9
+ let(:required) { true }
10
+
11
+ it { is_expected.to be(true) }
12
+ end
13
+
14
+ context 'when required option is false' do
15
+ let(:required) { false }
16
+
17
+ it { is_expected.to be(false) }
18
+ end
19
+ end
@@ -0,0 +1,107 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attribute, '#set_default_value' do
4
+ let(:object) { described_class.build(String, options.merge(:name => name, :default => default)) }
5
+
6
+ let(:model) { Class.new { def name; 'model'; end; attr_reader :test } }
7
+ let(:name) { :test }
8
+ let(:instance) { model.new }
9
+ let(:options) { {} }
10
+
11
+ before { object.set_default_value(instance) }
12
+
13
+ context 'with a nil' do
14
+ subject { instance }
15
+
16
+ let(:default) { nil }
17
+
18
+ describe '#test' do
19
+ subject { super().test }
20
+ it { is_expected.to be(nil) }
21
+ end
22
+
23
+ describe '#instance_variables' do
24
+ subject { super().instance_variables }
25
+ it { is_expected.to include(:'@test') }
26
+ end
27
+ end
28
+
29
+ context 'with a non-clonable object' do
30
+ subject { instance }
31
+
32
+ let(:object) { described_class.build('Boolean', options.merge(:name => name, :default => default)) }
33
+ let(:default) { true }
34
+
35
+ describe '#test' do
36
+ subject { super().test }
37
+ it { is_expected.to be(true) }
38
+ end
39
+
40
+ describe '#instance_variables' do
41
+ subject { super().instance_variables }
42
+ it { is_expected.to include(:'@test') }
43
+ end
44
+ end
45
+
46
+ context 'with a clonable' do
47
+ subject { instance }
48
+
49
+ let(:default) { [] }
50
+
51
+ describe '#test' do
52
+ subject { super().test }
53
+ it { is_expected.to eq(default) }
54
+ end
55
+
56
+ describe '#test' do
57
+ subject { super().test }
58
+ it { is_expected.not_to be(default) }
59
+ end
60
+ end
61
+
62
+ context 'with a callable' do
63
+ subject { instance }
64
+
65
+ let(:default) { lambda { |model, attribute| "#{model.name}-#{attribute.name}" } }
66
+
67
+ describe '#test' do
68
+ subject { super().test }
69
+ it { is_expected.to eq('model-test') }
70
+ end
71
+ end
72
+
73
+ context 'with a symbol' do
74
+ subject { instance }
75
+
76
+ context 'when it is a method name' do
77
+ let(:default) { :set_test }
78
+
79
+ context 'when method is public' do
80
+ let(:model) { Class.new { attr_reader :test; def set_test; @test = 'hello world'; end } }
81
+
82
+ describe '#test' do
83
+ subject { super().test }
84
+ it { is_expected.to eq('hello world') }
85
+ end
86
+ end
87
+
88
+ context 'when method is private' do
89
+ let(:model) { Class.new { attr_reader :test; private; def set_test; @test = 'hello world'; end } }
90
+
91
+ describe '#test' do
92
+ subject { super().test }
93
+ it { is_expected.to eq('hello world') }
94
+ end
95
+ end
96
+ end
97
+
98
+ context 'when it is not a method name' do
99
+ let(:default) { :hello_world }
100
+
101
+ describe '#test' do
102
+ subject { super().test }
103
+ it { is_expected.to eq('hello_world') }
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attribute, '#set' do
4
+ subject { object.set(instance, value) }
5
+
6
+ let(:object) { described_class.build(String, options.merge(:name => name)) }
7
+
8
+ let(:model) { Class.new { attr_reader :test } }
9
+ let(:name) { :test }
10
+ let(:instance) { model.new }
11
+ let(:value) { 'Jane Doe' }
12
+ let(:options) { {} }
13
+
14
+ it { is_expected.to be(value) }
15
+
16
+ context 'without coercion' do
17
+ specify do
18
+ expect { subject }.to change { instance.test }.to(value)
19
+ end
20
+ end
21
+
22
+ context 'with coercion' do
23
+ let(:value) { :'Jane Doe' }
24
+
25
+ specify do
26
+ expect { subject }.to change { instance.test }.to('Jane Doe')
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attribute, '#value_coerced?' do
4
+ subject { object.value_coerced?(input) }
5
+
6
+ let(:object) { described_class.build(String) }
7
+
8
+ context 'when input is coerced' do
9
+ let(:input) { '1' }
10
+
11
+ it { is_expected.to be(true) }
12
+ end
13
+
14
+ context 'when input is not coerced' do
15
+ let(:input) { 1 }
16
+
17
+ it { is_expected.to be(false) }
18
+ end
19
+ end