rom 3.0.0.rc1 → 3.0.0.rc2
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 +4 -4
- data/lib/rom/command.rb +281 -31
- data/lib/rom/container.rb +11 -15
- data/lib/rom/gateway.rb +51 -26
- data/lib/rom/relation.rb +44 -6
- data/lib/rom/version.rb +1 -1
- data/spec/unit/rom/commands/pre_and_post_processors_spec.rb +49 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c09afbd9407f30670628cfcfaebf9d903ca6619c
|
4
|
+
data.tar.gz: a94d330cd2a0ecb12c500042b9659ec5ee48c568
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cf5b188aca3186e6f402f4e246edc6555821fb0d0a24e771715267806a923fd3bd19ec4a0270a572b4fd7c48ee8d9ff380a2aca407202d6d84c1846f884b81e7
|
7
|
+
data.tar.gz: 552d6f7c1436ae873f1926feafd38f218090ec3ba0a109b45fa931eb3578452f4611745426adb2fbeca537c9ed36ba66c4b99b5ac00faf815e0e17a921811039
|
data/lib/rom/command.rb
CHANGED
@@ -21,7 +21,7 @@ module ROM
|
|
21
21
|
#
|
22
22
|
# @abstract
|
23
23
|
#
|
24
|
-
# @
|
24
|
+
# @api public
|
25
25
|
class Command
|
26
26
|
extend Initializer
|
27
27
|
include Dry::Equalizer(:relation, :options)
|
@@ -31,20 +31,199 @@ module ROM
|
|
31
31
|
extend Dry::Core::ClassAttributes
|
32
32
|
extend ClassInterface
|
33
33
|
|
34
|
-
|
34
|
+
# @!method self.adapter
|
35
|
+
# Get or set adapter identifier
|
36
|
+
#
|
37
|
+
# @overload adapter
|
38
|
+
# Get adapter identifier
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# ROM::Memory::Commands::Create.adapter
|
42
|
+
# # => :memory
|
43
|
+
#
|
44
|
+
# @return [Symbol]
|
45
|
+
#
|
46
|
+
# @overload adapter(identifier)
|
47
|
+
# Set adapter identifier. This must always match actual adapter identifier
|
48
|
+
# that was used to register an adapter.
|
49
|
+
#
|
50
|
+
# @example
|
51
|
+
# module MyAdapter
|
52
|
+
# class CreateCommand < ROM::Commands::Memory::Create
|
53
|
+
# adapter :my_adapter
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# @api public
|
58
|
+
defines :adapter
|
35
59
|
|
36
|
-
#
|
60
|
+
# @!method self.relation
|
61
|
+
# Get or set relation identifier
|
62
|
+
#
|
63
|
+
# @overload relation
|
64
|
+
# Get relation identifier
|
65
|
+
#
|
66
|
+
# @example
|
67
|
+
# module CreateUser < ROM::Commands::Create[:memory]
|
68
|
+
# relation :users
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# CreateUser.relation
|
72
|
+
# # => :users
|
73
|
+
#
|
74
|
+
# @return [Symbol]
|
75
|
+
#
|
76
|
+
# @overload relation(identifier)
|
77
|
+
# Set relation identifier.
|
78
|
+
#
|
79
|
+
# @example
|
80
|
+
# module CreateUser < ROM::Commands::Create[:memory]
|
81
|
+
# relation :users
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# @api public
|
85
|
+
defines :relation
|
86
|
+
|
87
|
+
# @!method self.relation
|
88
|
+
# Get or set result type
|
89
|
+
#
|
90
|
+
# @overload result
|
91
|
+
# Get result type
|
92
|
+
#
|
93
|
+
# @example
|
94
|
+
# module CreateUser < ROM::Commands::Create[:memory]
|
95
|
+
# result :one
|
96
|
+
# end
|
97
|
+
#
|
98
|
+
# CreateUser.result
|
99
|
+
# # => :one
|
100
|
+
#
|
101
|
+
# @return [Symbol]
|
102
|
+
#
|
103
|
+
# @overload relation(identifier)
|
104
|
+
# Set result type
|
105
|
+
#
|
106
|
+
# @example
|
107
|
+
# module CreateUser < ROM::Commands::Create[:memory]
|
108
|
+
# result :one
|
109
|
+
# end
|
110
|
+
#
|
111
|
+
# @api public
|
112
|
+
defines :result
|
113
|
+
|
114
|
+
# @!method self.relation
|
115
|
+
# Get or set input processing function. This is typically set during setup
|
116
|
+
# to relation's input_schema
|
117
|
+
#
|
118
|
+
# @overload input
|
119
|
+
# Get input processing function
|
120
|
+
#
|
121
|
+
# @example
|
122
|
+
# module CreateUser < ROM::Commands::Create[:memory]
|
123
|
+
# input -> tuple { .. }
|
124
|
+
# end
|
125
|
+
#
|
126
|
+
# CreateUser.input
|
127
|
+
# # Your custom function
|
128
|
+
#
|
129
|
+
# @return [Proc,#call]
|
130
|
+
#
|
131
|
+
# @overload input(identifier)
|
132
|
+
# Set input processing function
|
133
|
+
#
|
134
|
+
# @example
|
135
|
+
# module CreateUser < ROM::Commands::Create[:memory]
|
136
|
+
# input -> tuple { .. }
|
137
|
+
# end
|
138
|
+
#
|
139
|
+
# @api public
|
140
|
+
defines :input
|
141
|
+
|
142
|
+
# @!method self.register_as
|
143
|
+
# Get or set identifier that should be used to register a command in a container
|
144
|
+
#
|
145
|
+
# @overload register_as
|
146
|
+
# Get registration identifier
|
147
|
+
#
|
148
|
+
# @example
|
149
|
+
# module CreateUser < ROM::Commands::Create[:memory]
|
150
|
+
# register_as :create_user
|
151
|
+
# end
|
152
|
+
#
|
153
|
+
# CreateUser.register_as
|
154
|
+
# # => :create_user
|
155
|
+
#
|
156
|
+
# @return [Symbol]
|
157
|
+
#
|
158
|
+
# @overload register_as(identifier)
|
159
|
+
# Set registration identifier
|
160
|
+
#
|
161
|
+
# @example
|
162
|
+
# module CreateUser < ROM::Commands::Create[:memory]
|
163
|
+
# register_as :create_user
|
164
|
+
# end
|
165
|
+
#
|
166
|
+
# @api public
|
167
|
+
defines :register_as
|
168
|
+
|
169
|
+
# @!method self.restrictable
|
170
|
+
# @overload restrictable
|
171
|
+
# Check if a command class is restrictable
|
172
|
+
#
|
173
|
+
# @example
|
174
|
+
# module UpdateUser < ROM::Commands::Update[:memory]
|
175
|
+
# restrictable true
|
176
|
+
# end
|
177
|
+
#
|
178
|
+
# CreateUser.restrictable
|
179
|
+
# # => true
|
180
|
+
#
|
181
|
+
# @return [FalseClass, TrueClass]
|
182
|
+
#
|
183
|
+
# @overload restrictable(value)
|
184
|
+
# Set if a command is restrictable
|
185
|
+
#
|
186
|
+
# @example
|
187
|
+
# module UpdateUser < ROM::Commands::Update[:memory]
|
188
|
+
# restrictable true
|
189
|
+
# end
|
190
|
+
#
|
191
|
+
# @api public
|
192
|
+
defines :restrictable
|
193
|
+
|
194
|
+
# @!attribute [r] relation
|
195
|
+
# @return [Relation] Command's relation
|
37
196
|
param :relation
|
38
197
|
|
39
198
|
CommandType = Types::Strict::Symbol.enum(:create, :update, :delete)
|
40
199
|
Result = Types::Strict::Symbol.enum(:one, :many)
|
41
200
|
|
201
|
+
# @!attribute [r] type
|
202
|
+
# @return [Symbol] The command type, one of :create, :update or :delete
|
42
203
|
option :type, type: CommandType, optional: true
|
204
|
+
|
205
|
+
# @!attribute [r] source
|
206
|
+
# @return [Relation] The source relation
|
43
207
|
option :source, reader: true, optional: true, default: -> c { c.relation }
|
208
|
+
|
209
|
+
# @!attribute [r] type
|
210
|
+
# @return [Symbol] Result type, either :one or :many
|
44
211
|
option :result, reader: true, type: Result
|
212
|
+
|
213
|
+
# @!attribute [r] input
|
214
|
+
# @return [Proc, #call] Tuple processing function, typically uses Relation#input_schema
|
45
215
|
option :input, reader: true
|
216
|
+
|
217
|
+
# @!attribute [r] curry_args
|
218
|
+
# @return [Array] Curried args
|
46
219
|
option :curry_args, reader: true, default: -> _ { EMPTY_ARRAY }
|
220
|
+
|
221
|
+
# @!attribute [r] before
|
222
|
+
# @return [Array<Hash>] An array with before hooks configuration
|
47
223
|
option :before, Types::Coercible::Array, reader: true, as: :before_hooks, default: proc { EMPTY_ARRAY }
|
224
|
+
|
225
|
+
# @!attribute [r] before
|
226
|
+
# @return [Array<Hash>] An array with after hooks configuration
|
48
227
|
option :after, Types::Coercible::Array, reader: true, as: :after_hooks, default: proc { EMPTY_ARRAY }
|
49
228
|
|
50
229
|
input Hash
|
@@ -84,6 +263,8 @@ module ROM
|
|
84
263
|
|
85
264
|
# Call the command and return one or many tuples
|
86
265
|
#
|
266
|
+
# This method will apply before/after hooks automatically
|
267
|
+
#
|
87
268
|
# @api public
|
88
269
|
def call(*args, &block)
|
89
270
|
tuples =
|
@@ -98,7 +279,13 @@ module ROM
|
|
98
279
|
result = prepared ? execute(prepared, &block) : execute(&block)
|
99
280
|
|
100
281
|
if curried?
|
101
|
-
|
282
|
+
if args.size > 0
|
283
|
+
apply_hooks(after_hooks, result, *args)
|
284
|
+
elsif curry_args.size > 1
|
285
|
+
apply_hooks(after_hooks, result, curry_args[1])
|
286
|
+
else
|
287
|
+
apply_hooks(after_hooks, result)
|
288
|
+
end
|
102
289
|
else
|
103
290
|
apply_hooks(after_hooks, result, *args[1..args.size-1])
|
104
291
|
end
|
@@ -116,9 +303,10 @@ module ROM
|
|
116
303
|
|
117
304
|
# Curry this command with provided args
|
118
305
|
#
|
119
|
-
# Curried command can be called without args
|
306
|
+
# Curried command can be called without args. If argument is a graph input processor,
|
307
|
+
# lazy command will be returned, which is used for handling nested input hashes.
|
120
308
|
#
|
121
|
-
# @return [Command]
|
309
|
+
# @return [Command, Lazy]
|
122
310
|
#
|
123
311
|
# @api public
|
124
312
|
def curry(*args)
|
@@ -130,68 +318,130 @@ module ROM
|
|
130
318
|
end
|
131
319
|
alias_method :with, :curry
|
132
320
|
|
321
|
+
# Compose this command with other commands
|
322
|
+
#
|
323
|
+
# Composed commands can handle nested input
|
324
|
+
#
|
325
|
+
# @return [Command::Graph]
|
326
|
+
#
|
327
|
+
# @api public
|
328
|
+
def combine(*others)
|
329
|
+
Graph.new(self, others)
|
330
|
+
end
|
331
|
+
|
332
|
+
# Check if this command is curried
|
333
|
+
#
|
334
|
+
# @return [TrueClass, FalseClass]
|
335
|
+
#
|
133
336
|
# @api public
|
134
337
|
def curried?
|
135
338
|
curry_args.size > 0
|
136
339
|
end
|
137
340
|
|
138
|
-
#
|
139
|
-
|
140
|
-
|
341
|
+
# Return a new command with new options
|
342
|
+
#
|
343
|
+
# @param [Hash] new_opts A hash with new options
|
344
|
+
#
|
345
|
+
# @return [Command]
|
346
|
+
#
|
347
|
+
# @api public
|
348
|
+
def with_opts(new_opts)
|
349
|
+
self.class.new(relation, options.merge(new_opts))
|
141
350
|
end
|
142
351
|
|
352
|
+
# Return a new command with appended before hooks
|
353
|
+
#
|
354
|
+
# @param [Array<Hash>] hooks A list of before hooks configurations
|
355
|
+
#
|
356
|
+
# @return [Command]
|
357
|
+
#
|
143
358
|
# @api public
|
144
|
-
def
|
145
|
-
|
359
|
+
def before(*hooks)
|
360
|
+
self.class.new(relation, options.merge(before: before_hooks + hooks))
|
146
361
|
end
|
147
362
|
|
363
|
+
# Return a new command with appended after hooks
|
364
|
+
#
|
365
|
+
# @param [Array<Hash>] hooks A list of after hooks configurations
|
366
|
+
#
|
367
|
+
# @return [Command]
|
368
|
+
#
|
369
|
+
# @api public
|
370
|
+
def after(*hooks)
|
371
|
+
self.class.new(relation, options.merge(after: after_hooks + hooks))
|
372
|
+
end
|
373
|
+
|
374
|
+
# Return a new command with other source relation
|
375
|
+
#
|
376
|
+
# This can be used to restrict command with a specific relation
|
377
|
+
#
|
378
|
+
# @return [Command]
|
379
|
+
#
|
380
|
+
# @api public
|
381
|
+
def new(new_relation)
|
382
|
+
self.class.build(new_relation, options.merge(source: relation))
|
383
|
+
end
|
384
|
+
|
385
|
+
# Check if this command has any hooks
|
386
|
+
#
|
387
|
+
# @api private
|
388
|
+
def hooks?
|
389
|
+
before_hooks.size > 0 || after_hooks.size > 0
|
390
|
+
end
|
391
|
+
|
392
|
+
# Check if this command is lazy
|
393
|
+
#
|
394
|
+
# @return [false]
|
395
|
+
#
|
148
396
|
# @api private
|
149
397
|
def lazy?
|
150
398
|
false
|
151
399
|
end
|
152
400
|
|
401
|
+
# Check if this command is a graph
|
402
|
+
#
|
403
|
+
# @return [false]
|
404
|
+
#
|
153
405
|
# @api private
|
154
406
|
def graph?
|
155
407
|
false
|
156
408
|
end
|
157
409
|
|
410
|
+
# Check if this command returns a single tuple
|
411
|
+
#
|
412
|
+
# @return [TrueClass,FalseClass]
|
413
|
+
#
|
158
414
|
# @api private
|
159
415
|
def one?
|
160
416
|
result.equal?(:one)
|
161
417
|
end
|
162
418
|
|
419
|
+
# Check if this command returns many tuples
|
420
|
+
#
|
421
|
+
# @return [TrueClass,FalseClass]
|
422
|
+
#
|
163
423
|
# @api private
|
164
424
|
def many?
|
165
425
|
result.equal?(:many)
|
166
426
|
end
|
167
427
|
|
168
|
-
# @api private
|
169
|
-
def new(new_relation)
|
170
|
-
self.class.build(new_relation, options.merge(source: relation))
|
171
|
-
end
|
172
|
-
|
173
|
-
# @api public
|
174
|
-
def with_opts(new_opts)
|
175
|
-
self.class.new(relation, options.merge(new_opts))
|
176
|
-
end
|
177
|
-
|
178
|
-
# @api public
|
179
|
-
def before(*hooks)
|
180
|
-
self.class.new(relation, options.merge(before: before_hooks + hooks))
|
181
|
-
end
|
182
|
-
|
183
|
-
# @api public
|
184
|
-
def after(*hooks)
|
185
|
-
self.class.new(relation, options.merge(after: after_hooks + hooks))
|
186
|
-
end
|
187
|
-
|
188
428
|
private
|
189
429
|
|
430
|
+
# Hook called by Pipeline to get composite class for commands
|
431
|
+
#
|
432
|
+
# @return [Class]
|
433
|
+
#
|
190
434
|
# @api private
|
191
435
|
def composite_class
|
192
436
|
Command::Composite
|
193
437
|
end
|
194
438
|
|
439
|
+
# Apply provided hooks
|
440
|
+
#
|
441
|
+
# Used by #call
|
442
|
+
#
|
443
|
+
# @return [Array<Hash>]
|
444
|
+
#
|
195
445
|
# @api private
|
196
446
|
def apply_hooks(hooks, tuples, *args)
|
197
447
|
hooks.reduce(tuples) do |a, e|
|
data/lib/rom/container.rb
CHANGED
@@ -11,14 +11,14 @@ module ROM
|
|
11
11
|
#
|
12
12
|
# There are 3 types of container setup:
|
13
13
|
#
|
14
|
-
# *
|
14
|
+
# * Setup DSL - a simple block-based configuration which allows configuring
|
15
15
|
# all components and gives you back a container instance. This type is suitable
|
16
16
|
# for small scripts, or in some cases rake tasks
|
17
|
-
# *
|
17
|
+
# * Explicit setup - this type requires creating a configuration object,
|
18
18
|
# registering component classes (ie relation classes) and passing the config
|
19
19
|
# to container builder function. This type is suitable when your environment
|
20
20
|
# is not typical and you need full control over component registration
|
21
|
-
# *
|
21
|
+
# * Explicit setup with auto-registration - same as explicit setup but allows
|
22
22
|
# you to configure auto-registration mechanism which will register component
|
23
23
|
# classes for you, based on dir/file naming conventions. This is the most
|
24
24
|
# common type of setup that's used by framework integrations
|
@@ -99,24 +99,20 @@ module ROM
|
|
99
99
|
class Container
|
100
100
|
include Dry::Equalizer(:gateways, :relations, :mappers, :commands)
|
101
101
|
|
102
|
-
#
|
103
|
-
#
|
104
|
-
# @api public
|
102
|
+
# @!attribute [r] gateways
|
103
|
+
# @return [Hash] A hash with configured gateways
|
105
104
|
attr_reader :gateways
|
106
105
|
|
107
|
-
#
|
108
|
-
#
|
109
|
-
# @api public
|
106
|
+
# @!attribute [r] relations
|
107
|
+
# @return [RelationRegistry] The relation registry
|
110
108
|
attr_reader :relations
|
111
109
|
|
112
|
-
#
|
113
|
-
#
|
114
|
-
# @api public
|
110
|
+
# @!attribute [r] gateways
|
111
|
+
# @return [CommandRegistry] The command registry
|
115
112
|
attr_reader :commands
|
116
113
|
|
117
|
-
#
|
118
|
-
#
|
119
|
-
# @api public
|
114
|
+
# @!attribute [r] mappers
|
115
|
+
# @return [Hash] A hash with configured custom mappers
|
120
116
|
attr_reader :mappers
|
121
117
|
|
122
118
|
# @api private
|
data/lib/rom/gateway.rb
CHANGED
@@ -5,49 +5,61 @@ require 'rom/transaction'
|
|
5
5
|
module ROM
|
6
6
|
# Abstract gateway class
|
7
7
|
#
|
8
|
+
# Every adapter needs to inherit from this class and implement
|
9
|
+
# required interface
|
10
|
+
#
|
11
|
+
# @abstract
|
12
|
+
#
|
8
13
|
# @api public
|
9
14
|
class Gateway
|
10
15
|
extend Dry::Core::ClassAttributes
|
11
16
|
|
12
17
|
defines :adapter
|
13
18
|
|
14
|
-
#
|
15
|
-
#
|
16
|
-
# @return [Object] type varies depending on the gateway
|
17
|
-
#
|
18
|
-
# @api public
|
19
|
+
# @!attribute [r] connection
|
20
|
+
# @return [Object] The gateway's connection object (type varies across adapters)
|
19
21
|
attr_reader :connection
|
20
22
|
|
21
|
-
#
|
23
|
+
# Set up a gateway
|
22
24
|
#
|
23
25
|
# @overload setup(type, *args)
|
24
26
|
# Sets up a single-gateway given a gateway type.
|
25
27
|
# For custom gateways, create an instance and pass it directly.
|
26
28
|
#
|
27
|
-
# @
|
28
|
-
#
|
29
|
+
# @example
|
30
|
+
# module SuperDB
|
31
|
+
# class Gateway < ROM::Gateway
|
32
|
+
# def initialize(options)
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
# end
|
29
36
|
#
|
30
|
-
#
|
31
|
-
# @param [Gateway] gateway
|
37
|
+
# ROM.register_adapter(:super_db, SuperDB)
|
32
38
|
#
|
33
|
-
#
|
39
|
+
# Gateway.setup(:super_db, some: 'options')
|
40
|
+
# # SuperDB::Gateway.new(some: 'options') is called
|
41
|
+
#
|
42
|
+
# @param [Symbol] type Registered gateway identifier
|
43
|
+
# @param [Array] args Additional gateway options
|
34
44
|
#
|
35
|
-
# @
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
45
|
+
# @overload setup(gateway)
|
46
|
+
# Set up a gateway instance
|
47
|
+
#
|
48
|
+
# @example
|
49
|
+
# module SuperDB
|
50
|
+
# class Gateway < ROM::Gateway
|
51
|
+
# def initialize(options)
|
52
|
+
# end
|
39
53
|
# end
|
40
54
|
# end
|
41
|
-
# end
|
42
55
|
#
|
43
|
-
#
|
56
|
+
# ROM.register_adapter(:super_db, SuperDB)
|
57
|
+
#
|
58
|
+
# Gateway.setup(SuperDB::Gateway.new(some: 'options'))
|
44
59
|
#
|
45
|
-
# Gateway
|
46
|
-
# # SuperDB::Gateway.new(some: 'options') is called
|
60
|
+
# @param [Gateway] gateway
|
47
61
|
#
|
48
|
-
#
|
49
|
-
# super_db = Gateway.setup(SuperDB::Gateway.new(some: 'options'))
|
50
|
-
# Gateway.setup(super_db)
|
62
|
+
# @return [Gateway] a specific gateway subclass
|
51
63
|
#
|
52
64
|
# @api public
|
53
65
|
def self.setup(gateway_or_scheme, *args)
|
@@ -76,7 +88,7 @@ module ROM
|
|
76
88
|
|
77
89
|
# Get gateway subclass for a specific adapter
|
78
90
|
#
|
79
|
-
# @param [Symbol] type
|
91
|
+
# @param [Symbol] type Adapter identifier
|
80
92
|
#
|
81
93
|
# @return [Class]
|
82
94
|
#
|
@@ -109,6 +121,10 @@ module ROM
|
|
109
121
|
|
110
122
|
# A generic interface for setting up a logger
|
111
123
|
#
|
124
|
+
# This is not a required interface, it's a no-op by default
|
125
|
+
#
|
126
|
+
# @abstract
|
127
|
+
#
|
112
128
|
# @api public
|
113
129
|
def use_logger(*)
|
114
130
|
# noop
|
@@ -116,6 +132,11 @@ module ROM
|
|
116
132
|
|
117
133
|
# A generic interface for returning default logger
|
118
134
|
#
|
135
|
+
# Adapters should implement this method as handling loggers is different
|
136
|
+
# across adapters. This is a no-op by default and returns nil.
|
137
|
+
#
|
138
|
+
# @return [NilClass]
|
139
|
+
#
|
119
140
|
# @api public
|
120
141
|
def logger
|
121
142
|
# noop
|
@@ -123,8 +144,10 @@ module ROM
|
|
123
144
|
|
124
145
|
# Extension hook for adding gateway-specific behavior to a command class
|
125
146
|
#
|
126
|
-
#
|
127
|
-
#
|
147
|
+
# This simply returns back the class by default
|
148
|
+
#
|
149
|
+
# @param [Class] klass The command class
|
150
|
+
# @param [Object] _dataset The dataset that will be used with this command class
|
128
151
|
#
|
129
152
|
# @return [Class]
|
130
153
|
#
|
@@ -137,7 +160,7 @@ module ROM
|
|
137
160
|
#
|
138
161
|
# Every gateway that supports schema inference should implement this method
|
139
162
|
#
|
140
|
-
# @return [Array] array with
|
163
|
+
# @return [Array] An array with dataset names
|
141
164
|
#
|
142
165
|
# @api private
|
143
166
|
def schema
|
@@ -163,6 +186,8 @@ module ROM
|
|
163
186
|
transaction_runner(opts).run(opts, &block)
|
164
187
|
end
|
165
188
|
|
189
|
+
private
|
190
|
+
|
166
191
|
# @api private
|
167
192
|
def transaction_runner(_)
|
168
193
|
Transaction::NoOp
|
data/lib/rom/relation.rb
CHANGED
@@ -18,18 +18,23 @@ module ROM
|
|
18
18
|
# Base relation class
|
19
19
|
#
|
20
20
|
# Relation is a proxy for the dataset object provided by the gateway. It
|
21
|
-
#
|
21
|
+
# can forward methods to the dataset, which is why the "native" interface of
|
22
22
|
# the underlying gateway is available in the relation. This interface,
|
23
23
|
# however, is considered private and should not be used outside of the
|
24
24
|
# relation instance.
|
25
25
|
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
26
|
+
# Individual adapters sets up their relation classes and provide different APIs
|
27
|
+
# depending on their persistence backend.
|
28
|
+
#
|
29
|
+
# Vanilla Relation class doesn't have APIs that are specific to ROM container setup.
|
30
|
+
# When adapter Relation class inherits from this class, these APIs are added automatically,
|
31
|
+
# so that they can be registered within a container.
|
32
|
+
#
|
33
|
+
# @see ROM::Relation::ClassInterface
|
30
34
|
#
|
31
35
|
# @api public
|
32
36
|
class Relation
|
37
|
+
# Default no-op output schema which is called in `Relation#each`
|
33
38
|
NOOP_OUTPUT_SCHEMA = -> tuple { tuple }.freeze
|
34
39
|
|
35
40
|
extend Initializer
|
@@ -71,6 +76,15 @@ module ROM
|
|
71
76
|
|
72
77
|
# Return schema attribute
|
73
78
|
#
|
79
|
+
# @example accessing canonical attribute
|
80
|
+
# users[:id]
|
81
|
+
# # => #<ROM::SQL::Attribute[Integer] primary_key=true name=:id source=ROM::Relation::Name(users)>
|
82
|
+
#
|
83
|
+
# @example accessing joined attribute
|
84
|
+
# tasks_with_users = tasks.join(users).select_append(tasks[:title])
|
85
|
+
# tasks_with_users[:title, :tasks]
|
86
|
+
# # => #<ROM::SQL::Attribute[String] primary_key=false name=:title source=ROM::Relation::Name(tasks)>
|
87
|
+
#
|
74
88
|
# @return [Schema::Attribute]
|
75
89
|
#
|
76
90
|
# @api public
|
@@ -80,7 +94,10 @@ module ROM
|
|
80
94
|
|
81
95
|
# Yields relation tuples
|
82
96
|
#
|
97
|
+
# Every tuple is processed through Relation#output_schema, it's a no-op by default
|
98
|
+
#
|
83
99
|
# @yield [Hash]
|
100
|
+
#
|
84
101
|
# @return [Enumerator] if block is not provided
|
85
102
|
#
|
86
103
|
# @api public
|
@@ -91,7 +108,7 @@ module ROM
|
|
91
108
|
|
92
109
|
# Composes with other relations
|
93
110
|
#
|
94
|
-
# @param
|
111
|
+
# @param [Array<Relation>] others The other relation(s) to compose with
|
95
112
|
#
|
96
113
|
# @return [Relation::Graph]
|
97
114
|
#
|
@@ -147,6 +164,18 @@ module ROM
|
|
147
164
|
|
148
165
|
# Return a new relation with provided dataset and additional options
|
149
166
|
#
|
167
|
+
# Use this method whenever you need to use dataset API to get a new dataset
|
168
|
+
# and you want to return a relation back. Typically relation API should be
|
169
|
+
# enough though. If you find yourself using this method, it might be worth
|
170
|
+
# to consider reporting an issue that some dataset functionality is not available
|
171
|
+
# through relation API.
|
172
|
+
#
|
173
|
+
# @example with a new dataset
|
174
|
+
# users.new(users.dataset.some_method)
|
175
|
+
#
|
176
|
+
# @example with a new dataset and options
|
177
|
+
# users.new(users.dataset.some_method, other: 'options')
|
178
|
+
#
|
150
179
|
# @param [Object] dataset
|
151
180
|
# @param [Hash] new_opts Additional options
|
152
181
|
#
|
@@ -157,6 +186,9 @@ module ROM
|
|
157
186
|
|
158
187
|
# Returns a new instance with the same dataset but new options
|
159
188
|
#
|
189
|
+
# @example
|
190
|
+
# users.with(output_schema: -> tuple { .. })
|
191
|
+
#
|
160
192
|
# @param new_options [Hash]
|
161
193
|
#
|
162
194
|
# @return [Relation]
|
@@ -168,6 +200,8 @@ module ROM
|
|
168
200
|
|
169
201
|
# Return all registered relation schemas
|
170
202
|
#
|
203
|
+
# This holds all schemas defined via `view` DSL
|
204
|
+
#
|
171
205
|
# @return [Hash<Symbol=>Schema>]
|
172
206
|
#
|
173
207
|
# @api public
|
@@ -186,6 +220,10 @@ module ROM
|
|
186
220
|
|
187
221
|
private
|
188
222
|
|
223
|
+
# Hook used by `Pipeline` to get the class that should be used for composition
|
224
|
+
#
|
225
|
+
# @return [Class]
|
226
|
+
#
|
189
227
|
# @api private
|
190
228
|
def composite_class
|
191
229
|
Relation::Composite
|
data/lib/rom/version.rb
CHANGED
@@ -94,7 +94,7 @@ RSpec.describe ROM::Commands::Create[:memory], 'before/after hooks' do
|
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
97
|
-
context 'with curried
|
97
|
+
context 'with one curried arg' do
|
98
98
|
subject(:command) do
|
99
99
|
Class.new(ROM::Commands::Create[:memory]) do
|
100
100
|
result :many
|
@@ -142,6 +142,54 @@ RSpec.describe ROM::Commands::Create[:memory], 'before/after hooks' do
|
|
142
142
|
end
|
143
143
|
end
|
144
144
|
|
145
|
+
context 'with 2 curried args' do
|
146
|
+
subject(:command) do
|
147
|
+
Class.new(ROM::Commands::Create[:memory]) do
|
148
|
+
result :many
|
149
|
+
before :prepare
|
150
|
+
after :finalize
|
151
|
+
|
152
|
+
def execute(tuples)
|
153
|
+
input = tuples.map.with_index { |tuple, idx| tuple.merge(id: idx + 1) }
|
154
|
+
relation.insert(input)
|
155
|
+
input
|
156
|
+
end
|
157
|
+
|
158
|
+
def prepare(tuples, name)
|
159
|
+
tuples.map.with_index { |tuple, idx| tuple.merge(name: "#{name} #{idx + 1}") }
|
160
|
+
end
|
161
|
+
|
162
|
+
def finalize(tuples, *)
|
163
|
+
tuples.map { |tuple| tuple.merge(finalized: true) }
|
164
|
+
end
|
165
|
+
end.build(relation)
|
166
|
+
end
|
167
|
+
|
168
|
+
let(:tuples) do
|
169
|
+
[{ email: 'user-1@test.com' }, { email: 'user-2@test.com' }]
|
170
|
+
end
|
171
|
+
|
172
|
+
let(:relation) do
|
173
|
+
spy(:relation)
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'applies before/after hooks' do
|
177
|
+
insert_tuples = [
|
178
|
+
{ id: 1, email: 'user-1@test.com', name: 'User 1' },
|
179
|
+
{ id: 2, email: 'user-2@test.com', name: 'User 2' }
|
180
|
+
]
|
181
|
+
|
182
|
+
result = [
|
183
|
+
{ id: 1, email: 'user-1@test.com', name: 'User 1', finalized: true },
|
184
|
+
{ id: 2, email: 'user-2@test.com', name: 'User 2', finalized: true }
|
185
|
+
]
|
186
|
+
|
187
|
+
expect(command.with(tuples, 'User').call).to eql(result)
|
188
|
+
|
189
|
+
expect(relation).to have_received(:insert).with(insert_tuples)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
145
193
|
context 'with pre-set opts' do
|
146
194
|
subject(:command) do
|
147
195
|
Class.new(ROM::Commands::Create[:memory]) do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rom
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0.0.
|
4
|
+
version: 3.0.0.rc2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piotr Solnica
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-01-
|
11
|
+
date: 2017-01-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|