rom-core 4.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|