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,239 @@
1
+ require 'dry/core/inflector'
2
+ require 'dry/core/cache'
3
+
4
+ require 'rom/initializer'
5
+ require 'rom/commands'
6
+ require 'rom/command_proxy'
7
+
8
+ module ROM
9
+ # Builds commands for relations.
10
+ #
11
+ # This class is used by repositories to automatically create commands for
12
+ # their relations. This is used both by `Repository#command` method and
13
+ # `commands` repository class macros.
14
+ #
15
+ # @api private
16
+ class CommandCompiler
17
+ extend Dry::Core::Cache
18
+ extend Initializer
19
+
20
+ # @api private
21
+ def self.registry
22
+ @__registry__ ||= Hash.new { |h, k| h[k] = {} }
23
+ end
24
+
25
+ # @!attribute [r] gateways
26
+ # @return [ROM::Registry]
27
+ param :gateways
28
+
29
+ # @!attribute [r] relations
30
+ # @return [ROM::RelationRegistry]
31
+ param :relations
32
+
33
+ # @!attribute [r] relations
34
+ # @return [ROM::Registry]
35
+ param :commands
36
+
37
+ # @!attribute [r] notifications
38
+ # @return [Notifications::EventBus] Configuration notifications event bus
39
+ param :notifications
40
+
41
+ # @!attribute [r] id
42
+ # @return [Symbol] The command type registry identifier
43
+ option :id, optional: true
44
+
45
+ # @!attribute [r] adapter
46
+ # @return [Symbol] The adapter identifier ie :sql or :http
47
+ option :adapter, optional: true
48
+
49
+ # @!attribute [r] registry
50
+ # @return [Hash] local registry where commands will be stored during compilation
51
+ option :registry, optional: true, default: -> { self.class.registry }
52
+
53
+ # @!attribute [r] plugins
54
+ # @return [Array<Symbol>] a list of optional plugins that will be enabled for commands
55
+ option :plugins, optional: true, default: -> { EMPTY_ARRAY }
56
+
57
+ # @!attribute [r] meta
58
+ # @return [Array<Symbol>] Meta data for a command
59
+ option :meta, optional: true
60
+
61
+ # Return a specific command type for a given adapter and relation AST
62
+ #
63
+ # This class holds its own registry where all generated commands are being
64
+ # stored
65
+ #
66
+ # CommandProxy is returned for complex command graphs as they expect root
67
+ # relation name to be present in the input, which we don't want to have
68
+ # in repositories. It might be worth looking into removing this requirement
69
+ # from rom core Command::Graph API.
70
+ #
71
+ # @overload [](type, adapter, ast, plugins, meta)
72
+ # @param type [Symbol] The type of command
73
+ # @param adapter [Symbol] The adapter identifier
74
+ # @param ast [Array] The AST representation of a relation
75
+ # @param plugins [Array<Symbol>] A list of optional command plugins that should be used
76
+ # @param meta [Hash] Meta data for a command
77
+ #
78
+ # @return [Command, CommandProxy]
79
+ #
80
+ # @api private
81
+ def call(*args)
82
+ fetch_or_store(args.hash) do
83
+ type, adapter, ast, plugins, meta = args
84
+
85
+ compiler = with(
86
+ id: type,
87
+ adapter: adapter,
88
+ plugins: Array(plugins),
89
+ meta: meta
90
+ )
91
+
92
+ graph_opts = compiler.visit(ast)
93
+ command = ROM::Commands::Graph.build(registry, graph_opts)
94
+
95
+ if command.graph?
96
+ CommandProxy.new(command)
97
+ elsif command.lazy?
98
+ command.unwrap
99
+ else
100
+ command
101
+ end
102
+ end
103
+ end
104
+ alias_method :[], :call
105
+
106
+ # @api private
107
+ def type
108
+ @_type ||= Commands.const_get(Dry::Core::Inflector.classify(id))[adapter]
109
+ rescue NameError
110
+ nil
111
+ end
112
+
113
+ # @api private
114
+ def visit(ast, *args)
115
+ name, node = ast
116
+ __send__(:"visit_#{name}", node, *args)
117
+ end
118
+
119
+ private
120
+
121
+ # @api private
122
+ def visit_relation(node, parent_relation = nil)
123
+ name, header, meta = node
124
+ other = header.map { |attr| visit(attr, name) }.compact
125
+
126
+ if type
127
+ register_command(name, type, meta, parent_relation)
128
+
129
+ default_mapping =
130
+ if meta[:combine_type] == :many
131
+ name
132
+ else
133
+ { Dry::Core::Inflector.singularize(name).to_sym => name }
134
+ end
135
+
136
+ mapping =
137
+ if parent_relation
138
+ associations = relations[parent_relation].associations
139
+
140
+ assoc = associations[meta[:combine_name]]
141
+
142
+ if assoc
143
+ { assoc.key => assoc.target.name.to_sym }
144
+ else
145
+ default_mapping
146
+ end
147
+ else
148
+ default_mapping
149
+ end
150
+
151
+ if other.size > 0
152
+ [mapping, [type, other]]
153
+ else
154
+ [mapping, type]
155
+ end
156
+ else
157
+ registry[name][id] = commands[name][id]
158
+ [name, id]
159
+ end
160
+ end
161
+
162
+ # @api private
163
+ def visit_attribute(*args)
164
+ nil
165
+ end
166
+
167
+ # Build a command object for a specific relation
168
+ #
169
+ # The command will be prepared for handling associations if it's a combined
170
+ # relation. Additional plugins will be enabled if they are configured for
171
+ # this compiler.
172
+ #
173
+ # @param [Symbol] rel_name A relation identifier from the container registry
174
+ # @param [Symbol] type The command type
175
+ # @param [Hash] meta Meta information from relation AST
176
+ # @param [Symbol] parent_relation Optional parent relation identifier
177
+ #
178
+ # @return [ROM::Command]
179
+ #
180
+ # @api private
181
+ def register_command(rel_name, type, meta, parent_relation = nil)
182
+ relation = relations[rel_name]
183
+
184
+ type.create_class(rel_name, type) do |klass|
185
+ klass.result(meta.fetch(:combine_type, result))
186
+
187
+ if meta[:combine_type]
188
+ setup_associates(klass, relation, meta, parent_relation)
189
+ end
190
+
191
+ plugins.each do |plugin|
192
+ klass.use(plugin)
193
+ end
194
+
195
+ gateway = gateways[relation.gateway]
196
+
197
+ notifications.trigger(
198
+ 'configuration.commands.class.before_build',
199
+ command: klass, gateway: gateway, dataset: relation.dataset
200
+ )
201
+
202
+ klass.extend_for_relation(relation) if klass.restrictable
203
+
204
+ registry[rel_name][type] = klass.build(relation, input: relation.input_schema)
205
+ end
206
+ end
207
+
208
+ # Return default result type
209
+ #
210
+ # @return [Symbol]
211
+ #
212
+ # @api private
213
+ def result
214
+ meta.fetch(:result, :one)
215
+ end
216
+
217
+ # Sets up `associates` plugin for a given command class and relation
218
+ #
219
+ # @param [Class] klass The command class
220
+ # @param [Relation] relation The relation for the command
221
+ #
222
+ # @api private
223
+ def setup_associates(klass, relation, meta, parent_relation)
224
+ assoc_name =
225
+ if relation.associations.key?(parent_relation)
226
+ parent_relation
227
+ else
228
+ singular_name = Dry::Core::Inflector.singularize(parent_relation).to_sym
229
+ singular_name if relation.associations.key?(singular_name)
230
+ end
231
+
232
+ if assoc_name
233
+ klass.associates(assoc_name)
234
+ else
235
+ klass.associates(parent_relation)
236
+ end
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,24 @@
1
+ require 'dry/core/inflector'
2
+
3
+ module ROM
4
+ # TODO: look into making command graphs work without the root key in the input
5
+ # so that we can get rid of this wrapper
6
+ #
7
+ # @api private
8
+ class CommandProxy
9
+ attr_reader :command, :root
10
+
11
+ def initialize(command)
12
+ @command = command
13
+ @root = Dry::Core::Inflector.singularize(command.name.relation).to_sym
14
+ end
15
+
16
+ def call(input)
17
+ command.call(root => input)
18
+ end
19
+
20
+ def >>(other)
21
+ self.class.new(command >> other)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,141 @@
1
+ require 'concurrent/map'
2
+
3
+ require 'rom/constants'
4
+ require 'rom/registry'
5
+ require 'rom/commands/result'
6
+
7
+ module ROM
8
+ # Specialized registry class for commands
9
+ #
10
+ # @api public
11
+ class CommandRegistry < Registry
12
+ include Commands
13
+
14
+ # Internal command registry
15
+ #
16
+ # @return [Registry]
17
+ #
18
+ # @api private
19
+ param :elements
20
+
21
+ # Name of the relation from which commands are under
22
+ #
23
+ # @return [String]
24
+ #
25
+ # @api private
26
+ option :relation_name
27
+
28
+ option :mappers, optional: true
29
+
30
+ option :mapper, optional: true
31
+
32
+ option :compiler, optional: true
33
+
34
+ def self.element_not_found_error
35
+ CommandNotFoundError
36
+ end
37
+
38
+ # Try to execute a command in a block
39
+ #
40
+ # @yield [command] Passes command to the block
41
+ #
42
+ # @example
43
+ #
44
+ # rom.commands[:users].try { create(name: 'Jane') }
45
+ # rom.commands[:users].try { update(:by_id, 1).call(name: 'Jane Doe') }
46
+ # rom.commands[:users].try { delete(:by_id, 1) }
47
+ #
48
+ # @return [Commands::Result]
49
+ #
50
+ # @api public
51
+ def try(&block)
52
+ response = block.call
53
+
54
+ if response.is_a?(Command) || response.is_a?(Composite)
55
+ try { response.call }
56
+ else
57
+ Result::Success.new(response)
58
+ end
59
+ rescue CommandError => e
60
+ Result::Failure.new(e)
61
+ end
62
+
63
+ # Return a command from the registry
64
+ #
65
+ # If mapper is set command will be turned into a composite command with
66
+ # auto-mapping
67
+ #
68
+ # @example
69
+ # create_user = rom.commands[:users][:create]
70
+ # create_user[name: 'Jane']
71
+ #
72
+ # # with mapping, assuming :entity mapper is registered for :users relation
73
+ # create_user = rom.commands[:users].map_with(:entity)[:create]
74
+ # create_user[name: 'Jane'] # => result is send through :entity mapper
75
+ #
76
+ # @param [Symbol] name The name of a registered command
77
+ #
78
+ # @return [Command,Command::Composite]
79
+ #
80
+ # @api public
81
+ def [](*args)
82
+ if args.size.equal?(1)
83
+ command = super
84
+ mapper = options[:mapper]
85
+
86
+ if mapper
87
+ command.curry >> mapper
88
+ else
89
+ command
90
+ end
91
+ else
92
+ cache.fetch_or_store(args.hash) { compiler.(*args) }
93
+ end
94
+ end
95
+
96
+ # Specify a mapper that should be used for commands from this registry
97
+ #
98
+ # @example
99
+ # entity_commands = rom.commands[:users].map_with(:entity)
100
+ #
101
+ #
102
+ # @param [Symbol] mapper_name The name of a registered mapper
103
+ #
104
+ # @return [CommandRegistry]
105
+ #
106
+ # @api public
107
+ def map_with(mapper_name)
108
+ with(mapper: mappers[mapper_name])
109
+ end
110
+
111
+ # @api private
112
+ def set_compiler(compiler)
113
+ options[:compiler] = @compiler = compiler
114
+ end
115
+
116
+ # @api private
117
+ def set_mappers(mappers)
118
+ options[:mappers] = @mappers = mappers
119
+ end
120
+
121
+ private
122
+
123
+ # Allow checking if a certain command is available using dot-notation
124
+ #
125
+ # @api private
126
+ def respond_to_missing?(name, include_private = false)
127
+ key?(name) || super
128
+ end
129
+
130
+ # Allow retrieving commands using dot-notation
131
+ #
132
+ # @api private
133
+ def method_missing(name, *)
134
+ if key?(name)
135
+ self[name]
136
+ else
137
+ super
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,3 @@
1
+ require 'rom/commands/create'
2
+ require 'rom/commands/update'
3
+ require 'rom/commands/delete'
@@ -0,0 +1,270 @@
1
+ require 'dry/core/class_builder'
2
+ require 'dry/core/inflector'
3
+
4
+ module ROM
5
+ # Base command class with factory class-level interface and setup-related logic
6
+ #
7
+ # @private
8
+ class Command
9
+ module ClassInterface
10
+ # This hook sets up default class state
11
+ #
12
+ # @api private
13
+ def inherited(klass)
14
+ super
15
+ klass.instance_variable_set(:'@before', before.dup)
16
+ klass.instance_variable_set(:'@after', after.dup)
17
+ end
18
+
19
+ # Sets up the base class
20
+ #
21
+ # @api private
22
+ def self.extended(klass)
23
+ super
24
+ klass.set_hooks(:before, [])
25
+ klass.set_hooks(:after, [])
26
+ end
27
+
28
+ # Return adapter specific sub-class based on the adapter identifier
29
+ #
30
+ # This is a syntax sugar to make things consistent
31
+ #
32
+ # @example
33
+ # ROM::Commands::Create[:memory]
34
+ # # => ROM::Memory::Commands::Create
35
+ #
36
+ # @param [Symbol] adapter identifier
37
+ #
38
+ # @return [Class]
39
+ #
40
+ # @api public
41
+ def [](adapter)
42
+ adapter_namespace(adapter).const_get(Dry::Core::Inflector.demodulize(name))
43
+ end
44
+
45
+ # Return namespaces that contains command subclasses of a specific adapter
46
+ #
47
+ # @param [Symbol] adapter identifier
48
+ #
49
+ # @return [Module]
50
+ #
51
+ # @api private
52
+ def adapter_namespace(adapter)
53
+ ROM.adapters.fetch(adapter).const_get(:Commands)
54
+ rescue KeyError
55
+ raise AdapterNotPresentError.new(adapter, :relation)
56
+ end
57
+
58
+ # Build a command class for a specific relation with options
59
+ #
60
+ # @example
61
+ # class CreateUser < ROM::Commands::Create[:memory]
62
+ # end
63
+ #
64
+ # command = CreateUser.build(rom.relations[:users])
65
+ #
66
+ # @param [Relation] relation
67
+ # @param [Hash] options
68
+ #
69
+ # @return [Command]
70
+ #
71
+ # @api public
72
+ def build(relation, options = EMPTY_HASH)
73
+ new(relation, self.options.merge(options))
74
+ end
75
+
76
+ # Create a command class with a specific type
77
+ #
78
+ # @param [Symbol] command name
79
+ # @param [Class] parent class
80
+ #
81
+ # @yield [Class] create class
82
+ #
83
+ # @return [Class, Object] return result of the block if it was provided
84
+ #
85
+ # @api public
86
+ def create_class(name, type, &block)
87
+ klass = Dry::Core::ClassBuilder
88
+ .new(name: "#{Dry::Core::Inflector.classify(type)}[:#{name}]", parent: type)
89
+ .call
90
+
91
+ if block
92
+ yield(klass)
93
+ else
94
+ klass
95
+ end
96
+ end
97
+
98
+ # Use a configured plugin in this relation
99
+ #
100
+ # @example
101
+ # class CreateUser < ROM::Commands::Create[:memory]
102
+ # use :pagintion
103
+ #
104
+ # per_page 30
105
+ # end
106
+ #
107
+ # @param [Symbol] plugin
108
+ # @param [Hash] options
109
+ # @option options [Symbol] :adapter (:default) first adapter to check for plugin
110
+ #
111
+ # @api public
112
+ def use(plugin, _options = EMPTY_HASH)
113
+ ROM.plugin_registry.commands.fetch(plugin, adapter).apply_to(self)
114
+ end
115
+
116
+ # Extend a command class with relation view methods
117
+ #
118
+ # @param [Relation]
119
+ #
120
+ # @return [Class]
121
+ #
122
+ # @api public
123
+ def extend_for_relation(relation)
124
+ include(relation_methods_mod(relation.class))
125
+ end
126
+
127
+ # Set before-execute hooks
128
+ #
129
+ # @overload before(hook)
130
+ # Set an before hook as a method name
131
+ #
132
+ # @example
133
+ # class CreateUser < ROM::Commands::Create[:sql]
134
+ # relation :users
135
+ # register_as :create
136
+ #
137
+ # before :my_hook
138
+ #
139
+ # def my_hook(tuple, *)
140
+ # puts "hook called#
141
+ # end
142
+ # end
143
+ #
144
+ # @overload before(hook_opts)
145
+ # Set an before hook as a method name with arguments
146
+ #
147
+ # @example
148
+ # class CreateUser < ROM::Commands::Create[:sql]
149
+ # relation :users
150
+ # register_as :create
151
+ #
152
+ # before my_hook: { arg1: 1, arg1: 2 }
153
+ #
154
+ # def my_hook(tuple, arg1:, arg2:)
155
+ # puts "hook called with args: #{arg1} and #{arg2}"
156
+ # end
157
+ # end
158
+ #
159
+ # @param [Hash<Symbol=>Hash>] hook Options with method name and pre-set args
160
+ #
161
+ # @return [Array<Hash, Symbol>] A list of all configured before hooks
162
+ #
163
+ # @api public
164
+ def before(*hooks)
165
+ if hooks.size > 0
166
+ set_hooks(:before, hooks)
167
+ else
168
+ @before
169
+ end
170
+ end
171
+
172
+ # Set after-execute hooks
173
+ #
174
+ # @overload after(hook)
175
+ # Set an after hook as a method name
176
+ #
177
+ # @example
178
+ # class CreateUser < ROM::Commands::Create[:sql]
179
+ # relation :users
180
+ # register_as :create
181
+ #
182
+ # after :my_hook
183
+ #
184
+ # def my_hook(tuple, *)
185
+ # puts "hook called#
186
+ # end
187
+ # end
188
+ #
189
+ # @overload after(hook_opts)
190
+ # Set an after hook as a method name with arguments
191
+ #
192
+ # @example
193
+ # class CreateUser < ROM::Commands::Create[:sql]
194
+ # relation :users
195
+ # register_as :create
196
+ #
197
+ # after my_hook: { arg1: 1, arg1: 2 }
198
+ #
199
+ # def my_hook(tuple, arg1:, arg2:)
200
+ # puts "hook called with args: #{arg1} and #{arg2}"
201
+ # end
202
+ # end
203
+ #
204
+ # @param [Hash<Symbol=>Hash>] hook Options with method name and pre-set args
205
+ #
206
+ # @return [Array<Hash, Symbol>] A list of all configured after hooks
207
+ #
208
+ # @api public
209
+ def after(*hooks)
210
+ if hooks.size > 0
211
+ set_hooks(:after, hooks)
212
+ else
213
+ @after
214
+ end
215
+ end
216
+
217
+ # Set new or more hooks
218
+ #
219
+ # @api private
220
+ def set_hooks(type, hooks)
221
+ ivar = :"@#{type}"
222
+
223
+ if instance_variable_defined?(ivar)
224
+ instance_variable_get(ivar).concat(hooks)
225
+ else
226
+ instance_variable_set(ivar, hooks)
227
+ end
228
+ end
229
+
230
+ # Return default name of the command class based on its name
231
+ #
232
+ # During setup phase this is used by defalut as `register_as` option
233
+ #
234
+ # @return [Symbol]
235
+ #
236
+ # @api private
237
+ def default_name
238
+ Dry::Core::Inflector.underscore(Dry::Core::Inflector.demodulize(name)).to_sym
239
+ end
240
+
241
+ # Return default options based on class macros
242
+ #
243
+ # @return [Hash]
244
+ #
245
+ # @api private
246
+ def options
247
+ { input: input, result: result, before: before, after: after }
248
+ end
249
+
250
+ # @api private
251
+ def relation_methods_mod(relation_class)
252
+ Module.new do
253
+ relation_class.view_methods.each do |meth|
254
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
255
+ def #{meth}(*args)
256
+ response = relation.public_send(:#{meth}, *args)
257
+
258
+ if response.is_a?(relation.class)
259
+ new(response)
260
+ else
261
+ response
262
+ end
263
+ end
264
+ RUBY
265
+ end
266
+ end
267
+ end
268
+ end
269
+ end
270
+ end