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