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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +603 -0
- data/LICENSE +20 -0
- data/README.md +18 -0
- data/lib/rom-core.rb +1 -0
- data/lib/rom/array_dataset.rb +44 -0
- data/lib/rom/association_set.rb +16 -0
- data/lib/rom/associations/abstract.rb +135 -0
- data/lib/rom/associations/definitions.rb +5 -0
- data/lib/rom/associations/definitions/abstract.rb +116 -0
- data/lib/rom/associations/definitions/many_to_many.rb +24 -0
- data/lib/rom/associations/definitions/many_to_one.rb +11 -0
- data/lib/rom/associations/definitions/one_to_many.rb +11 -0
- data/lib/rom/associations/definitions/one_to_one.rb +11 -0
- data/lib/rom/associations/definitions/one_to_one_through.rb +11 -0
- data/lib/rom/associations/many_to_many.rb +81 -0
- data/lib/rom/associations/many_to_one.rb +37 -0
- data/lib/rom/associations/one_to_many.rb +37 -0
- data/lib/rom/associations/one_to_one.rb +8 -0
- data/lib/rom/associations/one_to_one_through.rb +8 -0
- data/lib/rom/associations/through_identifier.rb +39 -0
- data/lib/rom/auto_curry.rb +55 -0
- data/lib/rom/cache.rb +46 -0
- data/lib/rom/command.rb +488 -0
- data/lib/rom/command_compiler.rb +239 -0
- data/lib/rom/command_proxy.rb +24 -0
- data/lib/rom/command_registry.rb +141 -0
- data/lib/rom/commands.rb +3 -0
- data/lib/rom/commands/class_interface.rb +270 -0
- data/lib/rom/commands/composite.rb +53 -0
- data/lib/rom/commands/create.rb +13 -0
- data/lib/rom/commands/delete.rb +14 -0
- data/lib/rom/commands/graph.rb +88 -0
- data/lib/rom/commands/graph/class_interface.rb +62 -0
- data/lib/rom/commands/graph/input_evaluator.rb +62 -0
- data/lib/rom/commands/lazy.rb +99 -0
- data/lib/rom/commands/lazy/create.rb +23 -0
- data/lib/rom/commands/lazy/delete.rb +27 -0
- data/lib/rom/commands/lazy/update.rb +34 -0
- data/lib/rom/commands/result.rb +96 -0
- data/lib/rom/commands/update.rb +14 -0
- data/lib/rom/configuration.rb +114 -0
- data/lib/rom/configuration_dsl.rb +87 -0
- data/lib/rom/configuration_dsl/command.rb +41 -0
- data/lib/rom/configuration_dsl/command_dsl.rb +35 -0
- data/lib/rom/configuration_dsl/relation.rb +26 -0
- data/lib/rom/configuration_plugin.rb +17 -0
- data/lib/rom/constants.rb +64 -0
- data/lib/rom/container.rb +147 -0
- data/lib/rom/core.rb +46 -0
- data/lib/rom/create_container.rb +60 -0
- data/lib/rom/data_proxy.rb +94 -0
- data/lib/rom/enumerable_dataset.rb +68 -0
- data/lib/rom/environment.rb +70 -0
- data/lib/rom/gateway.rb +184 -0
- data/lib/rom/global.rb +58 -0
- data/lib/rom/global/plugin_dsl.rb +47 -0
- data/lib/rom/initializer.rb +64 -0
- data/lib/rom/lint/enumerable_dataset.rb +54 -0
- data/lib/rom/lint/gateway.rb +120 -0
- data/lib/rom/lint/linter.rb +78 -0
- data/lib/rom/lint/spec.rb +20 -0
- data/lib/rom/lint/test.rb +98 -0
- data/lib/rom/mapper_registry.rb +24 -0
- data/lib/rom/memory.rb +4 -0
- data/lib/rom/memory/associations.rb +4 -0
- data/lib/rom/memory/associations/many_to_many.rb +10 -0
- data/lib/rom/memory/associations/many_to_one.rb +10 -0
- data/lib/rom/memory/associations/one_to_many.rb +10 -0
- data/lib/rom/memory/associations/one_to_one.rb +10 -0
- data/lib/rom/memory/commands.rb +56 -0
- data/lib/rom/memory/dataset.rb +97 -0
- data/lib/rom/memory/gateway.rb +64 -0
- data/lib/rom/memory/relation.rb +62 -0
- data/lib/rom/memory/schema.rb +23 -0
- data/lib/rom/memory/storage.rb +59 -0
- data/lib/rom/memory/types.rb +9 -0
- data/lib/rom/pipeline.rb +105 -0
- data/lib/rom/plugin.rb +25 -0
- data/lib/rom/plugin_base.rb +45 -0
- data/lib/rom/plugin_registry.rb +197 -0
- data/lib/rom/plugins/command/schema.rb +37 -0
- data/lib/rom/plugins/configuration/configuration_dsl.rb +21 -0
- data/lib/rom/plugins/relation/instrumentation.rb +51 -0
- data/lib/rom/plugins/relation/registry_reader.rb +44 -0
- data/lib/rom/plugins/schema/timestamps.rb +58 -0
- data/lib/rom/registry.rb +71 -0
- data/lib/rom/relation.rb +548 -0
- data/lib/rom/relation/class_interface.rb +282 -0
- data/lib/rom/relation/commands.rb +23 -0
- data/lib/rom/relation/composite.rb +46 -0
- data/lib/rom/relation/curried.rb +103 -0
- data/lib/rom/relation/graph.rb +197 -0
- data/lib/rom/relation/loaded.rb +127 -0
- data/lib/rom/relation/materializable.rb +66 -0
- data/lib/rom/relation/name.rb +111 -0
- data/lib/rom/relation/view_dsl.rb +64 -0
- data/lib/rom/relation/wrap.rb +83 -0
- data/lib/rom/relation_registry.rb +10 -0
- data/lib/rom/schema.rb +437 -0
- data/lib/rom/schema/associations_dsl.rb +195 -0
- data/lib/rom/schema/attribute.rb +419 -0
- data/lib/rom/schema/dsl.rb +164 -0
- data/lib/rom/schema/inferrer.rb +66 -0
- data/lib/rom/schema_plugin.rb +27 -0
- data/lib/rom/setup.rb +68 -0
- data/lib/rom/setup/auto_registration.rb +74 -0
- data/lib/rom/setup/auto_registration_strategies/base.rb +16 -0
- data/lib/rom/setup/auto_registration_strategies/custom_namespace.rb +63 -0
- data/lib/rom/setup/auto_registration_strategies/no_namespace.rb +20 -0
- data/lib/rom/setup/auto_registration_strategies/with_namespace.rb +18 -0
- data/lib/rom/setup/finalize.rb +103 -0
- data/lib/rom/setup/finalize/finalize_commands.rb +60 -0
- data/lib/rom/setup/finalize/finalize_mappers.rb +56 -0
- data/lib/rom/setup/finalize/finalize_relations.rb +135 -0
- data/lib/rom/support/configurable.rb +85 -0
- data/lib/rom/support/memoizable.rb +58 -0
- data/lib/rom/support/notifications.rb +103 -0
- data/lib/rom/transaction.rb +24 -0
- data/lib/rom/types.rb +26 -0
- data/lib/rom/version.rb +5 -0
- 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,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
|
data/lib/rom/command.rb
ADDED
@@ -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
|