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,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus, '#attributes' do
4
+
5
+ shared_examples_for 'attribute hash' do
6
+ it 'includes all attributes' do
7
+ subject.attributes = { :test => 'Hello World', :test_priv => 'Yo' }
8
+
9
+ expect(subject.attributes).to eql(:test => 'Hello World')
10
+ end
11
+ end
12
+
13
+ context 'with a class' do
14
+ let(:model) {
15
+ Class.new {
16
+ include Virtus
17
+
18
+ attribute :test, String
19
+ attribute :test_priv, String, :reader => :private
20
+ }
21
+ }
22
+
23
+ it_behaves_like 'attribute hash' do
24
+ subject { model.new }
25
+ end
26
+ end
27
+
28
+ context 'with an instance' do
29
+ subject { model.new }
30
+
31
+ let(:model) { Class.new }
32
+
33
+ before do
34
+ subject.extend(Virtus)
35
+ subject.attribute :test, String
36
+ subject.attribute :test_priv, String, :reader => :private
37
+ end
38
+
39
+ it_behaves_like 'attribute hash'
40
+ end
41
+ end
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus, '#attributes=' do
4
+
5
+ shared_examples_for 'mass-assignment' do
6
+ it 'allows writing known attributes' do
7
+ subject.attributes = { :test => 'Hello World' }
8
+
9
+ expect(subject.test).to eql('Hello World')
10
+ end
11
+
12
+ it 'skips writing unknown attributes' do
13
+ subject.attributes = { :test => 'Hello World', :nothere => 'boom!' }
14
+
15
+ expect(subject.test).to eql('Hello World')
16
+ end
17
+ end
18
+
19
+ context 'with a class' do
20
+ let(:model) {
21
+ Class.new {
22
+ include Virtus
23
+
24
+ attribute :test, String
25
+ }
26
+ }
27
+
28
+ it_behaves_like 'mass-assignment' do
29
+ subject { model.new }
30
+ end
31
+
32
+ it 'raises when attributes is not hash-like object' do
33
+ expect { model.new('not a hash, really') }.to raise_error(
34
+ NoMethodError, 'Expected "not a hash, really" to respond to #to_hash'
35
+ )
36
+ end
37
+ end
38
+
39
+ context 'with an instance' do
40
+ subject { model.new }
41
+
42
+ let(:model) { Class.new }
43
+
44
+ before do
45
+ subject.extend(Virtus)
46
+ subject.attribute :test, String
47
+ end
48
+
49
+ it_behaves_like 'mass-assignment'
50
+ end
51
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus, '.finalize' do
4
+ before do
5
+ module Examples
6
+ class Person
7
+ include Virtus.model(:finalize => false)
8
+
9
+ attribute :name, String
10
+ attribute :articles, Array['Examples::Article']
11
+ attribute :address, :'Examples::Address'
12
+ end
13
+
14
+ class Article
15
+ include Virtus.model(:finalize => false)
16
+
17
+ attribute :posts, Hash['Examples::Person' => 'Examples::Post']
18
+ attribute :person, :'Examples::Person'
19
+ end
20
+
21
+ class Post
22
+ include Virtus.model
23
+
24
+ attribute :title, String
25
+ end
26
+
27
+ class Address
28
+ include Virtus.model
29
+
30
+ attribute :street, String
31
+ end
32
+ end
33
+
34
+ expect(Examples::Post.attribute_set[:title]).to be_finalized
35
+ expect(Examples::Address.attribute_set[:street]).to be_finalized
36
+
37
+ expect(Virtus::Builder.pending).not_to include(Examples::Post)
38
+ expect(Virtus::Builder.pending).not_to include(Examples::Address)
39
+
40
+ Virtus.finalize
41
+ end
42
+
43
+ it "sets attributes that don't require finalization" do
44
+ expect(Examples::Person.attribute_set[:name]).to be_instance_of(Virtus::Attribute)
45
+ expect(Examples::Person.attribute_set[:name].primitive).to be(String)
46
+ end
47
+
48
+ it 'it finalizes member type for a collection attribute' do
49
+ expect(Examples::Person.attribute_set[:address].primitive).to be(Examples::Address)
50
+ end
51
+
52
+ it 'it finalizes key type for a hash attribute' do
53
+ expect(Examples::Article.attribute_set[:posts].key_type.primitive).to be(Examples::Person)
54
+ end
55
+
56
+ it 'it finalizes value type for a hash attribute' do
57
+ expect(Examples::Article.attribute_set[:posts].value_type.primitive).to be(Examples::Post)
58
+ end
59
+
60
+ it 'it finalizes type for an EV attribute' do
61
+ expect(Examples::Article.attribute_set[:person].type.primitive).to be(Examples::Person)
62
+ end
63
+
64
+ it 'automatically resolves constant when it is already available' do
65
+ expect(Examples::Article.attribute_set[:person].type.primitive).to be(Examples::Person)
66
+ end
67
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus, '.new' do
4
+ let(:model) {
5
+ Class.new {
6
+ include Virtus
7
+
8
+ attribute :id, Integer
9
+ attribute :name, String, :default => 'John Doe'
10
+ attribute :email, String, :default => 'john@doe.com', :lazy => true, :writer => :private
11
+ }
12
+ }
13
+
14
+ context 'without attribute hash' do
15
+ subject { model.new }
16
+
17
+ it 'sets default values for non-lazy attributes' do
18
+ expect(subject.instance_variable_get('@name')).to eql('John Doe')
19
+ end
20
+
21
+ it 'skips setting default values for lazy attributes' do
22
+ expect(subject.instance_variable_get('@email')).to be(nil)
23
+ end
24
+ end
25
+
26
+ context 'with attribute hash' do
27
+ subject { model.new(:id => 1, :name => 'Jane Doe') }
28
+
29
+ it 'sets attributes with public writers' do
30
+ expect(subject.id).to be(1)
31
+ expect(subject.name).to eql('Jane Doe')
32
+ end
33
+
34
+ it 'skips setting attributes with private writers' do
35
+ expect(subject.instance_variable_get('@email')).to be(nil)
36
+ end
37
+ end
38
+
39
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus, '.config' do
4
+ it 'provides global configuration' do
5
+ Virtus.config { |config| config.coerce = false }
6
+
7
+ expect(Virtus.coerce).to be(false)
8
+
9
+ Virtus.config { |config| config.coerce = true }
10
+
11
+ expect(Virtus.coerce).to be(true)
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus, '#[]' do
4
+ subject { object[:test] }
5
+
6
+ let(:model) {
7
+ Class.new {
8
+ include Virtus
9
+
10
+ attribute :test, String
11
+ }
12
+ }
13
+
14
+ let(:object) { model.new }
15
+
16
+ before do
17
+ object.test = 'foo'
18
+ end
19
+
20
+ it { is_expected.to eq('foo') }
21
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus, '#[]=' do
4
+ subject { object[:test] = 'foo' }
5
+
6
+ let(:model) {
7
+ Class.new {
8
+ include Virtus
9
+
10
+ attribute :test, String
11
+ }
12
+ }
13
+
14
+ let(:object) { model.new }
15
+
16
+ specify do
17
+ expect { subject }.to change { object.test }.from(nil).to('foo')
18
+ end
19
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus, '#freeze' do
4
+ subject { object.freeze }
5
+
6
+ let(:model) {
7
+ Class.new {
8
+ include Virtus
9
+
10
+ attribute :name, String, :default => 'foo', :lazy => true
11
+ attribute :age, Integer, :default => 30
12
+ attribute :rand, Float, :default => Proc.new { rand }
13
+ }
14
+ }
15
+
16
+ let(:object) { model.new }
17
+
18
+ it { is_expected.to be_frozen }
19
+
20
+ describe '#name' do
21
+ subject { super().name }
22
+ it { is_expected.to eql('foo') }
23
+ end
24
+
25
+ describe '#age' do
26
+ subject { super().age }
27
+ it { is_expected.to be(30) }
28
+ end
29
+
30
+ it "does not change dynamic default values" do
31
+ original_value = object.rand
32
+ object.freeze
33
+ expect(object.rand).to eq original_value
34
+ end
35
+
36
+ it "does not change default attributes that have been explicitly set" do
37
+ object.rand = 3.14
38
+ object.freeze
39
+ expect(object.rand).to eq 3.14
40
+ end
41
+ end
@@ -0,0 +1,197 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus, '.model' do
4
+ shared_examples_for 'a model with constructor' do
5
+ it 'accepts attribute hash' do
6
+ instance = subject.new(:name => 'Jane')
7
+ expect(instance.name).to eql('Jane')
8
+ end
9
+ end
10
+
11
+ shared_examples_for 'a model with mass-assignment' do
12
+ let(:attributes) do
13
+ { :name => 'Jane', :something => nil }
14
+ end
15
+
16
+ before do
17
+ instance.attributes = attributes
18
+ end
19
+
20
+ it 'accepts attribute hash' do
21
+ expect(instance.attributes).to eql(attributes)
22
+ end
23
+ end
24
+
25
+ shared_examples_for 'a model with strict mode turned off' do
26
+ it 'has attributes with strict set to false' do
27
+ expect(subject.send(:attribute_set)[:name]).to_not be_strict
28
+ end
29
+ end
30
+
31
+ context 'with default configuration' do
32
+ let(:mod) { Virtus.model }
33
+
34
+ context 'with a class' do
35
+ let(:model) { Class.new }
36
+
37
+ subject { model }
38
+
39
+ before do
40
+ subject.send(:include, mod)
41
+ subject.attribute :name, String, :default => 'Jane'
42
+ subject.attribute :something
43
+ end
44
+
45
+ it_behaves_like 'a model with constructor'
46
+
47
+ it_behaves_like 'a model with strict mode turned off'
48
+
49
+ it_behaves_like 'a model with mass-assignment' do
50
+ let(:instance) { subject.new }
51
+ end
52
+
53
+ it 'defaults to Object for attribute type' do
54
+ expect(model.attribute_set[:something].type).to be(Axiom::Types::Object)
55
+ end
56
+
57
+ context 'with a sub-class' do
58
+ subject { Class.new(model) }
59
+
60
+ before do
61
+ subject.attribute :age, Integer
62
+ end
63
+
64
+ it_behaves_like 'a model with constructor'
65
+
66
+ it_behaves_like 'a model with strict mode turned off'
67
+
68
+ it_behaves_like 'a model with mass-assignment' do
69
+ let(:instance) { subject.new }
70
+
71
+ let(:attributes) {
72
+ { :name => 'Jane', :something => nil, :age => 23 }
73
+ }
74
+ end
75
+
76
+ it 'has its own attributes' do
77
+ expect(subject.attribute_set[:age]).to be_kind_of(Virtus::Attribute)
78
+ end
79
+ end
80
+ end
81
+
82
+ context 'with an instance' do
83
+ subject { Class.new.new }
84
+
85
+ before do
86
+ subject.extend(mod)
87
+ subject.attribute :name, String
88
+ subject.attribute :something
89
+ end
90
+
91
+ it_behaves_like 'a model with strict mode turned off'
92
+
93
+ it_behaves_like 'a model with mass-assignment' do
94
+ let(:instance) { subject }
95
+ end
96
+ end
97
+ end
98
+
99
+ context 'when constructor is disabled' do
100
+ subject { Class.new.send(:include, mod) }
101
+
102
+ let(:mod) { Virtus.model(:constructor => false) }
103
+
104
+ it 'does not accept attribute hash in the constructor' do
105
+ expect { subject.new({}) }.to raise_error(ArgumentError)
106
+ end
107
+ end
108
+
109
+ context 'when strict mode is enabled' do
110
+ let(:mod) { Virtus.model(:strict => true) }
111
+ let(:model) { Class.new }
112
+
113
+ context 'with a class' do
114
+ subject { model.new }
115
+
116
+ before do
117
+ model.send(:include, mod)
118
+ model.attribute :name, String
119
+ end
120
+
121
+ it 'has attributes with strict set to true' do
122
+ expect(model.attribute_set[:name]).to be_strict
123
+ end
124
+ end
125
+
126
+ context 'with an instance' do
127
+ subject { model.new }
128
+
129
+ before do
130
+ subject.extend(mod)
131
+ subject.attribute :name, String
132
+ end
133
+
134
+ it 'has attributes with strict set to true' do
135
+ expect(subject.send(:attribute_set)[:name]).to be_strict
136
+ end
137
+ end
138
+ end
139
+
140
+ context 'when mass-assignment is disabled' do
141
+ let(:mod) { Virtus.model(:mass_assignment => false) }
142
+ let(:model) { Class.new }
143
+
144
+ context 'with a class' do
145
+ subject { model.new }
146
+
147
+ before do
148
+ model.send(:include, mod)
149
+ end
150
+
151
+ it { is_expected.not_to respond_to(:attributes) }
152
+ it { is_expected.not_to respond_to(:attributes=) }
153
+ end
154
+
155
+ context 'with an instance' do
156
+ subject { model.new }
157
+
158
+ before do
159
+ subject.extend(mod)
160
+ end
161
+
162
+ it { is_expected.not_to respond_to(:attributes) }
163
+ it { is_expected.not_to respond_to(:attributes=) }
164
+ end
165
+ end
166
+
167
+ context 'when :required is set' do
168
+ let(:mod) { Virtus.model(:required => false) }
169
+ let(:model) { Class.new }
170
+
171
+ context 'with a class' do
172
+ subject { model.new }
173
+
174
+ before do
175
+ model.send(:include, mod)
176
+ model.attribute :name, String
177
+ end
178
+
179
+ it 'has attributes with :required option inherited from module' do
180
+ expect(model.attribute_set[:name]).to_not be_required
181
+ end
182
+ end
183
+
184
+ context 'with an instance' do
185
+ subject { model.new }
186
+
187
+ before do
188
+ subject.extend(mod)
189
+ subject.attribute :name, String
190
+ end
191
+
192
+ it 'has attributes with strict set to true' do
193
+ expect(subject.send(:attribute_set)[:name]).not_to be_required
194
+ end
195
+ end
196
+ end
197
+ end