virtus2 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +39 -0
- data/.rspec +2 -0
- data/.yardopts +1 -0
- data/CONTRIBUTING.md +18 -0
- data/Changelog.md +258 -0
- data/Gemfile +10 -0
- data/Guardfile +19 -0
- data/LICENSE +20 -0
- data/README.md +630 -0
- data/Rakefile +15 -0
- data/TODO.md +6 -0
- data/lib/virtus/attribute/accessor.rb +103 -0
- data/lib/virtus/attribute/boolean.rb +55 -0
- data/lib/virtus/attribute/builder.rb +182 -0
- data/lib/virtus/attribute/coercer.rb +45 -0
- data/lib/virtus/attribute/coercible.rb +20 -0
- data/lib/virtus/attribute/collection.rb +103 -0
- data/lib/virtus/attribute/default_value/from_callable.rb +35 -0
- data/lib/virtus/attribute/default_value/from_clonable.rb +35 -0
- data/lib/virtus/attribute/default_value/from_symbol.rb +35 -0
- data/lib/virtus/attribute/default_value.rb +51 -0
- data/lib/virtus/attribute/embedded_value.rb +67 -0
- data/lib/virtus/attribute/enum.rb +45 -0
- data/lib/virtus/attribute/hash.rb +130 -0
- data/lib/virtus/attribute/lazy_default.rb +18 -0
- data/lib/virtus/attribute/nullify_blank.rb +24 -0
- data/lib/virtus/attribute/strict.rb +26 -0
- data/lib/virtus/attribute.rb +245 -0
- data/lib/virtus/attribute_set.rb +240 -0
- data/lib/virtus/builder/hook_context.rb +51 -0
- data/lib/virtus/builder.rb +133 -0
- data/lib/virtus/class_inclusions.rb +48 -0
- data/lib/virtus/class_methods.rb +90 -0
- data/lib/virtus/coercer.rb +41 -0
- data/lib/virtus/configuration.rb +72 -0
- data/lib/virtus/const_missing_extensions.rb +18 -0
- data/lib/virtus/extensions.rb +105 -0
- data/lib/virtus/instance_methods.rb +218 -0
- data/lib/virtus/model.rb +68 -0
- data/lib/virtus/module_extensions.rb +88 -0
- data/lib/virtus/support/equalizer.rb +128 -0
- data/lib/virtus/support/options.rb +113 -0
- data/lib/virtus/support/type_lookup.rb +109 -0
- data/lib/virtus/value_object.rb +150 -0
- data/lib/virtus/version.rb +3 -0
- data/lib/virtus.rb +310 -0
- data/spec/integration/attributes_attribute_spec.rb +28 -0
- data/spec/integration/building_module_spec.rb +90 -0
- data/spec/integration/collection_member_coercion_spec.rb +96 -0
- data/spec/integration/custom_attributes_spec.rb +42 -0
- data/spec/integration/custom_collection_attributes_spec.rb +101 -0
- data/spec/integration/default_values_spec.rb +87 -0
- data/spec/integration/defining_attributes_spec.rb +86 -0
- data/spec/integration/embedded_value_spec.rb +50 -0
- data/spec/integration/extending_objects_spec.rb +35 -0
- data/spec/integration/hash_attributes_coercion_spec.rb +54 -0
- data/spec/integration/inheritance_spec.rb +42 -0
- data/spec/integration/injectible_coercers_spec.rb +48 -0
- data/spec/integration/mass_assignment_with_accessors_spec.rb +44 -0
- data/spec/integration/overriding_virtus_spec.rb +46 -0
- data/spec/integration/required_attributes_spec.rb +25 -0
- data/spec/integration/struct_as_embedded_value_spec.rb +28 -0
- data/spec/integration/using_modules_spec.rb +55 -0
- data/spec/integration/value_object_with_custom_constructor_spec.rb +42 -0
- data/spec/integration/virtus/instance_level_attributes_spec.rb +23 -0
- data/spec/integration/virtus/value_object_spec.rb +99 -0
- data/spec/shared/constants_helpers.rb +9 -0
- data/spec/shared/freeze_method_behavior.rb +40 -0
- data/spec/shared/idempotent_method_behaviour.rb +5 -0
- data/spec/shared/options_class_method.rb +19 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/unit/virtus/attribute/boolean/coerce_spec.rb +43 -0
- data/spec/unit/virtus/attribute/boolean/value_coerced_predicate_spec.rb +25 -0
- data/spec/unit/virtus/attribute/class_methods/build_spec.rb +180 -0
- data/spec/unit/virtus/attribute/class_methods/coerce_spec.rb +32 -0
- data/spec/unit/virtus/attribute/coerce_spec.rb +129 -0
- data/spec/unit/virtus/attribute/coercible_predicate_spec.rb +20 -0
- data/spec/unit/virtus/attribute/collection/class_methods/build_spec.rb +105 -0
- data/spec/unit/virtus/attribute/collection/coerce_spec.rb +74 -0
- data/spec/unit/virtus/attribute/collection/value_coerced_predicate_spec.rb +31 -0
- data/spec/unit/virtus/attribute/comparison_spec.rb +20 -0
- data/spec/unit/virtus/attribute/custom_collection_spec.rb +29 -0
- data/spec/unit/virtus/attribute/defined_spec.rb +20 -0
- data/spec/unit/virtus/attribute/embedded_value/class_methods/build_spec.rb +70 -0
- data/spec/unit/virtus/attribute/embedded_value/coerce_spec.rb +91 -0
- data/spec/unit/virtus/attribute/get_spec.rb +32 -0
- data/spec/unit/virtus/attribute/hash/class_methods/build_spec.rb +106 -0
- data/spec/unit/virtus/attribute/hash/coerce_spec.rb +92 -0
- data/spec/unit/virtus/attribute/lazy_predicate_spec.rb +20 -0
- data/spec/unit/virtus/attribute/rename_spec.rb +16 -0
- data/spec/unit/virtus/attribute/required_predicate_spec.rb +19 -0
- data/spec/unit/virtus/attribute/set_default_value_spec.rb +107 -0
- data/spec/unit/virtus/attribute/set_spec.rb +29 -0
- data/spec/unit/virtus/attribute/value_coerced_predicate_spec.rb +19 -0
- data/spec/unit/virtus/attribute_set/append_spec.rb +47 -0
- data/spec/unit/virtus/attribute_set/define_reader_method_spec.rb +36 -0
- data/spec/unit/virtus/attribute_set/define_writer_method_spec.rb +36 -0
- data/spec/unit/virtus/attribute_set/each_spec.rb +65 -0
- data/spec/unit/virtus/attribute_set/element_reference_spec.rb +17 -0
- data/spec/unit/virtus/attribute_set/element_set_spec.rb +64 -0
- data/spec/unit/virtus/attribute_set/merge_spec.rb +34 -0
- data/spec/unit/virtus/attribute_set/reset_spec.rb +71 -0
- data/spec/unit/virtus/attribute_spec.rb +229 -0
- data/spec/unit/virtus/attributes_reader_spec.rb +41 -0
- data/spec/unit/virtus/attributes_writer_spec.rb +51 -0
- data/spec/unit/virtus/class_methods/finalize_spec.rb +67 -0
- data/spec/unit/virtus/class_methods/new_spec.rb +39 -0
- data/spec/unit/virtus/config_spec.rb +13 -0
- data/spec/unit/virtus/element_reader_spec.rb +21 -0
- data/spec/unit/virtus/element_writer_spec.rb +19 -0
- data/spec/unit/virtus/freeze_spec.rb +41 -0
- data/spec/unit/virtus/model_spec.rb +197 -0
- data/spec/unit/virtus/module_spec.rb +174 -0
- data/spec/unit/virtus/set_default_attributes_spec.rb +32 -0
- data/spec/unit/virtus/value_object_spec.rb +138 -0
- data/virtus2.gemspec +26 -0
- metadata +225 -0
data/lib/virtus.rb
ADDED
@@ -0,0 +1,310 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
# Base module which adds Attribute API to your classes
|
4
|
+
module Virtus
|
5
|
+
|
6
|
+
# Provides args for const_get and const_defined? to make them behave
|
7
|
+
# consistently across different versions of ruby
|
8
|
+
EXTRA_CONST_ARGS = (RUBY_VERSION < '1.9' ? [] : [ false ]).freeze
|
9
|
+
|
10
|
+
# Represents an undefined parameter used by auto-generated option methods
|
11
|
+
Undefined = Object.new.freeze
|
12
|
+
|
13
|
+
class CoercionError < StandardError
|
14
|
+
attr_reader :output, :attribute
|
15
|
+
|
16
|
+
def initialize(output, attribute)
|
17
|
+
@output, @attribute = output, attribute
|
18
|
+
super(build_message)
|
19
|
+
end
|
20
|
+
|
21
|
+
def build_message
|
22
|
+
if attribute_name?
|
23
|
+
"Failed to coerce attribute `#{attribute_name}' from #{output.inspect} into #{target_type}"
|
24
|
+
else
|
25
|
+
"Failed to coerce #{output.inspect} into #{target_type}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def attribute_name
|
30
|
+
attribute.options[:name]
|
31
|
+
end
|
32
|
+
|
33
|
+
def attribute_name?
|
34
|
+
attribute_name ? true : false
|
35
|
+
end
|
36
|
+
|
37
|
+
def target_type
|
38
|
+
if attribute.respond_to?(:coercion_error_message)
|
39
|
+
attribute.coercion_error_message
|
40
|
+
else
|
41
|
+
attribute.primitive.inspect
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Extends base class or a module with virtus methods
|
47
|
+
#
|
48
|
+
# @param [Object] object
|
49
|
+
#
|
50
|
+
# @return [undefined]
|
51
|
+
#
|
52
|
+
# @deprecated
|
53
|
+
#
|
54
|
+
# @api private
|
55
|
+
def self.included(object)
|
56
|
+
super
|
57
|
+
if Class === object
|
58
|
+
Virtus.warn("including Virtus module is deprecated. Use 'include Virtus.model' instead #{caller.first}")
|
59
|
+
object.send(:include, ClassInclusions)
|
60
|
+
else
|
61
|
+
Virtus.warn("including Virtus module is deprecated. Use 'include Virtus.module' instead #{caller.first}")
|
62
|
+
object.extend(ModuleExtensions)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
private_class_method :included
|
66
|
+
|
67
|
+
# Extends an object with virtus extensions
|
68
|
+
#
|
69
|
+
# @param [Object] object
|
70
|
+
#
|
71
|
+
# @return [undefined]
|
72
|
+
#
|
73
|
+
# @deprecated
|
74
|
+
#
|
75
|
+
# @api private
|
76
|
+
def self.extended(object)
|
77
|
+
Virtus.warn("extending with Virtus module is deprecated. Use 'extend(Virtus.model)' instead #{caller.first}")
|
78
|
+
object.extend(Extensions)
|
79
|
+
end
|
80
|
+
private_class_method :extended
|
81
|
+
|
82
|
+
# Sets the global coercer configuration
|
83
|
+
#
|
84
|
+
# @example
|
85
|
+
# Virtus.coercer do |config|
|
86
|
+
# config.string.boolean_map = { true => '1', false => '0' }
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# @return [Coercible::Coercer]
|
90
|
+
#
|
91
|
+
# @api public
|
92
|
+
def self.coercer(&block)
|
93
|
+
configuration.coercer(&block)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Sets the global coercion configuration value
|
97
|
+
#
|
98
|
+
# @param [Boolean] value
|
99
|
+
#
|
100
|
+
# @return [Virtus]
|
101
|
+
#
|
102
|
+
# @api public
|
103
|
+
def self.coerce=(value)
|
104
|
+
configuration.coerce = value
|
105
|
+
self
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns the global coercion setting
|
109
|
+
#
|
110
|
+
# @return [Boolean]
|
111
|
+
#
|
112
|
+
# @api public
|
113
|
+
def self.coerce
|
114
|
+
configuration.coerce
|
115
|
+
end
|
116
|
+
|
117
|
+
# Provides access to the global Virtus configuration
|
118
|
+
#
|
119
|
+
# @example
|
120
|
+
# Virtus.config do |config|
|
121
|
+
# config.coerce = false
|
122
|
+
# end
|
123
|
+
#
|
124
|
+
# @return [Configuration]
|
125
|
+
#
|
126
|
+
# @api public
|
127
|
+
def self.config(&block)
|
128
|
+
yield configuration if block_given?
|
129
|
+
configuration
|
130
|
+
end
|
131
|
+
|
132
|
+
# Provides access to the Virtus module builder
|
133
|
+
# see Virtus::ModuleBuilder
|
134
|
+
#
|
135
|
+
# @example
|
136
|
+
# MyVirtusModule = Virtus.module { |mod|
|
137
|
+
# mod.coerce = true
|
138
|
+
# mod.string.boolean_map = { 'yup' => true, 'nope' => false }
|
139
|
+
# }
|
140
|
+
#
|
141
|
+
# class Book
|
142
|
+
# include MyVirtusModule
|
143
|
+
#
|
144
|
+
# attribute :published, Boolean
|
145
|
+
# end
|
146
|
+
#
|
147
|
+
# # This could be made more succinct as well
|
148
|
+
# class OtherBook
|
149
|
+
# include Virtus.module { |m| m.coerce = false }
|
150
|
+
# end
|
151
|
+
#
|
152
|
+
# @return [Module]
|
153
|
+
#
|
154
|
+
# @api public
|
155
|
+
def self.model(options = {}, &block)
|
156
|
+
ModelBuilder.call(options, &block)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Builds a module for...modules
|
160
|
+
#
|
161
|
+
# @example
|
162
|
+
#
|
163
|
+
# module Common
|
164
|
+
# include Virtus.module
|
165
|
+
#
|
166
|
+
# attribute :name, String
|
167
|
+
# attribute :age, Integer
|
168
|
+
# end
|
169
|
+
#
|
170
|
+
# class User
|
171
|
+
# include Common
|
172
|
+
# end
|
173
|
+
#
|
174
|
+
# class Admin
|
175
|
+
# include Common
|
176
|
+
# end
|
177
|
+
#
|
178
|
+
# @return [Module]
|
179
|
+
#
|
180
|
+
# @api public
|
181
|
+
def self.module(options = {}, &block)
|
182
|
+
ModuleBuilder.call(options, &block)
|
183
|
+
end
|
184
|
+
|
185
|
+
# Builds a module for value object models
|
186
|
+
#
|
187
|
+
# @example
|
188
|
+
#
|
189
|
+
# class GeoLocation
|
190
|
+
# include Virtus.value_object
|
191
|
+
#
|
192
|
+
# values do
|
193
|
+
# attribute :lat, Float
|
194
|
+
# attribute :lng, Float
|
195
|
+
# end
|
196
|
+
# end
|
197
|
+
#
|
198
|
+
# @return [Module]
|
199
|
+
#
|
200
|
+
# @api public
|
201
|
+
def self.value_object(options = {}, &block)
|
202
|
+
ValueObjectBuilder.call(options, &block)
|
203
|
+
end
|
204
|
+
|
205
|
+
# Global configuration instance
|
206
|
+
#
|
207
|
+
# @ return [Configuration]
|
208
|
+
#
|
209
|
+
# @api private
|
210
|
+
def self.configuration
|
211
|
+
@configuration ||= Configuration.new
|
212
|
+
end
|
213
|
+
|
214
|
+
# @api private
|
215
|
+
def self.constantize(type)
|
216
|
+
inflector.constantize(type)
|
217
|
+
end
|
218
|
+
|
219
|
+
# @api private
|
220
|
+
def self.inflector
|
221
|
+
@inflector ||=
|
222
|
+
begin
|
223
|
+
require 'dry/inflector'
|
224
|
+
Dry::Inflector.new
|
225
|
+
rescue LoadError
|
226
|
+
raise(
|
227
|
+
NotImplementedError,
|
228
|
+
'Virtus needs dry-inflector gem to constantize namespaced constant names'
|
229
|
+
)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# Finalize pending attributes
|
234
|
+
#
|
235
|
+
# @example
|
236
|
+
# class User
|
237
|
+
# include Virtus.model(:finalize => false)
|
238
|
+
#
|
239
|
+
# attribute :address, 'Address'
|
240
|
+
# end
|
241
|
+
#
|
242
|
+
# class Address
|
243
|
+
# include Virtus.model(:finalize => false)
|
244
|
+
#
|
245
|
+
# attribute :user, 'User'
|
246
|
+
# end
|
247
|
+
#
|
248
|
+
# Virtus.finalize # this will resolve constant names
|
249
|
+
#
|
250
|
+
# @return [Array] array of finalized models
|
251
|
+
#
|
252
|
+
# @api public
|
253
|
+
def self.finalize
|
254
|
+
Builder.pending.each do |klass|
|
255
|
+
klass.attribute_set.finalize
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# @api private
|
260
|
+
def self.warn(msg)
|
261
|
+
Kernel.warn(msg)
|
262
|
+
end
|
263
|
+
|
264
|
+
end # module Virtus
|
265
|
+
|
266
|
+
require 'descendants_tracker'
|
267
|
+
require 'axiom-types'
|
268
|
+
require 'coercible'
|
269
|
+
|
270
|
+
require 'virtus/support/equalizer'
|
271
|
+
require 'virtus/support/options'
|
272
|
+
require 'virtus/support/type_lookup'
|
273
|
+
|
274
|
+
require 'virtus/model'
|
275
|
+
require 'virtus/extensions'
|
276
|
+
require 'virtus/const_missing_extensions'
|
277
|
+
require 'virtus/class_inclusions'
|
278
|
+
require 'virtus/module_extensions'
|
279
|
+
|
280
|
+
require 'virtus/configuration'
|
281
|
+
require 'virtus/builder'
|
282
|
+
require 'virtus/builder/hook_context'
|
283
|
+
|
284
|
+
require 'virtus/class_methods'
|
285
|
+
require 'virtus/instance_methods'
|
286
|
+
|
287
|
+
require 'virtus/value_object'
|
288
|
+
|
289
|
+
require 'virtus/coercer'
|
290
|
+
require 'virtus/attribute_set'
|
291
|
+
|
292
|
+
require 'virtus/attribute/default_value'
|
293
|
+
require 'virtus/attribute/default_value/from_clonable'
|
294
|
+
require 'virtus/attribute/default_value/from_callable'
|
295
|
+
require 'virtus/attribute/default_value/from_symbol'
|
296
|
+
|
297
|
+
require 'virtus/attribute'
|
298
|
+
require 'virtus/attribute/builder'
|
299
|
+
require 'virtus/attribute/coercer'
|
300
|
+
require 'virtus/attribute/accessor'
|
301
|
+
require 'virtus/attribute/coercible'
|
302
|
+
require 'virtus/attribute/strict'
|
303
|
+
require 'virtus/attribute/lazy_default'
|
304
|
+
require 'virtus/attribute/nullify_blank'
|
305
|
+
|
306
|
+
require 'virtus/attribute/boolean'
|
307
|
+
require 'virtus/attribute/collection'
|
308
|
+
require 'virtus/attribute/enum'
|
309
|
+
require 'virtus/attribute/hash'
|
310
|
+
require 'virtus/attribute/embedded_value'
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Adding attribute called 'attributes'" do
|
4
|
+
|
5
|
+
context "when mass assignment is disabled" do
|
6
|
+
before do
|
7
|
+
module Examples
|
8
|
+
class User
|
9
|
+
include Virtus.model(mass_assignment: false)
|
10
|
+
|
11
|
+
attribute :attributes
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it "allows model to use `attributes` attribute" do
|
17
|
+
user = Examples::User.new
|
18
|
+
expect(user.attributes).to eq(nil)
|
19
|
+
user.attributes = "attributes string"
|
20
|
+
expect(user.attributes).to eq("attributes string")
|
21
|
+
end
|
22
|
+
|
23
|
+
it "doesn't accept `attributes` key in initializer" do
|
24
|
+
user = Examples::User.new(attributes: 'attributes string')
|
25
|
+
expect(user.attributes).to eq(nil)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'I can create a Virtus module' do
|
4
|
+
before do
|
5
|
+
module Examples
|
6
|
+
NoncoercingModule = Virtus.model { |config|
|
7
|
+
config.coerce = false
|
8
|
+
}
|
9
|
+
|
10
|
+
CoercingModule = Virtus.model { |config|
|
11
|
+
config.coerce = true
|
12
|
+
|
13
|
+
config.coercer do |coercer|
|
14
|
+
coercer.string.boolean_map = { 'yup' => true, 'nope' => false }
|
15
|
+
end
|
16
|
+
}
|
17
|
+
|
18
|
+
StrictModule = Virtus.model { |config|
|
19
|
+
config.strict = true
|
20
|
+
}
|
21
|
+
|
22
|
+
BlankModule = Virtus.model { |config|
|
23
|
+
config.nullify_blank = true
|
24
|
+
}
|
25
|
+
|
26
|
+
class NoncoercedUser
|
27
|
+
include NoncoercingModule
|
28
|
+
|
29
|
+
attribute :name, String
|
30
|
+
attribute :happy, String
|
31
|
+
end
|
32
|
+
|
33
|
+
class CoercedUser
|
34
|
+
include CoercingModule
|
35
|
+
|
36
|
+
attribute :name, String
|
37
|
+
attribute :happy, Boolean
|
38
|
+
end
|
39
|
+
|
40
|
+
class StrictModel
|
41
|
+
include StrictModule
|
42
|
+
|
43
|
+
attribute :stuff, Hash
|
44
|
+
attribute :happy, Boolean, :strict => false
|
45
|
+
end
|
46
|
+
|
47
|
+
class BlankModel
|
48
|
+
include BlankModule
|
49
|
+
|
50
|
+
attribute :stuff, Hash
|
51
|
+
attribute :happy, Boolean, :nullify_blank => false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
specify 'including a custom module with coercion disabled' do
|
57
|
+
user = Examples::NoncoercedUser.new(:name => :Giorgio, :happy => 'yes')
|
58
|
+
|
59
|
+
expect(user.name).to be(:Giorgio)
|
60
|
+
expect(user.happy).to eql('yes')
|
61
|
+
end
|
62
|
+
|
63
|
+
specify 'including a custom module with coercion enabled' do
|
64
|
+
user = Examples::CoercedUser.new(:name => 'Paul', :happy => 'nope')
|
65
|
+
|
66
|
+
expect(user.name).to eql('Paul')
|
67
|
+
expect(user.happy).to be(false)
|
68
|
+
end
|
69
|
+
|
70
|
+
specify 'including a custom module with strict enabled' do
|
71
|
+
model = Examples::StrictModel.new
|
72
|
+
|
73
|
+
expect { model.stuff = 'foo' }.to raise_error(Virtus::CoercionError)
|
74
|
+
|
75
|
+
model.happy = 'foo'
|
76
|
+
|
77
|
+
expect(model.happy).to eql('foo')
|
78
|
+
end
|
79
|
+
|
80
|
+
specify 'including a custom module with nullify blank enabled' do
|
81
|
+
model = Examples::BlankModel.new
|
82
|
+
|
83
|
+
model.stuff = ''
|
84
|
+
expect(model.stuff).to be_nil
|
85
|
+
|
86
|
+
model.happy = 'foo'
|
87
|
+
|
88
|
+
expect(model.happy).to eql('foo')
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# TODO: refactor to make it inline with the new style of integration specs
|
4
|
+
|
5
|
+
class Address
|
6
|
+
include Virtus
|
7
|
+
|
8
|
+
attribute :address, String
|
9
|
+
attribute :locality, String
|
10
|
+
attribute :region, String
|
11
|
+
attribute :postal_code, String
|
12
|
+
end
|
13
|
+
|
14
|
+
class PhoneNumber
|
15
|
+
include Virtus
|
16
|
+
|
17
|
+
attribute :number, String
|
18
|
+
end
|
19
|
+
|
20
|
+
class User
|
21
|
+
include Virtus
|
22
|
+
|
23
|
+
attribute :phone_numbers, Array[PhoneNumber]
|
24
|
+
attribute :addresses, Set[Address]
|
25
|
+
end
|
26
|
+
|
27
|
+
describe User do
|
28
|
+
it { is_expected.to respond_to(:phone_numbers) }
|
29
|
+
it { is_expected.to respond_to(:phone_numbers=) }
|
30
|
+
it { is_expected.to respond_to(:addresses) }
|
31
|
+
it { is_expected.to respond_to(:addresses=) }
|
32
|
+
|
33
|
+
let(:instance) do
|
34
|
+
described_class.new(:phone_numbers => phone_numbers_attributes,
|
35
|
+
:addresses => addresses_attributes)
|
36
|
+
end
|
37
|
+
|
38
|
+
let(:phone_numbers_attributes) { [
|
39
|
+
{ :number => '212-555-1212' },
|
40
|
+
{ :number => '919-444-3265' },
|
41
|
+
] }
|
42
|
+
|
43
|
+
let(:addresses_attributes) { [
|
44
|
+
{ :address => '1234 Any St.', :locality => 'Anytown', :region => "DC", :postal_code => "21234" },
|
45
|
+
] }
|
46
|
+
|
47
|
+
describe '#phone_numbers' do
|
48
|
+
describe 'first entry' do
|
49
|
+
subject { instance.phone_numbers.first }
|
50
|
+
|
51
|
+
it { is_expected.to be_instance_of(PhoneNumber) }
|
52
|
+
|
53
|
+
describe '#number' do
|
54
|
+
subject { super().number }
|
55
|
+
it { is_expected.to eql('212-555-1212') }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe 'last entry' do
|
60
|
+
subject { instance.phone_numbers.last }
|
61
|
+
|
62
|
+
it { is_expected.to be_instance_of(PhoneNumber) }
|
63
|
+
|
64
|
+
describe '#number' do
|
65
|
+
subject { super().number }
|
66
|
+
it { is_expected.to eql('919-444-3265') }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe '#addresses' do
|
72
|
+
subject { instance.addresses.first }
|
73
|
+
|
74
|
+
it { is_expected.to be_instance_of(Address) }
|
75
|
+
|
76
|
+
describe '#address' do
|
77
|
+
subject { super().address }
|
78
|
+
it { is_expected.to eql('1234 Any St.') }
|
79
|
+
end
|
80
|
+
|
81
|
+
describe '#locality' do
|
82
|
+
subject { super().locality }
|
83
|
+
it { is_expected.to eql('Anytown') }
|
84
|
+
end
|
85
|
+
|
86
|
+
describe '#region' do
|
87
|
+
subject { super().region }
|
88
|
+
it { is_expected.to eql('DC') }
|
89
|
+
end
|
90
|
+
|
91
|
+
describe '#postal_code' do
|
92
|
+
subject { super().postal_code }
|
93
|
+
it { is_expected.to eql('21234') }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|