rom 5.4.2 → 6.0.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +58 -71
  3. data/LICENSE +1 -1
  4. data/README.md +7 -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 +302 -23
@@ -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