vorpal 0.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 +15 -0
- data/.gitignore +15 -0
- data/.yardopts +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +167 -0
- data/Rakefile +2 -0
- data/lib/simple_serializer/simple_deserializer.rb +67 -0
- data/lib/simple_serializer/simple_serializer.rb +72 -0
- data/lib/vorpal/aggregate_repository.rb +445 -0
- data/lib/vorpal/config_builder.rb +148 -0
- data/lib/vorpal/configs.rb +274 -0
- data/lib/vorpal/configuration.rb +61 -0
- data/lib/vorpal/hash_initialization.rb +10 -0
- data/lib/vorpal/identity_map.rb +26 -0
- data/lib/vorpal/version.rb +3 -0
- data/lib/vorpal.rb +5 -0
- data/spec/integration_spec_helper.rb +41 -0
- data/spec/vorpal/aggregate_repository_spec.rb +759 -0
- data/spec/vorpal/class_config_builder_spec.rb +11 -0
- data/vorpal.gemspec +28 -0
- metadata +153 -0
@@ -0,0 +1,445 @@
|
|
1
|
+
require 'vorpal/identity_map'
|
2
|
+
|
3
|
+
module Vorpal
|
4
|
+
class AggregateRepository
|
5
|
+
# @private
|
6
|
+
def initialize(class_configs)
|
7
|
+
configure(class_configs)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Saves an aggregate to the DB. Inserts objects that are new to the
|
11
|
+
# aggregate, updates existing objects and deletes objects that are no longer
|
12
|
+
# present.
|
13
|
+
#
|
14
|
+
# Objects that are on the boundary of the aggregate (owned: false) will not
|
15
|
+
# be inserted, updated, or deleted. However, the relationships to these
|
16
|
+
# objects (provided they are stored within the aggregate) will be saved.
|
17
|
+
#
|
18
|
+
# @param object [Object] Root of the aggregate to be saved.
|
19
|
+
# @return [Object] Root of the aggregate.
|
20
|
+
def persist(object)
|
21
|
+
mapping = {}
|
22
|
+
serialize(object, mapping)
|
23
|
+
new_objects = get_unsaved_objects(mapping.keys)
|
24
|
+
set_primary_keys(object, mapping)
|
25
|
+
set_foreign_keys(object, mapping)
|
26
|
+
remove_orphans(object, mapping)
|
27
|
+
save(object, mapping)
|
28
|
+
object
|
29
|
+
rescue
|
30
|
+
nil_out_object_ids(new_objects)
|
31
|
+
raise
|
32
|
+
end
|
33
|
+
|
34
|
+
# Like {#persist} but operates on multiple aggregates. Roots do not need to
|
35
|
+
# be of the same type.
|
36
|
+
#
|
37
|
+
# @param objects [[Object]] array of aggregate roots to be saved.
|
38
|
+
# @return [[Object]] array of aggregate roots.
|
39
|
+
#
|
40
|
+
# TODO: Nil out object ids if one of the objects can't be saved?
|
41
|
+
def persist_all(objects)
|
42
|
+
objects.map(&method(:persist))
|
43
|
+
end
|
44
|
+
|
45
|
+
# Loads an aggregate from the DB. Will eagerly load all objects in the
|
46
|
+
# aggregate and on the boundary (owned: false).
|
47
|
+
#
|
48
|
+
# @param id [Integer] Primary key value of the root of the aggregate to be
|
49
|
+
# loaded.
|
50
|
+
# @param domain_class [Class] Type of the root of the aggregate to
|
51
|
+
# be loaded.
|
52
|
+
# @return [Object] Entity with the given primary key value and type.
|
53
|
+
def load(id, domain_class, identity_map=IdentityMap.new)
|
54
|
+
db_object = @configs.config_for(domain_class).load_by_id(id)
|
55
|
+
hydrate(db_object, identity_map)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Like {#load} but operates on multiple ids.
|
59
|
+
#
|
60
|
+
# @param ids [[Integer]] Array of primary key values of the roots of the
|
61
|
+
# aggregates to be loaded.
|
62
|
+
# @param domain_class [Class] Type of the roots of the aggregate to be loaded.
|
63
|
+
# @return [[Object]] Entities with the given primary key values and type.
|
64
|
+
def load_all(ids, domain_class)
|
65
|
+
identity_map = IdentityMap.new
|
66
|
+
ids.map { |id| load(id, domain_class, identity_map) }
|
67
|
+
end
|
68
|
+
|
69
|
+
# Removes an aggregate from the DB. Even if the aggregate contains unsaved
|
70
|
+
# changes this method will correctly remove everything.
|
71
|
+
#
|
72
|
+
# @param object [Object] Root of the aggregate to be destroyed.
|
73
|
+
# @return [Object] Root that was passed in.
|
74
|
+
def destroy(object)
|
75
|
+
config = @configs.config_for(object.class)
|
76
|
+
db_object = config.find_in_db(object)
|
77
|
+
@traversal.accept_for_db(db_object, DestroyVisitor.new())
|
78
|
+
object
|
79
|
+
end
|
80
|
+
|
81
|
+
# Like {#destroy} but operates on multiple aggregates. Roots do not need to
|
82
|
+
# be of the same type.
|
83
|
+
#
|
84
|
+
# @param objects [[Object]] Array of roots of the aggregates to be destroyed.
|
85
|
+
# @return [[Object]] Roots that were passed in.
|
86
|
+
def destroy_all(objects)
|
87
|
+
objects.each(&method(:destroy))
|
88
|
+
objects
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def configure(class_configs)
|
94
|
+
@configs = MasterConfig.new(class_configs)
|
95
|
+
@traversal = Traversal.new(@configs)
|
96
|
+
end
|
97
|
+
|
98
|
+
def hydrate(db_object, identity_map)
|
99
|
+
deserialize(db_object, identity_map)
|
100
|
+
set_associations(db_object, identity_map)
|
101
|
+
identity_map.get(db_object)
|
102
|
+
end
|
103
|
+
|
104
|
+
def serialize(object, mapping)
|
105
|
+
@traversal.accept_for_domain(object, SerializeVisitor.new(mapping))
|
106
|
+
end
|
107
|
+
|
108
|
+
def set_primary_keys(object, mapping)
|
109
|
+
@traversal.accept_for_domain(object, IdentityVisitor.new(mapping))
|
110
|
+
mapping.rehash # needs to happen because setting the id on an AR::Base model changes its hash value
|
111
|
+
end
|
112
|
+
|
113
|
+
def set_foreign_keys(object, mapping)
|
114
|
+
@traversal.accept_for_domain(object, PersistenceAssociationVisitor.new(mapping))
|
115
|
+
end
|
116
|
+
|
117
|
+
def save(object, mapping)
|
118
|
+
@traversal.accept_for_domain(object, SaveVisitor.new(mapping))
|
119
|
+
end
|
120
|
+
|
121
|
+
def remove_orphans(object, mapping)
|
122
|
+
diff_visitor = AggregateDiffVisitor.new(mapping.values)
|
123
|
+
@traversal.accept_for_db(mapping[object], diff_visitor)
|
124
|
+
|
125
|
+
orphans = diff_visitor.orphans
|
126
|
+
orphans.each { |o| @configs.config_for_db(o.class).destroy(o) }
|
127
|
+
end
|
128
|
+
|
129
|
+
def deserialize(db_object, identity_map)
|
130
|
+
@traversal.accept_for_db(db_object, DeserializeVisitor.new(identity_map))
|
131
|
+
end
|
132
|
+
|
133
|
+
def set_associations(db_object, identity_map)
|
134
|
+
@traversal.accept_for_db(db_object, DomainAssociationVisitor.new(identity_map))
|
135
|
+
end
|
136
|
+
|
137
|
+
def get_unsaved_objects(objects)
|
138
|
+
objects.find_all { |object| object.id.nil? }
|
139
|
+
end
|
140
|
+
|
141
|
+
def nil_out_object_ids(objects)
|
142
|
+
objects ||= []
|
143
|
+
objects.each { |object| object.id = nil }
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# @private
|
148
|
+
class Traversal
|
149
|
+
def initialize(configs)
|
150
|
+
@configs = configs
|
151
|
+
end
|
152
|
+
|
153
|
+
def accept_for_domain(object, visitor, already_visited=[])
|
154
|
+
return if object.nil?
|
155
|
+
|
156
|
+
config = @configs.config_for(object.class)
|
157
|
+
return if config.nil?
|
158
|
+
|
159
|
+
return if already_visited.include?(object)
|
160
|
+
already_visited << object
|
161
|
+
|
162
|
+
visitor.visit_object(object, config)
|
163
|
+
|
164
|
+
config.belongs_tos.each do |belongs_to_config|
|
165
|
+
child = belongs_to_config.get_child(object)
|
166
|
+
accept_for_domain(child, visitor, already_visited) if visitor.continue_traversal?(belongs_to_config)
|
167
|
+
|
168
|
+
visitor.visit_belongs_to(object, child, belongs_to_config)
|
169
|
+
end
|
170
|
+
|
171
|
+
config.has_ones.each do |has_one_config|
|
172
|
+
child = has_one_config.get_child(object)
|
173
|
+
accept_for_domain(child, visitor, already_visited) if visitor.continue_traversal?(has_one_config)
|
174
|
+
|
175
|
+
visitor.visit_has_one(object, child, has_one_config)
|
176
|
+
end
|
177
|
+
|
178
|
+
config.has_manys.each do |has_many_config|
|
179
|
+
children = has_many_config.get_children(object)
|
180
|
+
children.each do |child|
|
181
|
+
accept_for_domain(child, visitor, already_visited) if visitor.continue_traversal?(has_many_config)
|
182
|
+
end
|
183
|
+
visitor.visit_has_many(object, children, has_many_config)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def accept_for_db(db_object, visitor, already_visited=[])
|
188
|
+
return if db_object.nil?
|
189
|
+
|
190
|
+
config = @configs.config_for_db(db_object.class)
|
191
|
+
return if config.nil?
|
192
|
+
|
193
|
+
return if already_visited.include?(db_object)
|
194
|
+
already_visited << db_object
|
195
|
+
|
196
|
+
visitor.visit_object(db_object, config)
|
197
|
+
|
198
|
+
config.belongs_tos.each do |belongs_to_config|
|
199
|
+
child = belongs_to_config.load_child(db_object)
|
200
|
+
accept_for_db(child, visitor, already_visited) if visitor.continue_traversal?(belongs_to_config)
|
201
|
+
|
202
|
+
visitor.visit_belongs_to(db_object, child, belongs_to_config)
|
203
|
+
end
|
204
|
+
|
205
|
+
config.has_ones.each do |has_one_config|
|
206
|
+
child = has_one_config.load_child(db_object)
|
207
|
+
accept_for_db(child, visitor, already_visited) if visitor.continue_traversal?(has_one_config)
|
208
|
+
|
209
|
+
visitor.visit_belongs_to(db_object, child, has_one_config)
|
210
|
+
end
|
211
|
+
|
212
|
+
config.has_manys.each do |has_many_config|
|
213
|
+
children = has_many_config.load_children(db_object)
|
214
|
+
children.each do |child|
|
215
|
+
accept_for_db(child, visitor, already_visited) if visitor.continue_traversal?(has_many_config)
|
216
|
+
end
|
217
|
+
visitor.visit_has_many(db_object, children, has_many_config)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# @private
|
223
|
+
module AggregateVisitorTemplate
|
224
|
+
def visit_object(object, config)
|
225
|
+
# override me!
|
226
|
+
end
|
227
|
+
|
228
|
+
def visit_belongs_to(parent, child, belongs_to_config)
|
229
|
+
# override me!
|
230
|
+
end
|
231
|
+
|
232
|
+
def visit_has_one(parent, child, has_one_config)
|
233
|
+
# override me!
|
234
|
+
end
|
235
|
+
|
236
|
+
def visit_has_many(parent, children, has_many_config)
|
237
|
+
# override me!
|
238
|
+
end
|
239
|
+
|
240
|
+
def continue_traversal?(association_config)
|
241
|
+
true
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# @private
|
246
|
+
class SerializeVisitor
|
247
|
+
include AggregateVisitorTemplate
|
248
|
+
|
249
|
+
def initialize(mapping)
|
250
|
+
@mapping = mapping
|
251
|
+
end
|
252
|
+
|
253
|
+
def visit_object(object, config)
|
254
|
+
serialize(object, config)
|
255
|
+
end
|
256
|
+
|
257
|
+
def continue_traversal?(association_config)
|
258
|
+
association_config.owned
|
259
|
+
end
|
260
|
+
|
261
|
+
def serialize(object, config)
|
262
|
+
db_object = serialize_object(object, config)
|
263
|
+
@mapping[object] = db_object
|
264
|
+
end
|
265
|
+
|
266
|
+
def serialize_object(object, config)
|
267
|
+
if config.serialization_required?
|
268
|
+
attributes = config.serialize(object)
|
269
|
+
if object.id.nil?
|
270
|
+
config.build_db_object(attributes)
|
271
|
+
else
|
272
|
+
db_object = config.find_in_db(object)
|
273
|
+
config.set_db_object_attributes(db_object, attributes)
|
274
|
+
db_object
|
275
|
+
end
|
276
|
+
else
|
277
|
+
object
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# @private
|
283
|
+
class IdentityVisitor
|
284
|
+
include AggregateVisitorTemplate
|
285
|
+
|
286
|
+
def initialize(mapping)
|
287
|
+
@mapping = mapping
|
288
|
+
end
|
289
|
+
|
290
|
+
def visit_object(object, config)
|
291
|
+
set_primary_key(object, config)
|
292
|
+
end
|
293
|
+
|
294
|
+
def continue_traversal?(association_config)
|
295
|
+
association_config.owned
|
296
|
+
end
|
297
|
+
|
298
|
+
private
|
299
|
+
|
300
|
+
def set_primary_key(object, config)
|
301
|
+
return unless object.id.nil?
|
302
|
+
|
303
|
+
primary_key = config.get_primary_keys(1).first
|
304
|
+
|
305
|
+
@mapping[object].id = primary_key
|
306
|
+
object.id = primary_key
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
# @private
|
311
|
+
class PersistenceAssociationVisitor
|
312
|
+
include AggregateVisitorTemplate
|
313
|
+
|
314
|
+
def initialize(mapping)
|
315
|
+
@mapping = mapping
|
316
|
+
end
|
317
|
+
|
318
|
+
def visit_belongs_to(parent, child, belongs_to_config)
|
319
|
+
belongs_to_config.set_foreign_key(@mapping[parent], child)
|
320
|
+
end
|
321
|
+
|
322
|
+
def visit_has_one(parent, child, has_one_config)
|
323
|
+
return unless has_one_config.owned
|
324
|
+
has_one_config.set_foreign_key(@mapping[child], parent)
|
325
|
+
end
|
326
|
+
|
327
|
+
def visit_has_many(parent, children, has_many_config)
|
328
|
+
return unless has_many_config.owned
|
329
|
+
children.each do |child|
|
330
|
+
has_many_config.set_foreign_key(@mapping[child], parent)
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
def continue_traversal?(association_config)
|
335
|
+
association_config.owned
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
# @private
|
340
|
+
class SaveVisitor
|
341
|
+
include AggregateVisitorTemplate
|
342
|
+
|
343
|
+
def initialize(mapping)
|
344
|
+
@mapping = mapping
|
345
|
+
end
|
346
|
+
|
347
|
+
def visit_object(object, config)
|
348
|
+
config.save(@mapping[object])
|
349
|
+
end
|
350
|
+
|
351
|
+
def continue_traversal?(association_config)
|
352
|
+
association_config.owned
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
# @private
|
357
|
+
class AggregateDiffVisitor
|
358
|
+
include AggregateVisitorTemplate
|
359
|
+
|
360
|
+
def initialize(db_objects_in_aggregate)
|
361
|
+
@db_objects_in_aggregate = db_objects_in_aggregate
|
362
|
+
@db_objects_in_db = []
|
363
|
+
end
|
364
|
+
|
365
|
+
def visit_object(db_object, config)
|
366
|
+
@db_objects_in_db << db_object
|
367
|
+
end
|
368
|
+
|
369
|
+
def continue_traversal?(association_config)
|
370
|
+
association_config.owned
|
371
|
+
end
|
372
|
+
|
373
|
+
def orphans
|
374
|
+
@db_objects_in_db - @db_objects_in_aggregate
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
# @private
|
379
|
+
class DeserializeVisitor
|
380
|
+
include AggregateVisitorTemplate
|
381
|
+
|
382
|
+
def initialize(identity_map)
|
383
|
+
@identity_map = identity_map
|
384
|
+
end
|
385
|
+
|
386
|
+
def visit_object(db_object, config)
|
387
|
+
deserialize_db_object(db_object, config)
|
388
|
+
end
|
389
|
+
|
390
|
+
private
|
391
|
+
|
392
|
+
def deserialize_db_object(db_object, config)
|
393
|
+
@identity_map.get_and_set(db_object) { config.deserialize(db_object) }
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
# @private
|
398
|
+
class DomainAssociationVisitor
|
399
|
+
include AggregateVisitorTemplate
|
400
|
+
|
401
|
+
def initialize(identity_map)
|
402
|
+
@identity_map = identity_map
|
403
|
+
end
|
404
|
+
|
405
|
+
def visit_belongs_to(db_object, db_child, belongs_to_config)
|
406
|
+
associate_one_to_one(db_object, db_child, belongs_to_config)
|
407
|
+
end
|
408
|
+
|
409
|
+
def visit_has_one(db_parent, db_child, has_one_config)
|
410
|
+
associate_one_to_one(db_parent, db_child, has_one_config)
|
411
|
+
end
|
412
|
+
|
413
|
+
def visit_has_many(db_object, db_children, has_many_config)
|
414
|
+
associate_one_to_many(db_object, db_children, has_many_config)
|
415
|
+
end
|
416
|
+
|
417
|
+
private
|
418
|
+
|
419
|
+
def associate_one_to_many(db_object, db_children, has_many_config)
|
420
|
+
parent = @identity_map.get(db_object)
|
421
|
+
children = @identity_map.map(db_children)
|
422
|
+
has_many_config.set_children(parent, children)
|
423
|
+
end
|
424
|
+
|
425
|
+
def associate_one_to_one(db_parent, db_child, belongs_to_config)
|
426
|
+
parent = @identity_map.get(db_parent)
|
427
|
+
child = @identity_map.get(db_child)
|
428
|
+
belongs_to_config.set_child(parent, child)
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
# @private
|
433
|
+
class DestroyVisitor
|
434
|
+
include AggregateVisitorTemplate
|
435
|
+
|
436
|
+
def visit_object(object, config)
|
437
|
+
config.destroy(object)
|
438
|
+
end
|
439
|
+
|
440
|
+
def continue_traversal?(association_config)
|
441
|
+
association_config.owned
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'simple_serializer/simple_serializer'
|
2
|
+
require 'simple_serializer/simple_deserializer'
|
3
|
+
require 'vorpal/configs'
|
4
|
+
|
5
|
+
module Vorpal
|
6
|
+
class ConfigBuilder
|
7
|
+
|
8
|
+
# @private
|
9
|
+
def initialize(clazz, options)
|
10
|
+
@domain_class = clazz
|
11
|
+
@class_options = options
|
12
|
+
@has_manys = []
|
13
|
+
@has_ones = []
|
14
|
+
@belongs_tos = []
|
15
|
+
@fields = []
|
16
|
+
end
|
17
|
+
|
18
|
+
# Maps the given fields to and from the domain object and the DB. Not needed
|
19
|
+
# if a serializer and deserializer were provided.
|
20
|
+
def fields(*fields)
|
21
|
+
@fields = fields
|
22
|
+
end
|
23
|
+
|
24
|
+
# Defines a one-to-many association with a list of objects of the same type.
|
25
|
+
#
|
26
|
+
# @param name [String] Name of the field that will refer to the other object.
|
27
|
+
# @param options [Hash]
|
28
|
+
# @option options [Boolean] :owned
|
29
|
+
# @option options [String] :fk
|
30
|
+
# @option options [String] :fk_type
|
31
|
+
# @option options [Class] :child_class
|
32
|
+
def has_many(name, options={})
|
33
|
+
@has_manys << {name: name}.merge(options)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Defines a one-to-one association with another object where the foreign key
|
37
|
+
# is stored on the other object.
|
38
|
+
#
|
39
|
+
# @param name [String] Name of the field that will refer to the other object.
|
40
|
+
# @param options [Hash]
|
41
|
+
# @option options [Boolean] :owned
|
42
|
+
# @option options [String] :fk
|
43
|
+
# @option options [String] :fk_type
|
44
|
+
# @option options [Class] :child_class
|
45
|
+
def has_one(name, options={})
|
46
|
+
@has_ones << {name: name}.merge(options)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Defines a one-to-one association with another object where the foreign key
|
50
|
+
# is stored on this object.
|
51
|
+
#
|
52
|
+
# This association can be polymorphic. i.e.
|
53
|
+
#
|
54
|
+
# @param name [String] Name of the field that will refer to the other object.
|
55
|
+
# @param options [Hash]
|
56
|
+
# @option options [Boolean] :owned
|
57
|
+
# @option options [String] :fk
|
58
|
+
# @option options [String] :fk_type
|
59
|
+
# @option options [Class] :child_class
|
60
|
+
# @option options [[Class]] :child_classes
|
61
|
+
def belongs_to(name, options={})
|
62
|
+
@belongs_tos << {name: name}.merge(options)
|
63
|
+
end
|
64
|
+
|
65
|
+
# @private
|
66
|
+
def build
|
67
|
+
class_config = build_class_config
|
68
|
+
class_config.has_manys = build_has_manys
|
69
|
+
class_config.has_ones = build_has_ones
|
70
|
+
class_config.belongs_tos = build_belongs_tos
|
71
|
+
|
72
|
+
class_config
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def build_class_config
|
78
|
+
Vorpal::ClassConfig.new(
|
79
|
+
domain_class: @domain_class,
|
80
|
+
table_name: @class_options[:table_name] || table_name,
|
81
|
+
serializer: @class_options[:serializer] || serializer(fields_with_id),
|
82
|
+
deserializer: @class_options[:deserializer] || deserializer(fields_with_id),
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
def fields_with_id
|
87
|
+
[:id].concat @fields
|
88
|
+
end
|
89
|
+
|
90
|
+
def table_name
|
91
|
+
@domain_class.name.tableize
|
92
|
+
end
|
93
|
+
|
94
|
+
def build_has_manys
|
95
|
+
@has_manys.map { |options| build_has_many(options) }
|
96
|
+
end
|
97
|
+
|
98
|
+
def build_has_many(options)
|
99
|
+
options[:child_class] ||= child_class(options[:name])
|
100
|
+
options[:fk] ||= foreign_key(@domain_class.name)
|
101
|
+
options[:owned] = options.fetch(:owned, true)
|
102
|
+
Vorpal::HasManyConfig.new(options)
|
103
|
+
end
|
104
|
+
|
105
|
+
def foreign_key(name)
|
106
|
+
name.to_s.underscore + '_id'
|
107
|
+
end
|
108
|
+
|
109
|
+
def child_class(association_name)
|
110
|
+
association_name.to_s.classify.constantize
|
111
|
+
end
|
112
|
+
|
113
|
+
def build_has_ones
|
114
|
+
@has_ones.map { |options| build_has_one(options) }
|
115
|
+
end
|
116
|
+
|
117
|
+
def build_has_one(options)
|
118
|
+
options[:child_class] ||= child_class(options[:name])
|
119
|
+
options[:fk] ||= foreign_key(@domain_class.name)
|
120
|
+
options[:owned] = options.fetch(:owned, true)
|
121
|
+
Vorpal::HasOneConfig.new(options)
|
122
|
+
end
|
123
|
+
|
124
|
+
def build_belongs_tos
|
125
|
+
@belongs_tos.map { |options| build_belongs_to(options) }
|
126
|
+
end
|
127
|
+
|
128
|
+
def build_belongs_to(options)
|
129
|
+
child_class = options[:child_classes] || options[:child_class] || child_class(options[:name])
|
130
|
+
options[:child_classes] = Array(child_class)
|
131
|
+
options[:fk] ||= foreign_key(options[:name])
|
132
|
+
options[:owned] = options.fetch(:owned, true)
|
133
|
+
Vorpal::BelongsToConfig.new(options)
|
134
|
+
end
|
135
|
+
|
136
|
+
def serializer(attrs)
|
137
|
+
Class.new(SimpleSerializer) do
|
138
|
+
attributes *attrs
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def deserializer(attrs)
|
143
|
+
Class.new(SimpleDeserializer) do
|
144
|
+
data_attributes *attrs
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|