rom-core 4.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +603 -0
  3. data/LICENSE +20 -0
  4. data/README.md +18 -0
  5. data/lib/rom-core.rb +1 -0
  6. data/lib/rom/array_dataset.rb +44 -0
  7. data/lib/rom/association_set.rb +16 -0
  8. data/lib/rom/associations/abstract.rb +135 -0
  9. data/lib/rom/associations/definitions.rb +5 -0
  10. data/lib/rom/associations/definitions/abstract.rb +116 -0
  11. data/lib/rom/associations/definitions/many_to_many.rb +24 -0
  12. data/lib/rom/associations/definitions/many_to_one.rb +11 -0
  13. data/lib/rom/associations/definitions/one_to_many.rb +11 -0
  14. data/lib/rom/associations/definitions/one_to_one.rb +11 -0
  15. data/lib/rom/associations/definitions/one_to_one_through.rb +11 -0
  16. data/lib/rom/associations/many_to_many.rb +81 -0
  17. data/lib/rom/associations/many_to_one.rb +37 -0
  18. data/lib/rom/associations/one_to_many.rb +37 -0
  19. data/lib/rom/associations/one_to_one.rb +8 -0
  20. data/lib/rom/associations/one_to_one_through.rb +8 -0
  21. data/lib/rom/associations/through_identifier.rb +39 -0
  22. data/lib/rom/auto_curry.rb +55 -0
  23. data/lib/rom/cache.rb +46 -0
  24. data/lib/rom/command.rb +488 -0
  25. data/lib/rom/command_compiler.rb +239 -0
  26. data/lib/rom/command_proxy.rb +24 -0
  27. data/lib/rom/command_registry.rb +141 -0
  28. data/lib/rom/commands.rb +3 -0
  29. data/lib/rom/commands/class_interface.rb +270 -0
  30. data/lib/rom/commands/composite.rb +53 -0
  31. data/lib/rom/commands/create.rb +13 -0
  32. data/lib/rom/commands/delete.rb +14 -0
  33. data/lib/rom/commands/graph.rb +88 -0
  34. data/lib/rom/commands/graph/class_interface.rb +62 -0
  35. data/lib/rom/commands/graph/input_evaluator.rb +62 -0
  36. data/lib/rom/commands/lazy.rb +99 -0
  37. data/lib/rom/commands/lazy/create.rb +23 -0
  38. data/lib/rom/commands/lazy/delete.rb +27 -0
  39. data/lib/rom/commands/lazy/update.rb +34 -0
  40. data/lib/rom/commands/result.rb +96 -0
  41. data/lib/rom/commands/update.rb +14 -0
  42. data/lib/rom/configuration.rb +114 -0
  43. data/lib/rom/configuration_dsl.rb +87 -0
  44. data/lib/rom/configuration_dsl/command.rb +41 -0
  45. data/lib/rom/configuration_dsl/command_dsl.rb +35 -0
  46. data/lib/rom/configuration_dsl/relation.rb +26 -0
  47. data/lib/rom/configuration_plugin.rb +17 -0
  48. data/lib/rom/constants.rb +64 -0
  49. data/lib/rom/container.rb +147 -0
  50. data/lib/rom/core.rb +46 -0
  51. data/lib/rom/create_container.rb +60 -0
  52. data/lib/rom/data_proxy.rb +94 -0
  53. data/lib/rom/enumerable_dataset.rb +68 -0
  54. data/lib/rom/environment.rb +70 -0
  55. data/lib/rom/gateway.rb +184 -0
  56. data/lib/rom/global.rb +58 -0
  57. data/lib/rom/global/plugin_dsl.rb +47 -0
  58. data/lib/rom/initializer.rb +64 -0
  59. data/lib/rom/lint/enumerable_dataset.rb +54 -0
  60. data/lib/rom/lint/gateway.rb +120 -0
  61. data/lib/rom/lint/linter.rb +78 -0
  62. data/lib/rom/lint/spec.rb +20 -0
  63. data/lib/rom/lint/test.rb +98 -0
  64. data/lib/rom/mapper_registry.rb +24 -0
  65. data/lib/rom/memory.rb +4 -0
  66. data/lib/rom/memory/associations.rb +4 -0
  67. data/lib/rom/memory/associations/many_to_many.rb +10 -0
  68. data/lib/rom/memory/associations/many_to_one.rb +10 -0
  69. data/lib/rom/memory/associations/one_to_many.rb +10 -0
  70. data/lib/rom/memory/associations/one_to_one.rb +10 -0
  71. data/lib/rom/memory/commands.rb +56 -0
  72. data/lib/rom/memory/dataset.rb +97 -0
  73. data/lib/rom/memory/gateway.rb +64 -0
  74. data/lib/rom/memory/relation.rb +62 -0
  75. data/lib/rom/memory/schema.rb +23 -0
  76. data/lib/rom/memory/storage.rb +59 -0
  77. data/lib/rom/memory/types.rb +9 -0
  78. data/lib/rom/pipeline.rb +105 -0
  79. data/lib/rom/plugin.rb +25 -0
  80. data/lib/rom/plugin_base.rb +45 -0
  81. data/lib/rom/plugin_registry.rb +197 -0
  82. data/lib/rom/plugins/command/schema.rb +37 -0
  83. data/lib/rom/plugins/configuration/configuration_dsl.rb +21 -0
  84. data/lib/rom/plugins/relation/instrumentation.rb +51 -0
  85. data/lib/rom/plugins/relation/registry_reader.rb +44 -0
  86. data/lib/rom/plugins/schema/timestamps.rb +58 -0
  87. data/lib/rom/registry.rb +71 -0
  88. data/lib/rom/relation.rb +548 -0
  89. data/lib/rom/relation/class_interface.rb +282 -0
  90. data/lib/rom/relation/commands.rb +23 -0
  91. data/lib/rom/relation/composite.rb +46 -0
  92. data/lib/rom/relation/curried.rb +103 -0
  93. data/lib/rom/relation/graph.rb +197 -0
  94. data/lib/rom/relation/loaded.rb +127 -0
  95. data/lib/rom/relation/materializable.rb +66 -0
  96. data/lib/rom/relation/name.rb +111 -0
  97. data/lib/rom/relation/view_dsl.rb +64 -0
  98. data/lib/rom/relation/wrap.rb +83 -0
  99. data/lib/rom/relation_registry.rb +10 -0
  100. data/lib/rom/schema.rb +437 -0
  101. data/lib/rom/schema/associations_dsl.rb +195 -0
  102. data/lib/rom/schema/attribute.rb +419 -0
  103. data/lib/rom/schema/dsl.rb +164 -0
  104. data/lib/rom/schema/inferrer.rb +66 -0
  105. data/lib/rom/schema_plugin.rb +27 -0
  106. data/lib/rom/setup.rb +68 -0
  107. data/lib/rom/setup/auto_registration.rb +74 -0
  108. data/lib/rom/setup/auto_registration_strategies/base.rb +16 -0
  109. data/lib/rom/setup/auto_registration_strategies/custom_namespace.rb +63 -0
  110. data/lib/rom/setup/auto_registration_strategies/no_namespace.rb +20 -0
  111. data/lib/rom/setup/auto_registration_strategies/with_namespace.rb +18 -0
  112. data/lib/rom/setup/finalize.rb +103 -0
  113. data/lib/rom/setup/finalize/finalize_commands.rb +60 -0
  114. data/lib/rom/setup/finalize/finalize_mappers.rb +56 -0
  115. data/lib/rom/setup/finalize/finalize_relations.rb +135 -0
  116. data/lib/rom/support/configurable.rb +85 -0
  117. data/lib/rom/support/memoizable.rb +58 -0
  118. data/lib/rom/support/notifications.rb +103 -0
  119. data/lib/rom/transaction.rb +24 -0
  120. data/lib/rom/types.rb +26 -0
  121. data/lib/rom/version.rb +5 -0
  122. metadata +289 -0
@@ -0,0 +1,81 @@
1
+ require 'rom/types'
2
+ require 'rom/associations/abstract'
3
+
4
+ module ROM
5
+ module Associations
6
+ class ManyToMany < Abstract
7
+ attr_reader :join_relation
8
+
9
+ # @api private
10
+ def initialize(*)
11
+ super
12
+ @join_relation = relations[through]
13
+ end
14
+
15
+ # @api public
16
+ def call(*)
17
+ raise NotImplementedError
18
+ end
19
+
20
+ # @api public
21
+ def foreign_key
22
+ definition.foreign_key || join_relation.foreign_key(source.name)
23
+ end
24
+
25
+ # @api public
26
+ def through
27
+ definition.through
28
+ end
29
+
30
+ # @api private
31
+ def parent_combine_keys
32
+ target.associations[source.name].combine_keys.to_a.flatten(1)
33
+ end
34
+
35
+ # @api private
36
+ def associate(children, parent)
37
+ ((spk, sfk), (tfk, tpk)) = join_key_map
38
+
39
+ case parent
40
+ when Array
41
+ parent.map { |p| associate(children, p) }.flatten(1)
42
+ else
43
+ children.map { |tuple|
44
+ { sfk => tuple.fetch(spk), tfk => parent.fetch(tpk) }
45
+ }
46
+ end
47
+ end
48
+
49
+ protected
50
+
51
+ # @api protected
52
+ def source_key
53
+ source.primary_key
54
+ end
55
+
56
+ # @api protected
57
+ def target_key
58
+ foreign_key
59
+ end
60
+
61
+ # @api protected
62
+ def join_assoc
63
+ if join_relation.associations.key?(through.assoc_name)
64
+ join_relation.associations[through.assoc_name]
65
+ else
66
+ join_relation.associations[through.target]
67
+ end
68
+ end
69
+
70
+ # @api protected
71
+ def join_key_map
72
+ left = super
73
+ right = join_assoc.join_key_map
74
+
75
+ [left, right]
76
+ end
77
+
78
+ memoize :foreign_key, :join_assoc, :join_key_map
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,37 @@
1
+ require 'rom/associations/abstract'
2
+
3
+ module ROM
4
+ module Associations
5
+ class ManyToOne < Abstract
6
+ # @api public
7
+ def call(*)
8
+ raise NotImplementedError
9
+ end
10
+
11
+ # @api public
12
+ def foreign_key
13
+ definition.foreign_key || source.foreign_key(target.name)
14
+ end
15
+
16
+ # @api private
17
+ def associate(child, parent)
18
+ fk, pk = join_key_map
19
+ child.merge(fk => parent.fetch(pk))
20
+ end
21
+
22
+ protected
23
+
24
+ # @api protected
25
+ def source_key
26
+ foreign_key
27
+ end
28
+
29
+ # @api protected
30
+ def target_key
31
+ target.schema.primary_key_name
32
+ end
33
+
34
+ memoize :foreign_key
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ require 'rom/associations/abstract'
2
+
3
+ module ROM
4
+ module Associations
5
+ class OneToMany < Abstract
6
+ # @api public
7
+ def call(*)
8
+ raise NotImplementedError
9
+ end
10
+
11
+ # @api public
12
+ def foreign_key
13
+ definition.foreign_key || target.foreign_key(source.name)
14
+ end
15
+
16
+ # @api private
17
+ def associate(child, parent)
18
+ pk, fk = join_key_map
19
+ child.merge(fk => parent.fetch(pk))
20
+ end
21
+
22
+ protected
23
+
24
+ # @api protected
25
+ def source_key
26
+ source.schema.primary_key_name
27
+ end
28
+
29
+ # @api protected
30
+ def target_key
31
+ foreign_key
32
+ end
33
+
34
+ memoize :foreign_key
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,8 @@
1
+ require 'rom/associations/one_to_many'
2
+
3
+ module ROM
4
+ module Associations
5
+ class OneToOne < OneToMany
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ require 'rom/associations/many_to_many'
2
+
3
+ module ROM
4
+ module Associations
5
+ class OneToOneThrough < ManyToMany
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,39 @@
1
+ require 'dry/core/inflector'
2
+
3
+ module ROM
4
+ module Associations
5
+ # @api private
6
+ class ThroughIdentifier
7
+ # @api private
8
+ attr_reader :source
9
+
10
+ # @api private
11
+ attr_reader :target
12
+
13
+ # @api private
14
+ attr_reader :assoc_name
15
+
16
+ # @api private
17
+ def self.[](source, target, assoc_name = nil)
18
+ new(source, target, assoc_name || default_assoc_name(target))
19
+ end
20
+
21
+ # @api private
22
+ def self.default_assoc_name(relation)
23
+ Dry::Core::Inflector.singularize(relation).to_sym
24
+ end
25
+
26
+ # @api private
27
+ def initialize(source, target, assoc_name)
28
+ @source = source
29
+ @target = target
30
+ @assoc_name = assoc_name
31
+ end
32
+
33
+ # @api private
34
+ def to_sym
35
+ source
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,55 @@
1
+ module ROM
2
+ module AutoCurry
3
+ def self.extended(klass)
4
+ klass.define_singleton_method(:method_added) do |name|
5
+ return if auto_curry_busy?
6
+ auto_curry_guard { auto_curry(name) }
7
+ super(name)
8
+ end
9
+ end
10
+
11
+ def auto_curry_guard
12
+ @__auto_curry_busy__ = true
13
+ yield
14
+ ensure
15
+ @__auto_curry_busy__ = false
16
+ end
17
+
18
+ def auto_curry_busy?
19
+ @__auto_curry_busy__ ||= false
20
+ end
21
+
22
+ def auto_curried_methods
23
+ @__auto_curried_methods__ ||= []
24
+ end
25
+
26
+ def auto_curry(name, &block)
27
+ arity = instance_method(name).arity
28
+
29
+ return unless public_instance_methods.include?(name) && arity != 0
30
+
31
+ mod = Module.new
32
+
33
+ mod.module_eval do
34
+ define_method(name) do |*args, &mblock|
35
+ response =
36
+ if arity < 0 || arity == args.size
37
+ super(*args, &mblock)
38
+ else
39
+ self.class.curried.new(self, view: name, curry_args: args, arity: arity)
40
+ end
41
+
42
+ if block
43
+ response.instance_exec(&block)
44
+ else
45
+ response
46
+ end
47
+ end
48
+ end
49
+
50
+ auto_curried_methods << name
51
+
52
+ prepend(mod)
53
+ end
54
+ end
55
+ end
data/lib/rom/cache.rb ADDED
@@ -0,0 +1,46 @@
1
+ require 'concurrent/map'
2
+
3
+ module ROM
4
+ # @api private
5
+ class Cache
6
+ attr_reader :objects
7
+
8
+ class Namespaced
9
+ attr_reader :cache
10
+
11
+ attr_reader :namespace
12
+
13
+ def initialize(cache, namespace)
14
+ @cache = cache
15
+ @namespace = namespace.to_sym
16
+ end
17
+
18
+ def [](key)
19
+ cache[[namespace, key].hash]
20
+ end
21
+
22
+ def fetch_or_store(*args, &block)
23
+ cache.fetch_or_store([namespace, args.hash].hash, &block)
24
+ end
25
+ end
26
+
27
+ # @api private
28
+ def initialize
29
+ @objects = Concurrent::Map.new
30
+ @namespaced = {}
31
+ end
32
+
33
+ def [](key)
34
+ cache[key]
35
+ end
36
+
37
+ # @api private
38
+ def fetch_or_store(*args, &block)
39
+ objects.fetch_or_store(args.hash, &block)
40
+ end
41
+
42
+ def namespaced(namespace)
43
+ @namespaced[namespace] ||= Namespaced.new(objects, namespace)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,488 @@
1
+ require 'dry/core/deprecations'
2
+ require 'dry/core/class_attributes'
3
+
4
+ require 'rom/types'
5
+ require 'rom/initializer'
6
+ require 'rom/pipeline'
7
+
8
+ require 'rom/commands/class_interface'
9
+ require 'rom/commands/composite'
10
+ require 'rom/commands/graph'
11
+ require 'rom/commands/lazy'
12
+
13
+ module ROM
14
+ # Abstract command class
15
+ #
16
+ # Provides a constructor accepting relation with options and basic behavior
17
+ # for calling, currying and composing commands.
18
+ #
19
+ # Typically command subclasses should inherit from specialized
20
+ # Create/Update/Delete, not this one.
21
+ #
22
+ # @abstract
23
+ #
24
+ # @api public
25
+ class Command
26
+ extend Initializer
27
+ include Dry::Equalizer(:relation, :options)
28
+ include Commands
29
+ include Pipeline::Operator
30
+
31
+ extend Dry::Core::ClassAttributes
32
+ extend ClassInterface
33
+
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
59
+
60
+ # @!method self.relation
61
+ # Get or set relation identifier
62
+ #
63
+ # @overload relation
64
+ # Get relation identifier
65
+ #
66
+ # @example
67
+ # class 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
+ # class 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
+ # class 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
+ # class 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
+ # class 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
+ # class 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
+ # class 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
+ # class 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
+ # class 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
+ # class 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
196
+ param :relation
197
+
198
+ CommandType = Types::Strict::Symbol.enum(:create, :update, :delete)
199
+ Result = Types::Strict::Symbol.enum(:one, :many)
200
+
201
+ # @!attribute [r] type
202
+ # @return [Symbol] The command type, one of :create, :update or :delete
203
+ option :type, type: CommandType, optional: true
204
+
205
+ # @!attribute [r] source
206
+ # @return [Relation] The source relation
207
+ option :source, default: -> { relation }
208
+
209
+ # @!attribute [r] result
210
+ # @return [Symbol] Result type, either :one or :many
211
+ option :result, type: Result
212
+
213
+ # @!attribute [r] input
214
+ # @return [Proc, #call] Tuple processing function, typically uses Relation#input_schema
215
+ option :input
216
+
217
+ # @!attribute [r] curry_args
218
+ # @return [Array] Curried args
219
+ option :curry_args, default: -> { EMPTY_ARRAY }
220
+
221
+ # @!attribute [r] before
222
+ # @return [Array<Hash>] An array with before hooks configuration
223
+ option :before, Types::Coercible::Array, reader: false, default: -> { self.class.before }
224
+
225
+ # @!attribute [r] after
226
+ # @return [Array<Hash>] An array with after hooks configuration
227
+ option :after, Types::Coercible::Array, reader: false, default: -> { self.class.after }
228
+
229
+ input Hash
230
+ result :many
231
+
232
+ # Return name of this command's relation
233
+ #
234
+ # @return [ROM::Relation::Name]
235
+ #
236
+ # @api public
237
+ def name
238
+ relation.name
239
+ end
240
+
241
+ # Return gateway of this command's relation
242
+ #
243
+ # @return [Symbol]
244
+ #
245
+ # @api public
246
+ def gateway
247
+ relation.gateway
248
+ end
249
+
250
+ # Execute the command
251
+ #
252
+ # @abstract
253
+ #
254
+ # @return [Array] an array with inserted tuples
255
+ #
256
+ # @api private
257
+ def execute(*)
258
+ raise(
259
+ NotImplementedError,
260
+ "#{self.class}##{__method__} must be implemented"
261
+ )
262
+ end
263
+
264
+ # Call the command and return one or many tuples
265
+ #
266
+ # This method will apply before/after hooks automatically
267
+ #
268
+ # @api public
269
+ def call(*args, &block)
270
+ tuples =
271
+ if hooks?
272
+ prepared =
273
+ if curried?
274
+ apply_hooks(before_hooks, *(curry_args + args))
275
+ else
276
+ apply_hooks(before_hooks, *args)
277
+ end
278
+
279
+ result = prepared ? execute(prepared, &block) : execute(&block)
280
+
281
+ if curried?
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
289
+ else
290
+ apply_hooks(after_hooks, result, *args[1..args.size-1])
291
+ end
292
+ else
293
+ execute(*(curry_args + args), &block)
294
+ end
295
+
296
+ if one?
297
+ tuples.first
298
+ else
299
+ tuples
300
+ end
301
+ end
302
+ alias_method :[], :call
303
+
304
+ # Curry this command with provided args
305
+ #
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.
308
+ #
309
+ # @return [Command, Lazy]
310
+ #
311
+ # @api public
312
+ def curry(*args)
313
+ if curry_args.empty? && args.first.is_a?(Graph::InputEvaluator)
314
+ Lazy[self].new(self, *args)
315
+ else
316
+ self.class.build(relation, **options, curry_args: args)
317
+ end
318
+ end
319
+ alias_method :with, :curry
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
+ #
336
+ # @api public
337
+ def curried?
338
+ curry_args.size > 0
339
+ end
340
+
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))
350
+ end
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
+ #
358
+ # @api public
359
+ def before(*hooks)
360
+ self.class.new(relation, **options, before: before_hooks + hooks)
361
+ end
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, after: after_hooks + hooks)
372
+ end
373
+
374
+ # List of before hooks
375
+ #
376
+ # @return [Array]
377
+ #
378
+ # @api public
379
+ def before_hooks
380
+ options[:before]
381
+ end
382
+
383
+ # List of after hooks
384
+ #
385
+ # @return [Array]
386
+ #
387
+ # @api public
388
+ def after_hooks
389
+ options[:after]
390
+ end
391
+
392
+ # Return a new command with other source relation
393
+ #
394
+ # This can be used to restrict command with a specific relation
395
+ #
396
+ # @return [Command]
397
+ #
398
+ # @api public
399
+ def new(new_relation)
400
+ self.class.build(new_relation, **options, source: relation)
401
+ end
402
+
403
+ # Check if this command has any hooks
404
+ #
405
+ # @api private
406
+ def hooks?
407
+ before_hooks.size > 0 || after_hooks.size > 0
408
+ end
409
+
410
+ # Check if this command is lazy
411
+ #
412
+ # @return [false]
413
+ #
414
+ # @api private
415
+ def lazy?
416
+ false
417
+ end
418
+
419
+ # Check if this command is a graph
420
+ #
421
+ # @return [false]
422
+ #
423
+ # @api private
424
+ def graph?
425
+ false
426
+ end
427
+
428
+ # Check if this command returns a single tuple
429
+ #
430
+ # @return [TrueClass,FalseClass]
431
+ #
432
+ # @api private
433
+ def one?
434
+ result.equal?(:one)
435
+ end
436
+
437
+ # Check if this command returns many tuples
438
+ #
439
+ # @return [TrueClass,FalseClass]
440
+ #
441
+ # @api private
442
+ def many?
443
+ result.equal?(:many)
444
+ end
445
+
446
+ private
447
+
448
+ # Hook called by Pipeline to get composite class for commands
449
+ #
450
+ # @return [Class]
451
+ #
452
+ # @api private
453
+ def composite_class
454
+ Command::Composite
455
+ end
456
+
457
+ # Apply provided hooks
458
+ #
459
+ # Used by #call
460
+ #
461
+ # @return [Array<Hash>]
462
+ #
463
+ # @api private
464
+ def apply_hooks(hooks, tuples, *args)
465
+ hooks.reduce(tuples) do |a, e|
466
+ if e.is_a?(Hash)
467
+ hook_meth, hook_args = e.to_a.flatten(1)
468
+ __send__(hook_meth, a, *args, **hook_args)
469
+ else
470
+ __send__(e, a, *args)
471
+ end
472
+ end
473
+ end
474
+
475
+ # Pipes a dataset through command's relation
476
+ #
477
+ # @return [Array]
478
+ #
479
+ # @api private
480
+ def wrap_dataset(dataset)
481
+ if relation.is_a?(Relation::Composite)
482
+ relation.new(dataset).to_a
483
+ else
484
+ dataset
485
+ end
486
+ end
487
+ end
488
+ end