virtus2 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
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