rom-core 4.0.0.beta1

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