rom 5.3.2 → 6.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +58 -47
  3. data/LICENSE +1 -1
  4. data/README.md +6 -6
  5. data/lib/rom/array_dataset.rb +46 -0
  6. data/lib/rom/associations/abstract.rb +217 -0
  7. data/lib/rom/associations/definitions/abstract.rb +150 -0
  8. data/lib/rom/associations/definitions/many_to_many.rb +29 -0
  9. data/lib/rom/associations/definitions/many_to_one.rb +14 -0
  10. data/lib/rom/associations/definitions/one_to_many.rb +14 -0
  11. data/lib/rom/associations/definitions/one_to_one.rb +14 -0
  12. data/lib/rom/associations/definitions/one_to_one_through.rb +14 -0
  13. data/lib/rom/associations/definitions.rb +7 -0
  14. data/lib/rom/associations/many_to_many.rb +128 -0
  15. data/lib/rom/associations/many_to_one.rb +65 -0
  16. data/lib/rom/associations/one_to_many.rb +65 -0
  17. data/lib/rom/associations/one_to_one.rb +13 -0
  18. data/lib/rom/associations/one_to_one_through.rb +13 -0
  19. data/lib/rom/associations/through_identifier.rb +41 -0
  20. data/lib/rom/attribute.rb +425 -0
  21. data/lib/rom/auto_curry.rb +70 -0
  22. data/lib/rom/cache.rb +87 -0
  23. data/lib/rom/changeset/associated.rb +110 -0
  24. data/lib/rom/changeset/create.rb +18 -0
  25. data/lib/rom/changeset/delete.rb +15 -0
  26. data/lib/rom/changeset/extensions/relation.rb +26 -0
  27. data/lib/rom/changeset/pipe.rb +81 -0
  28. data/lib/rom/changeset/pipe_registry.rb +27 -0
  29. data/lib/rom/changeset/stateful.rb +285 -0
  30. data/lib/rom/changeset/update.rb +81 -0
  31. data/lib/rom/changeset.rb +185 -0
  32. data/lib/rom/command.rb +351 -0
  33. data/lib/rom/command_compiler.rb +201 -0
  34. data/lib/rom/command_proxy.rb +36 -0
  35. data/lib/rom/commands/class_interface.rb +236 -0
  36. data/lib/rom/commands/composite.rb +55 -0
  37. data/lib/rom/commands/create.rb +15 -0
  38. data/lib/rom/commands/delete.rb +16 -0
  39. data/lib/rom/commands/graph/class_interface.rb +64 -0
  40. data/lib/rom/commands/graph/input_evaluator.rb +94 -0
  41. data/lib/rom/commands/graph.rb +88 -0
  42. data/lib/rom/commands/lazy/create.rb +35 -0
  43. data/lib/rom/commands/lazy/delete.rb +39 -0
  44. data/lib/rom/commands/lazy/update.rb +46 -0
  45. data/lib/rom/commands/lazy.rb +106 -0
  46. data/lib/rom/commands/update.rb +16 -0
  47. data/lib/rom/commands.rb +5 -0
  48. data/lib/rom/compat/auto_registration.rb +115 -0
  49. data/lib/rom/compat/auto_registration_strategies/base.rb +29 -0
  50. data/lib/rom/compat/auto_registration_strategies/custom_namespace.rb +84 -0
  51. data/lib/rom/compat/auto_registration_strategies/no_namespace.rb +33 -0
  52. data/lib/rom/compat/auto_registration_strategies/with_namespace.rb +29 -0
  53. data/lib/rom/compat/command.rb +74 -0
  54. data/lib/rom/compat/components/dsl/schema.rb +130 -0
  55. data/lib/rom/compat/components.rb +91 -0
  56. data/lib/rom/compat/global.rb +17 -0
  57. data/lib/rom/compat/mapper.rb +22 -0
  58. data/lib/rom/compat/registries.rb +47 -0
  59. data/lib/rom/compat/relation.rb +40 -0
  60. data/lib/rom/compat/schema/dsl.rb +260 -0
  61. data/lib/rom/compat/setting_proxy.rb +44 -0
  62. data/lib/rom/compat/setup.rb +151 -0
  63. data/lib/rom/compat/transformer.rb +49 -0
  64. data/lib/rom/compat.rb +22 -0
  65. data/lib/rom/components/association.rb +26 -0
  66. data/lib/rom/components/command.rb +24 -0
  67. data/lib/rom/components/core.rb +148 -0
  68. data/lib/rom/components/dataset.rb +60 -0
  69. data/lib/rom/components/dsl/association.rb +47 -0
  70. data/lib/rom/components/dsl/command.rb +60 -0
  71. data/lib/rom/components/dsl/core.rb +126 -0
  72. data/lib/rom/components/dsl/dataset.rb +33 -0
  73. data/lib/rom/components/dsl/gateway.rb +14 -0
  74. data/lib/rom/components/dsl/mapper.rb +70 -0
  75. data/lib/rom/components/dsl/relation.rb +49 -0
  76. data/lib/rom/components/dsl/schema.rb +150 -0
  77. data/lib/rom/components/dsl/view.rb +82 -0
  78. data/lib/rom/components/dsl.rb +255 -0
  79. data/lib/rom/components/gateway.rb +50 -0
  80. data/lib/rom/components/mapper.rb +29 -0
  81. data/lib/rom/components/provider.rb +160 -0
  82. data/lib/rom/components/registry.rb +154 -0
  83. data/lib/rom/components/relation.rb +41 -0
  84. data/lib/rom/components/schema.rb +61 -0
  85. data/lib/rom/components/view.rb +55 -0
  86. data/lib/rom/components.rb +55 -0
  87. data/lib/rom/configuration_dsl.rb +4 -0
  88. data/lib/rom/constants.rb +135 -0
  89. data/lib/rom/container.rb +182 -0
  90. data/lib/rom/core.rb +125 -0
  91. data/lib/rom/data_proxy.rb +97 -0
  92. data/lib/rom/enumerable_dataset.rb +70 -0
  93. data/lib/rom/gateway.rb +232 -0
  94. data/lib/rom/global.rb +56 -0
  95. data/lib/rom/header/attribute.rb +190 -0
  96. data/lib/rom/header.rb +198 -0
  97. data/lib/rom/inferrer.rb +55 -0
  98. data/lib/rom/initializer.rb +80 -0
  99. data/lib/rom/lint/enumerable_dataset.rb +56 -0
  100. data/lib/rom/lint/gateway.rb +120 -0
  101. data/lib/rom/lint/linter.rb +79 -0
  102. data/lib/rom/lint/spec.rb +22 -0
  103. data/lib/rom/lint/test.rb +98 -0
  104. data/lib/rom/loader.rb +161 -0
  105. data/lib/rom/mapper/attribute_dsl.rb +480 -0
  106. data/lib/rom/mapper/dsl.rb +107 -0
  107. data/lib/rom/mapper/model_dsl.rb +61 -0
  108. data/lib/rom/mapper.rb +99 -0
  109. data/lib/rom/mapper_compiler.rb +84 -0
  110. data/lib/rom/memory/associations/many_to_many.rb +12 -0
  111. data/lib/rom/memory/associations/many_to_one.rb +12 -0
  112. data/lib/rom/memory/associations/one_to_many.rb +12 -0
  113. data/lib/rom/memory/associations/one_to_one.rb +12 -0
  114. data/lib/rom/memory/associations.rb +6 -0
  115. data/lib/rom/memory/commands.rb +60 -0
  116. data/lib/rom/memory/dataset.rb +127 -0
  117. data/lib/rom/memory/gateway.rb +66 -0
  118. data/lib/rom/memory/mapper_compiler.rb +10 -0
  119. data/lib/rom/memory/relation.rb +91 -0
  120. data/lib/rom/memory/schema.rb +32 -0
  121. data/lib/rom/memory/storage.rb +61 -0
  122. data/lib/rom/memory/types.rb +11 -0
  123. data/lib/rom/memory.rb +7 -0
  124. data/lib/rom/model_builder.rb +103 -0
  125. data/lib/rom/open_struct.rb +112 -0
  126. data/lib/rom/pipeline.rb +111 -0
  127. data/lib/rom/plugin.rb +130 -0
  128. data/lib/rom/plugins/class_methods.rb +37 -0
  129. data/lib/rom/plugins/command/schema.rb +45 -0
  130. data/lib/rom/plugins/command/timestamps.rb +149 -0
  131. data/lib/rom/plugins/dsl.rb +53 -0
  132. data/lib/rom/plugins/relation/changeset.rb +97 -0
  133. data/lib/rom/plugins/relation/instrumentation.rb +66 -0
  134. data/lib/rom/plugins/relation/registry_reader.rb +36 -0
  135. data/lib/rom/plugins/schema/timestamps.rb +59 -0
  136. data/lib/rom/plugins.rb +100 -0
  137. data/lib/rom/processor/composer.rb +37 -0
  138. data/lib/rom/processor/transformer.rb +415 -0
  139. data/lib/rom/processor.rb +30 -0
  140. data/lib/rom/registries/associations.rb +26 -0
  141. data/lib/rom/registries/commands.rb +11 -0
  142. data/lib/rom/registries/container.rb +12 -0
  143. data/lib/rom/registries/datasets.rb +21 -0
  144. data/lib/rom/registries/gateways.rb +8 -0
  145. data/lib/rom/registries/mappers.rb +21 -0
  146. data/lib/rom/registries/nestable.rb +32 -0
  147. data/lib/rom/registries/relations.rb +8 -0
  148. data/lib/rom/registries/root.rb +203 -0
  149. data/lib/rom/registries/schemas.rb +44 -0
  150. data/lib/rom/registries/views.rb +11 -0
  151. data/lib/rom/relation/class_interface.rb +61 -0
  152. data/lib/rom/relation/combined.rb +160 -0
  153. data/lib/rom/relation/commands.rb +65 -0
  154. data/lib/rom/relation/composite.rb +53 -0
  155. data/lib/rom/relation/curried.rb +129 -0
  156. data/lib/rom/relation/graph.rb +107 -0
  157. data/lib/rom/relation/loaded.rb +136 -0
  158. data/lib/rom/relation/materializable.rb +62 -0
  159. data/lib/rom/relation/name.rb +122 -0
  160. data/lib/rom/relation/wrap.rb +64 -0
  161. data/lib/rom/relation.rb +625 -0
  162. data/lib/rom/repository/class_interface.rb +162 -0
  163. data/lib/rom/repository/relation_reader.rb +48 -0
  164. data/lib/rom/repository/root.rb +75 -0
  165. data/lib/rom/repository/session.rb +60 -0
  166. data/lib/rom/repository.rb +179 -0
  167. data/lib/rom/schema/associations_dsl.rb +222 -0
  168. data/lib/rom/schema/inferrer.rb +106 -0
  169. data/lib/rom/schema.rb +471 -0
  170. data/lib/rom/settings.rb +141 -0
  171. data/lib/rom/setup.rb +297 -0
  172. data/lib/rom/struct.rb +99 -0
  173. data/lib/rom/struct_compiler.rb +114 -0
  174. data/lib/rom/support/configurable.rb +213 -0
  175. data/lib/rom/support/inflector.rb +31 -0
  176. data/lib/rom/support/memoizable.rb +61 -0
  177. data/lib/rom/support/notifications.rb +238 -0
  178. data/lib/rom/transaction.rb +26 -0
  179. data/lib/rom/transformer.rb +46 -0
  180. data/lib/rom/types.rb +74 -0
  181. data/lib/rom/version.rb +1 -1
  182. data/lib/rom-changeset.rb +4 -0
  183. data/lib/rom-core.rb +3 -0
  184. data/lib/rom-repository.rb +4 -0
  185. data/lib/rom.rb +3 -3
  186. metadata +273 -36
@@ -0,0 +1,625 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/memoizable"
4
+ require "dry/core/class_attributes"
5
+
6
+ require "rom/struct"
7
+ require "rom/constants"
8
+ require "rom/initializer"
9
+ require "rom/support/inflector"
10
+
11
+ require "rom/plugins/class_methods"
12
+ require "rom/relation/class_interface"
13
+
14
+ require "rom/auto_curry"
15
+ require "rom/pipeline"
16
+
17
+ require "rom/relation/loaded"
18
+ require "rom/relation/curried"
19
+ require "rom/relation/commands"
20
+ require "rom/relation/composite"
21
+ require "rom/relation/combined"
22
+ require "rom/relation/wrap"
23
+ require "rom/relation/materializable"
24
+
25
+ require "rom/types"
26
+
27
+ require_relative "registries/root"
28
+ require_relative "components/provider"
29
+
30
+ module ROM
31
+ # Base relation class
32
+ #
33
+ # Relation is a proxy for the dataset object provided by the gateway. It
34
+ # can forward methods to the dataset, which is why the "native" interface of
35
+ # the underlying gateway is available in the relation
36
+ #
37
+ # Individual adapters sets up their relation classes and provide different APIs
38
+ # depending on their persistence backend.
39
+ #
40
+ # @api public
41
+ class Relation
42
+ extend ROM::Provider(:dataset, :schema, :view, :association, type: :relation)
43
+ extend Plugins::ClassMethods
44
+ extend Initializer
45
+ extend ClassInterface
46
+
47
+ include Dry::Core::Memoizable
48
+ include Relation::Commands
49
+
50
+ # Default no-op output schema which is called in `Relation#each`
51
+ NOOP_OUTPUT_SCHEMA = -> tuple { tuple }.freeze
52
+
53
+ setting :auto_map, default: true
54
+ setting :auto_struct, default: false
55
+ setting :struct_namespace, default: ROM::Struct
56
+ setting :wrap_class, default: Relation::Wrap
57
+
58
+ # @api private
59
+ def self.inherited(klass)
60
+ super
61
+
62
+ adapter = config.component.adapter
63
+
64
+ klass.configure do |config|
65
+ # Relations that inherit from an adapter subclass are not considered abstract anymore
66
+ # You can override it later inside your class' config of course
67
+ if adapter
68
+ config.component.abstract = false
69
+
70
+ # Use klass' name to set defaults
71
+ #
72
+ # ie `Relations::Users` assumes :users id and a corresponding dataset (table in case of SQL)
73
+ #
74
+ # TODO: make this behavior configurable?
75
+ #
76
+ if klass.name
77
+ config.component.id = config.component.inflector.component_id(klass.name).to_sym
78
+ config.component.dataset = config.component.id
79
+ else
80
+ config.component.id = :anonymous
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ include Dry::Equalizer(:name, :dataset)
87
+ include Materializable
88
+ include Pipeline
89
+
90
+ # @!attribute [r] config
91
+ # @return [Dry::Configurable::Config]
92
+ # @api private
93
+ option :config, default: -> { self.class.config }
94
+
95
+ # @!attribute [r] name
96
+ # @return [Name] The relation name
97
+ # @api public
98
+ option :name, default: -> { Name[config.component.id, config.component.dataset] }
99
+
100
+ # @!attribute [r] registry
101
+ # @return [registry] Registry::Root with runtime dependency resolving
102
+ option :registry, default: -> { self.class.registry(config: config) }
103
+
104
+ # @!attribute [r] inflector
105
+ # @return [Dry::Inflector] The default inflector
106
+ # @api public
107
+ option :inflector, default: -> { config.component.inflector }
108
+
109
+ # @!attribute [r] schemas
110
+ # @return [Setup::registry] Relation schemas
111
+ option :schemas, default: -> { registry.schemas.scoped(config.component.id, config: config) }
112
+
113
+ # @!attribute [r] schema
114
+ # @return [Setup::registry] The canonical schema
115
+ option :schema, default: -> { schemas.infer(config.component.id) }
116
+
117
+ # @!attribute [r] datasets
118
+ # @return [registry] Relation associations
119
+ option :datasets, default: -> { registry.datasets.scoped(config.component.id, config: config) }
120
+
121
+ # @!attribute [r] dataset
122
+ # @return [Object] dataset used by the relation provided by relation's gateway
123
+ # @api public
124
+ option :dataset, default: -> { datasets.infer(config.component.id) }
125
+
126
+ # @!attribute [r] associations
127
+ # @return [Setup::registry] Relation associations
128
+ option :associations, default: -> { registry.associations.scoped(config.component.id) }
129
+
130
+ # @!attribute [r] input_schema
131
+ # @return [Object#[]] tuple processing function, uses schema or defaults to Hash[]
132
+ # @api private
133
+ option :input_schema, default: -> { schema.to_input_hash }
134
+
135
+ # @!attribute [r] output_schema
136
+ # @return [Object#[]] tuple processing function, uses schema or defaults to NOOP_OUTPUT_SCHEMA
137
+ # @api private
138
+ option :output_schema, default: lambda {
139
+ schema.any?(&:read?) ? schema.to_output_hash : NOOP_OUTPUT_SCHEMA
140
+ }
141
+
142
+ # @!attribute [r] auto_map
143
+ # @return [TrueClass,FalseClass] Whether or not a relation and its compositions should be auto-mapped
144
+ # @api private
145
+ option :auto_map, default: -> { config.auto_map }
146
+
147
+ # @!attribute [r] auto_struct
148
+ # @return [TrueClass,FalseClass] Whether or not tuples should be auto-mapped to structs
149
+ # @api private
150
+ option :auto_struct, default: -> { config.auto_struct }
151
+
152
+ # @!attribute [r] struct_namespace
153
+ # @return [Module] Custom struct namespace
154
+ # @api private
155
+ option :struct_namespace, reader: false, default: -> { config.struct_namespace }
156
+
157
+ # @!attribute [r] mappers
158
+ # @return [registry] an optional mapper registry (empty by default)
159
+ option :mappers,
160
+ -> mappers {
161
+ if mappers.is_a?(Hash)
162
+ registry.mappers
163
+ .scoped(config.component.id, opts: {adapter: config.component.adapter})
164
+ .import(mappers)
165
+ else
166
+ mappers
167
+ end
168
+ },
169
+ default: -> {
170
+ registry.mappers.scoped(config.component.id, opts: {adapter: adapter})
171
+ }
172
+
173
+ # @!attribute [r] commands
174
+ # @return [Commandregistry] Command registry
175
+ # @api private
176
+ option :commands, default: -> do
177
+ registry.commands.scoped(config.component.id, opts: {adapter: adapter})
178
+ end
179
+
180
+ # @!attribute [r] meta
181
+ # @return [Hash] Meta data stored in a hash
182
+ # @api private
183
+ option :meta, reader: true, default: -> { EMPTY_HASH }
184
+
185
+ # @api public
186
+ def self.new(dataset = nil, **opts)
187
+ if dataset
188
+ super(**opts, dataset: dataset)
189
+ else
190
+ super(**opts)
191
+ end
192
+ end
193
+
194
+ # Return schema attribute
195
+ #
196
+ # @example accessing canonical attribute
197
+ # users[:id]
198
+ # # => #<ROM::SQL::Attribute[Integer] primary_key=true name=:id source=ROM::Relation::Name(users)>
199
+ #
200
+ # @example accessing joined attribute
201
+ # tasks_with_users = tasks.join(users).select_append(tasks[:title])
202
+ # tasks_with_users[:title, :tasks]
203
+ # # => #<ROM::SQL::Attribute[String] primary_key=false name=:title source=ROM::Relation::Name(tasks)>
204
+ #
205
+ # @return [Attribute]
206
+ #
207
+ # @api public
208
+ def [](name)
209
+ schema[name]
210
+ end
211
+
212
+ # Yields relation tuples
213
+ #
214
+ # Every tuple is processed through Relation#output_schema, it's a no-op by default
215
+ #
216
+ # @yield [Hash]
217
+ #
218
+ # @return [Enumerator] if block is not provided
219
+ #
220
+ # @api public
221
+ def each(&block)
222
+ return to_enum unless block_given?
223
+
224
+ if auto_map?
225
+ mapper.(dataset.map { |tuple| output_schema[tuple] }).each(&block)
226
+ else
227
+ dataset.each { |tuple| yield(output_schema[tuple]) }
228
+ end
229
+ end
230
+
231
+ # Combine with other relations using configured associations
232
+ #
233
+ # @overload combine(*associations)
234
+ # @example
235
+ # users.combine(:tasks, :posts)
236
+ #
237
+ # @param *associations [Array<Symbol>] A list of association names
238
+ #
239
+ # @overload combine(*associations, **nested_associations)
240
+ # @example
241
+ # users.combine(:tasks, posts: :authors)
242
+ #
243
+ # @param *associations [Array<Symbol>] A list of association names
244
+ # @param *nested_associations [Hash] A hash with nested association names
245
+ #
246
+ # @overload combine(associations)
247
+ # @example
248
+ # users.combine(posts: [:authors, reviews: [:tags, comments: :author])
249
+ #
250
+ # @param *associations [Hash] A hash with nested association names
251
+ #
252
+ # @return [Relation]
253
+ #
254
+ # @api public
255
+ def combine(*args)
256
+ combine_with(*nodes(*args))
257
+ end
258
+
259
+ # Composes with other relations
260
+ #
261
+ # @param [Array<Relation>] others The other relation(s) to compose with
262
+ #
263
+ # @return [Relation::Graph]
264
+ #
265
+ # @api public
266
+ def combine_with(*others)
267
+ Combined.new(self, others)
268
+ end
269
+
270
+ # @api private
271
+ def nodes(*args)
272
+ args.reduce([]) do |acc, arg|
273
+ case arg
274
+ when Symbol
275
+ acc << node(arg)
276
+ when Hash
277
+ acc.concat(arg.map { |name, opts| node(name).combine(opts) })
278
+ when Array
279
+ acc.concat(arg.map { |opts| nodes(opts) }.reduce(:concat))
280
+ end
281
+ end
282
+ end
283
+
284
+ # Create a graph node for a given association identifier
285
+ #
286
+ # @param [Symbol, Relation::Name] name
287
+ #
288
+ # @return [Relation]
289
+ #
290
+ # @api public
291
+ def node(name)
292
+ assoc = associations[name]
293
+ other = assoc.node
294
+ other.eager_load(assoc)
295
+ end
296
+
297
+ # Return a graph node prepared by the given association
298
+ #
299
+ # @param [Association] assoc An association object
300
+ #
301
+ # @return [Relation]
302
+ #
303
+ # @api public
304
+ def eager_load(assoc)
305
+ relation = assoc.prepare(self)
306
+
307
+ if assoc.override?
308
+ relation.(assoc)
309
+ else
310
+ relation.preload_assoc(assoc)
311
+ end
312
+ end
313
+
314
+ # Preload other relation via association
315
+ #
316
+ # This is used internally when relations are composed
317
+ #
318
+ # @return [Relation::Curried]
319
+ #
320
+ # @api private
321
+ def preload_assoc(assoc, other)
322
+ assoc.preload(self, other)
323
+ end
324
+
325
+ # Wrap other relations using association names
326
+ #
327
+ # @example
328
+ # tasks.wrap(:owner)
329
+ #
330
+ # @param [Array<Symbol>] names A list with association identifiers
331
+ #
332
+ # @return [Wrap]
333
+ #
334
+ # @api public
335
+ def wrap(*names)
336
+ wrap_around(*names.map { |n| associations[n].wrap })
337
+ end
338
+
339
+ # Wrap around other relations
340
+ #
341
+ # @param [Array<Relation>] others Other relations
342
+ #
343
+ # @return [Relation::Wrap]
344
+ #
345
+ # @api public
346
+ def wrap_around(*others)
347
+ wrap_class.new(self, others)
348
+ end
349
+
350
+ # Loads a relation
351
+ #
352
+ # @return [Relation::Loaded]
353
+ #
354
+ # @api public
355
+ def call
356
+ Loaded.new(self)
357
+ end
358
+
359
+ # Materializes a relation into an array
360
+ #
361
+ # @return [Array<Hash>]
362
+ #
363
+ # @api public
364
+ def to_a
365
+ to_enum.to_a
366
+ end
367
+
368
+ # Returns if this relation is curried
369
+ #
370
+ # @return [false]
371
+ #
372
+ # @api private
373
+ def curried?
374
+ false
375
+ end
376
+
377
+ # Returns if this relation is a graph
378
+ #
379
+ # @return [false]
380
+ #
381
+ # @api private
382
+ def graph?
383
+ false
384
+ end
385
+
386
+ # Return if this is a wrap relation
387
+ #
388
+ # @return [false]
389
+ #
390
+ # @api private
391
+ def wrap?
392
+ false
393
+ end
394
+
395
+ # Returns true if a relation has schema defined
396
+ #
397
+ # @return [TrueClass, FalseClass]
398
+ #
399
+ # @api private
400
+ def schema?
401
+ !schema.empty?
402
+ end
403
+
404
+ # Return a new relation with provided dataset and additional options
405
+ #
406
+ # Use this method whenever you need to use dataset API to get a new dataset
407
+ # and you want to return a relation back. Typically relation API should be
408
+ # enough though. If you find yourself using this method, it might be worth
409
+ # to consider reporting an issue that some dataset functionality is not available
410
+ # through relation API.
411
+ #
412
+ # @example with a new dataset
413
+ # users.new(users.dataset.some_method)
414
+ #
415
+ # @example with a new dataset and options
416
+ # users.new(users.dataset.some_method, other: 'options')
417
+ #
418
+ # @param [Object] dataset
419
+ # @param [Hash] new_opts Additional options
420
+ #
421
+ # @api public
422
+ def new(dataset, **new_opts)
423
+ opts =
424
+ if new_opts.empty?
425
+ options
426
+ elsif new_opts.key?(:schema)
427
+ options.merge(new_opts).reject { |k, _| k == :input_schema || k == :output_schema }
428
+ else
429
+ options.merge(new_opts)
430
+ end
431
+
432
+ self.class.new(**opts, dataset: dataset)
433
+ end
434
+
435
+ undef_method :with
436
+
437
+ # Returns a new instance with the same dataset but new options
438
+ #
439
+ # @example
440
+ # users.with(output_schema: -> tuple { .. })
441
+ #
442
+ # @param [Hash] opts New options
443
+ #
444
+ # @return [Relation]
445
+ #
446
+ # @api public
447
+ def with(opts)
448
+ new_options =
449
+ if opts.key?(:meta)
450
+ opts.merge(meta: meta.merge(opts[:meta]))
451
+ else
452
+ opts
453
+ end
454
+
455
+ new(dataset, **options, **new_options)
456
+ end
457
+
458
+ # Returns AST for the wrapped relation
459
+ #
460
+ # @return [Array]
461
+ #
462
+ # @api public
463
+ def to_ast
464
+ [:relation, [name.relation, attr_ast, meta_ast]]
465
+ end
466
+
467
+ # @api private
468
+ def attr_ast
469
+ schema.map(&:to_read_ast)
470
+ end
471
+
472
+ # @api private
473
+ def meta_ast
474
+ meta = self.meta.merge(dataset: name.dataset, alias: name.aliaz,
475
+ struct_namespace: options[:struct_namespace])
476
+ meta[:model] = false unless auto_struct? || meta[:model]
477
+ meta
478
+ end
479
+
480
+ # @api private
481
+ def auto_map?
482
+ (auto_map || auto_struct) && !meta[:combine_type]
483
+ end
484
+
485
+ # @api private
486
+ def auto_struct?
487
+ auto_struct && !meta[:combine_type]
488
+ end
489
+
490
+ # @api private
491
+ def mapper
492
+ mappers[to_ast]
493
+ end
494
+
495
+ # Maps relation with custom mappers available via registry
496
+ #
497
+ # When `auto_map` is enabled, your mappers will be applied after performing
498
+ # default auto-mapping. This means that you can compose complex relations
499
+ # and have them auto-mapped, and use much simpler custom mappers to adjust
500
+ # resulting data according to your requirements.
501
+ #
502
+ # @overload map_with(*mappers)
503
+ # Map tuples using registered mappers
504
+ #
505
+ # @example
506
+ # users.map_with(:my_mapper, :my_other_mapper)
507
+ #
508
+ # @param [Array<Symbol>] mappers A list of mapper identifiers
509
+ #
510
+ # @overload map_with(*mappers, auto_map: true)
511
+ # Map tuples using custom registered mappers and enforce auto-mapping
512
+ #
513
+ # @example
514
+ # users.map_with(:my_mapper, :my_other_mapper, auto_map: true)
515
+ #
516
+ # @param [Array<Symbol>] mappers A list of mapper identifiers
517
+ #
518
+ # @return [Relation::Composite] Mapped relation
519
+ #
520
+ # @api public
521
+ def map_with(*names, **opts)
522
+ super(*names).with(opts)
523
+ end
524
+
525
+ # Return a new relation that will map its tuples to instances of the provided class
526
+ #
527
+ # @example
528
+ # users.map_to(MyUserModel)
529
+ #
530
+ # @param [Class] klass Your custom model class
531
+ #
532
+ # @return [Relation]
533
+ #
534
+ # @api public
535
+ def map_to(klass, **opts)
536
+ with(opts.merge(auto_map: false, auto_struct: true, meta: {model: klass}))
537
+ end
538
+
539
+ # Return a new relation with an aliased name
540
+ #
541
+ # @example
542
+ # users.as(:people)
543
+ #
544
+ # @param [Symbol] aliaz Aliased name
545
+ #
546
+ # @return [Relation]
547
+ #
548
+ # @api public
549
+ def as(aliaz)
550
+ with(name: name.as(aliaz))
551
+ end
552
+
553
+ # @return [Symbol] The wrapped relation's adapter identifier ie :sql or :http
554
+ #
555
+ # @api private
556
+ def adapter
557
+ config.component.adapter
558
+ end
559
+
560
+ # Return name of the source gateway of this relation
561
+ #
562
+ # @return [Symbol]
563
+ #
564
+ # @api private
565
+ def gateway
566
+ config.component.gateway
567
+ end
568
+
569
+ # Return a foreign key name for the provided relation name
570
+ #
571
+ # @param [Name] name The relation name object
572
+ #
573
+ # @return [Symbol]
574
+ #
575
+ # @api private
576
+ def foreign_key(name)
577
+ attr = schema.foreign_key(name.dataset)
578
+
579
+ if attr
580
+ attr.name
581
+ else
582
+ :"#{inflector.singularize(name.dataset)}_id"
583
+ end
584
+ end
585
+
586
+ # Return a new relation configured with the provided struct namespace
587
+ #
588
+ # @param [Module] ns Custom namespace module for auto-structs
589
+ #
590
+ # @return [Relation]
591
+ #
592
+ # @api public
593
+ def struct_namespace(ns)
594
+ options[:struct_namespace] == ns ? self : with(struct_namespace: ns)
595
+ end
596
+
597
+ memoize :to_ast, :auto_map?, :auto_struct?, :foreign_key, :combine, :wrap, :node
598
+
599
+ # we do it here because we want to avoid previous methods to be auto_curried
600
+ # via method_added hook, which is what AutoCurry uses
601
+ extend AutoCurry
602
+
603
+ auto_curry :preload_assoc
604
+
605
+ private
606
+
607
+ # Hook used by `Pipeline` to get the class that should be used for composition
608
+ #
609
+ # @return [Class]
610
+ #
611
+ # @api private
612
+ def composite_class
613
+ Relation::Composite
614
+ end
615
+
616
+ # Return configured "wrap" relation class used in Relation#wrap
617
+ #
618
+ # @return [Class]
619
+ #
620
+ # @api private
621
+ def wrap_class
622
+ config.wrap_class
623
+ end
624
+ end
625
+ end