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.
- 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
|