rom-core 4.0.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +603 -0
- data/LICENSE +20 -0
- data/README.md +18 -0
- data/lib/rom-core.rb +1 -0
- data/lib/rom/array_dataset.rb +44 -0
- data/lib/rom/association_set.rb +16 -0
- data/lib/rom/associations/abstract.rb +135 -0
- data/lib/rom/associations/definitions.rb +5 -0
- data/lib/rom/associations/definitions/abstract.rb +116 -0
- data/lib/rom/associations/definitions/many_to_many.rb +24 -0
- data/lib/rom/associations/definitions/many_to_one.rb +11 -0
- data/lib/rom/associations/definitions/one_to_many.rb +11 -0
- data/lib/rom/associations/definitions/one_to_one.rb +11 -0
- data/lib/rom/associations/definitions/one_to_one_through.rb +11 -0
- data/lib/rom/associations/many_to_many.rb +81 -0
- data/lib/rom/associations/many_to_one.rb +37 -0
- data/lib/rom/associations/one_to_many.rb +37 -0
- data/lib/rom/associations/one_to_one.rb +8 -0
- data/lib/rom/associations/one_to_one_through.rb +8 -0
- data/lib/rom/associations/through_identifier.rb +39 -0
- data/lib/rom/auto_curry.rb +55 -0
- data/lib/rom/cache.rb +46 -0
- data/lib/rom/command.rb +488 -0
- data/lib/rom/command_compiler.rb +239 -0
- data/lib/rom/command_proxy.rb +24 -0
- data/lib/rom/command_registry.rb +141 -0
- data/lib/rom/commands.rb +3 -0
- data/lib/rom/commands/class_interface.rb +270 -0
- data/lib/rom/commands/composite.rb +53 -0
- data/lib/rom/commands/create.rb +13 -0
- data/lib/rom/commands/delete.rb +14 -0
- data/lib/rom/commands/graph.rb +88 -0
- data/lib/rom/commands/graph/class_interface.rb +62 -0
- data/lib/rom/commands/graph/input_evaluator.rb +62 -0
- data/lib/rom/commands/lazy.rb +99 -0
- data/lib/rom/commands/lazy/create.rb +23 -0
- data/lib/rom/commands/lazy/delete.rb +27 -0
- data/lib/rom/commands/lazy/update.rb +34 -0
- data/lib/rom/commands/result.rb +96 -0
- data/lib/rom/commands/update.rb +14 -0
- data/lib/rom/configuration.rb +114 -0
- data/lib/rom/configuration_dsl.rb +87 -0
- data/lib/rom/configuration_dsl/command.rb +41 -0
- data/lib/rom/configuration_dsl/command_dsl.rb +35 -0
- data/lib/rom/configuration_dsl/relation.rb +26 -0
- data/lib/rom/configuration_plugin.rb +17 -0
- data/lib/rom/constants.rb +64 -0
- data/lib/rom/container.rb +147 -0
- data/lib/rom/core.rb +46 -0
- data/lib/rom/create_container.rb +60 -0
- data/lib/rom/data_proxy.rb +94 -0
- data/lib/rom/enumerable_dataset.rb +68 -0
- data/lib/rom/environment.rb +70 -0
- data/lib/rom/gateway.rb +184 -0
- data/lib/rom/global.rb +58 -0
- data/lib/rom/global/plugin_dsl.rb +47 -0
- data/lib/rom/initializer.rb +64 -0
- data/lib/rom/lint/enumerable_dataset.rb +54 -0
- data/lib/rom/lint/gateway.rb +120 -0
- data/lib/rom/lint/linter.rb +78 -0
- data/lib/rom/lint/spec.rb +20 -0
- data/lib/rom/lint/test.rb +98 -0
- data/lib/rom/mapper_registry.rb +24 -0
- data/lib/rom/memory.rb +4 -0
- data/lib/rom/memory/associations.rb +4 -0
- data/lib/rom/memory/associations/many_to_many.rb +10 -0
- data/lib/rom/memory/associations/many_to_one.rb +10 -0
- data/lib/rom/memory/associations/one_to_many.rb +10 -0
- data/lib/rom/memory/associations/one_to_one.rb +10 -0
- data/lib/rom/memory/commands.rb +56 -0
- data/lib/rom/memory/dataset.rb +97 -0
- data/lib/rom/memory/gateway.rb +64 -0
- data/lib/rom/memory/relation.rb +62 -0
- data/lib/rom/memory/schema.rb +23 -0
- data/lib/rom/memory/storage.rb +59 -0
- data/lib/rom/memory/types.rb +9 -0
- data/lib/rom/pipeline.rb +105 -0
- data/lib/rom/plugin.rb +25 -0
- data/lib/rom/plugin_base.rb +45 -0
- data/lib/rom/plugin_registry.rb +197 -0
- data/lib/rom/plugins/command/schema.rb +37 -0
- data/lib/rom/plugins/configuration/configuration_dsl.rb +21 -0
- data/lib/rom/plugins/relation/instrumentation.rb +51 -0
- data/lib/rom/plugins/relation/registry_reader.rb +44 -0
- data/lib/rom/plugins/schema/timestamps.rb +58 -0
- data/lib/rom/registry.rb +71 -0
- data/lib/rom/relation.rb +548 -0
- data/lib/rom/relation/class_interface.rb +282 -0
- data/lib/rom/relation/commands.rb +23 -0
- data/lib/rom/relation/composite.rb +46 -0
- data/lib/rom/relation/curried.rb +103 -0
- data/lib/rom/relation/graph.rb +197 -0
- data/lib/rom/relation/loaded.rb +127 -0
- data/lib/rom/relation/materializable.rb +66 -0
- data/lib/rom/relation/name.rb +111 -0
- data/lib/rom/relation/view_dsl.rb +64 -0
- data/lib/rom/relation/wrap.rb +83 -0
- data/lib/rom/relation_registry.rb +10 -0
- data/lib/rom/schema.rb +437 -0
- data/lib/rom/schema/associations_dsl.rb +195 -0
- data/lib/rom/schema/attribute.rb +419 -0
- data/lib/rom/schema/dsl.rb +164 -0
- data/lib/rom/schema/inferrer.rb +66 -0
- data/lib/rom/schema_plugin.rb +27 -0
- data/lib/rom/setup.rb +68 -0
- data/lib/rom/setup/auto_registration.rb +74 -0
- data/lib/rom/setup/auto_registration_strategies/base.rb +16 -0
- data/lib/rom/setup/auto_registration_strategies/custom_namespace.rb +63 -0
- data/lib/rom/setup/auto_registration_strategies/no_namespace.rb +20 -0
- data/lib/rom/setup/auto_registration_strategies/with_namespace.rb +18 -0
- data/lib/rom/setup/finalize.rb +103 -0
- data/lib/rom/setup/finalize/finalize_commands.rb +60 -0
- data/lib/rom/setup/finalize/finalize_mappers.rb +56 -0
- data/lib/rom/setup/finalize/finalize_relations.rb +135 -0
- data/lib/rom/support/configurable.rb +85 -0
- data/lib/rom/support/memoizable.rb +58 -0
- data/lib/rom/support/notifications.rb +103 -0
- data/lib/rom/transaction.rb +24 -0
- data/lib/rom/types.rb +26 -0
- data/lib/rom/version.rb +5 -0
- metadata +289 -0
data/lib/rom/registry.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'dry/equalizer'
|
2
|
+
|
3
|
+
require 'rom/initializer'
|
4
|
+
require 'rom/cache'
|
5
|
+
require 'rom/constants'
|
6
|
+
|
7
|
+
module ROM
|
8
|
+
# @api private
|
9
|
+
class Registry
|
10
|
+
extend Initializer
|
11
|
+
|
12
|
+
include Enumerable
|
13
|
+
include Dry::Equalizer(:elements)
|
14
|
+
|
15
|
+
param :elements
|
16
|
+
|
17
|
+
option :cache, reader: true, default: -> { Cache.new }
|
18
|
+
|
19
|
+
def self.new(*args)
|
20
|
+
case args.size
|
21
|
+
when 0
|
22
|
+
super({}, {})
|
23
|
+
when 1
|
24
|
+
super(*args, {})
|
25
|
+
else
|
26
|
+
super(*args)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.element_not_found_error
|
31
|
+
ElementNotFoundError
|
32
|
+
end
|
33
|
+
|
34
|
+
def map(&block)
|
35
|
+
new_elements = elements.each_with_object({}) do |(name, element), h|
|
36
|
+
h[name] = yield(element)
|
37
|
+
end
|
38
|
+
self.class.new(new_elements, options)
|
39
|
+
end
|
40
|
+
|
41
|
+
def each(&block)
|
42
|
+
return to_enum unless block
|
43
|
+
elements.each { |element| yield(element) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def key?(name)
|
47
|
+
!name.nil? && elements.key?(name.to_sym)
|
48
|
+
end
|
49
|
+
|
50
|
+
def fetch(key)
|
51
|
+
raise ArgumentError.new('key cannot be nil') if key.nil?
|
52
|
+
|
53
|
+
elements.fetch(key.to_sym) do
|
54
|
+
return yield if block_given?
|
55
|
+
|
56
|
+
raise self.class.element_not_found_error.new(key, self)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
alias_method :[], :fetch
|
60
|
+
|
61
|
+
def respond_to_missing?(name, include_private = false)
|
62
|
+
elements.key?(name) || super
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def method_missing(name, *)
|
68
|
+
elements.fetch(name) { super }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/rom/relation.rb
ADDED
@@ -0,0 +1,548 @@
|
|
1
|
+
require 'dry/core/class_attributes'
|
2
|
+
|
3
|
+
require 'rom/struct'
|
4
|
+
require 'rom/constants'
|
5
|
+
require 'rom/initializer'
|
6
|
+
require 'rom/support/memoizable'
|
7
|
+
|
8
|
+
require 'rom/relation/class_interface'
|
9
|
+
|
10
|
+
require 'rom/auto_curry'
|
11
|
+
require 'rom/pipeline'
|
12
|
+
require 'rom/mapper_registry'
|
13
|
+
require 'rom/command_registry'
|
14
|
+
|
15
|
+
require 'rom/relation/loaded'
|
16
|
+
require 'rom/relation/curried'
|
17
|
+
require 'rom/relation/composite'
|
18
|
+
require 'rom/relation/graph'
|
19
|
+
require 'rom/relation/wrap'
|
20
|
+
require 'rom/relation/materializable'
|
21
|
+
require 'rom/relation/commands'
|
22
|
+
require 'rom/association_set'
|
23
|
+
|
24
|
+
require 'rom/types'
|
25
|
+
require 'rom/schema'
|
26
|
+
|
27
|
+
module ROM
|
28
|
+
# Base relation class
|
29
|
+
#
|
30
|
+
# Relation is a proxy for the dataset object provided by the gateway. It
|
31
|
+
# can forward methods to the dataset, which is why the "native" interface of
|
32
|
+
# the underlying gateway is available in the relation. This interface,
|
33
|
+
# however, is considered private and should not be used outside of the
|
34
|
+
# relation instance.
|
35
|
+
#
|
36
|
+
# Individual adapters sets up their relation classes and provide different APIs
|
37
|
+
# depending on their persistence backend.
|
38
|
+
#
|
39
|
+
# Vanilla Relation class doesn't have APIs that are specific to ROM container setup.
|
40
|
+
# When adapter Relation class inherits from this class, these APIs are added automatically,
|
41
|
+
# so that they can be registered within a container.
|
42
|
+
#
|
43
|
+
# @see ROM::Relation::ClassInterface
|
44
|
+
#
|
45
|
+
# @api public
|
46
|
+
class Relation
|
47
|
+
# Default no-op output schema which is called in `Relation#each`
|
48
|
+
NOOP_OUTPUT_SCHEMA = -> tuple { tuple }.freeze
|
49
|
+
|
50
|
+
extend Initializer
|
51
|
+
extend ClassInterface
|
52
|
+
|
53
|
+
include Relation::Commands
|
54
|
+
include Memoizable
|
55
|
+
|
56
|
+
extend Dry::Core::ClassAttributes
|
57
|
+
|
58
|
+
defines :adapter, :gateway, :schema_opts, :schema_class,
|
59
|
+
:schema_attr_class, :schema_inferrer, :schema_dsl,
|
60
|
+
:wrap_class, :auto_map, :auto_struct, :struct_namespace
|
61
|
+
|
62
|
+
gateway :default
|
63
|
+
|
64
|
+
auto_map true
|
65
|
+
auto_struct false
|
66
|
+
struct_namespace ROM::Struct
|
67
|
+
|
68
|
+
schema_opts EMPTY_HASH
|
69
|
+
schema_dsl Schema::DSL
|
70
|
+
schema_attr_class Schema::Attribute
|
71
|
+
schema_class Schema
|
72
|
+
schema_inferrer Schema::DEFAULT_INFERRER
|
73
|
+
|
74
|
+
wrap_class Relation::Wrap
|
75
|
+
|
76
|
+
include Dry::Equalizer(:name, :dataset)
|
77
|
+
include Materializable
|
78
|
+
include Pipeline
|
79
|
+
|
80
|
+
# @!attribute [r] dataset
|
81
|
+
# @return [Object] dataset used by the relation provided by relation's gateway
|
82
|
+
# @api public
|
83
|
+
param :dataset
|
84
|
+
|
85
|
+
# @!attribute [r] schema
|
86
|
+
# @return [Schema] relation schema, defaults to class-level canonical
|
87
|
+
# schema (if it was defined) and sets an empty one as
|
88
|
+
# the fallback
|
89
|
+
# @api public
|
90
|
+
option :schema, default: -> { self.class.schema || self.class.default_schema }
|
91
|
+
|
92
|
+
# @!attribute [r] name
|
93
|
+
# @return [Object] The relation name
|
94
|
+
# @api public
|
95
|
+
option :name, default: -> { self.class.schema ? self.class.schema.name : self.class.default_name }
|
96
|
+
|
97
|
+
# @!attribute [r] input_schema
|
98
|
+
# @return [Object#[]] tuple processing function, uses schema or defaults to Hash[]
|
99
|
+
# @api private
|
100
|
+
option :input_schema, default: -> { schema.to_input_hash }
|
101
|
+
|
102
|
+
# @!attribute [r] output_schema
|
103
|
+
# @return [Object#[]] tuple processing function, uses schema or defaults to NOOP_OUTPUT_SCHEMA
|
104
|
+
# @api private
|
105
|
+
option :output_schema, default: -> {
|
106
|
+
schema.any?(&:read?) ? schema.to_output_hash : NOOP_OUTPUT_SCHEMA
|
107
|
+
}
|
108
|
+
|
109
|
+
# @!attribute [r] auto_map
|
110
|
+
# @return [TrueClass,FalseClass] Whether or not a relation and its compositions should be auto-mapped
|
111
|
+
# @api private
|
112
|
+
option :auto_map, reader: true, default: -> { self.class.auto_map }
|
113
|
+
|
114
|
+
# @!attribute [r] auto_struct
|
115
|
+
# @return [TrueClass,FalseClass] Whether or not tuples should be auto-mapped to structs
|
116
|
+
# @api private
|
117
|
+
option :auto_struct, reader: true, default: -> { self.class.auto_struct }
|
118
|
+
|
119
|
+
# @!attribute [r] struct_namespace
|
120
|
+
# @return [Module] Custom struct namespace
|
121
|
+
# @api private
|
122
|
+
option :struct_namespace, reader: false, default: -> { self.class.struct_namespace }
|
123
|
+
|
124
|
+
# @!attribute [r] mappers
|
125
|
+
# @return [MapperRegistry] an optional mapper registry (empty by default)
|
126
|
+
option :mappers, default: -> { MapperRegistry.new }
|
127
|
+
|
128
|
+
# @!attribute [r] commands
|
129
|
+
# @return [CommandRegistry] Command registry
|
130
|
+
# @api private
|
131
|
+
option :commands, default: -> { CommandRegistry.new({}, relation_name: name.relation) }
|
132
|
+
|
133
|
+
# @!attribute [r] meta
|
134
|
+
# @return [Hash] Meta data stored in a hash
|
135
|
+
# @api private
|
136
|
+
option :meta, reader: true, default: -> { EMPTY_HASH }
|
137
|
+
|
138
|
+
# Return schema attribute
|
139
|
+
#
|
140
|
+
# @example accessing canonical attribute
|
141
|
+
# users[:id]
|
142
|
+
# # => #<ROM::SQL::Attribute[Integer] primary_key=true name=:id source=ROM::Relation::Name(users)>
|
143
|
+
#
|
144
|
+
# @example accessing joined attribute
|
145
|
+
# tasks_with_users = tasks.join(users).select_append(tasks[:title])
|
146
|
+
# tasks_with_users[:title, :tasks]
|
147
|
+
# # => #<ROM::SQL::Attribute[String] primary_key=false name=:title source=ROM::Relation::Name(tasks)>
|
148
|
+
#
|
149
|
+
# @return [Schema::Attribute]
|
150
|
+
#
|
151
|
+
# @api public
|
152
|
+
def [](name)
|
153
|
+
schema[name]
|
154
|
+
end
|
155
|
+
|
156
|
+
# Yields relation tuples
|
157
|
+
#
|
158
|
+
# Every tuple is processed through Relation#output_schema, it's a no-op by default
|
159
|
+
#
|
160
|
+
# @yield [Hash]
|
161
|
+
#
|
162
|
+
# @return [Enumerator] if block is not provided
|
163
|
+
#
|
164
|
+
# @api public
|
165
|
+
def each(&block)
|
166
|
+
return to_enum unless block
|
167
|
+
|
168
|
+
if auto_struct?
|
169
|
+
mapper.(dataset.map { |tuple| output_schema[tuple] }).each { |struct| yield(struct) }
|
170
|
+
else
|
171
|
+
dataset.each { |tuple| yield(output_schema[tuple]) }
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Composes with other relations
|
176
|
+
#
|
177
|
+
# @param [Array<Relation>] others The other relation(s) to compose with
|
178
|
+
#
|
179
|
+
# @return [Relation::Graph]
|
180
|
+
#
|
181
|
+
# @api public
|
182
|
+
def graph(*others)
|
183
|
+
Graph.build(self, others)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Combine with other relations
|
187
|
+
#
|
188
|
+
# @overload combine(*associations)
|
189
|
+
# Composes relations using configured associations
|
190
|
+
|
191
|
+
# @example
|
192
|
+
# users.combine(:tasks, :posts)
|
193
|
+
# @param *associations [Array<Symbol>] A list of association names
|
194
|
+
#
|
195
|
+
# @return [Relation]
|
196
|
+
#
|
197
|
+
# @api public
|
198
|
+
def combine(*args)
|
199
|
+
graph(*nodes(*args))
|
200
|
+
end
|
201
|
+
|
202
|
+
# @api private
|
203
|
+
def nodes(*args)
|
204
|
+
args.map do |arg|
|
205
|
+
case arg
|
206
|
+
when Symbol
|
207
|
+
node(arg)
|
208
|
+
when Hash
|
209
|
+
arg.reduce(self) { |r, (k, v)| r.node(k).combine(*v) }
|
210
|
+
when Array
|
211
|
+
arg.map { |opts| nodes(opts) }
|
212
|
+
end
|
213
|
+
end.flatten(0)
|
214
|
+
end
|
215
|
+
|
216
|
+
# @api public
|
217
|
+
def node(name)
|
218
|
+
assoc = associations[name]
|
219
|
+
other = assoc.node
|
220
|
+
other.eager_load(assoc)
|
221
|
+
end
|
222
|
+
|
223
|
+
# @api public
|
224
|
+
def eager_load(assoc)
|
225
|
+
relation = assoc.prepare(self)
|
226
|
+
|
227
|
+
if assoc.override?
|
228
|
+
relation.(assoc)
|
229
|
+
else
|
230
|
+
relation.preload_assoc(assoc)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# @api private
|
235
|
+
def preload_assoc(assoc, other)
|
236
|
+
assoc.preload(self, other)
|
237
|
+
end
|
238
|
+
|
239
|
+
# Wrap other relations
|
240
|
+
#
|
241
|
+
# @example
|
242
|
+
# tasks.wrap(:owner)
|
243
|
+
#
|
244
|
+
# @param [Hash] options
|
245
|
+
#
|
246
|
+
# @return [RelationProxy]
|
247
|
+
#
|
248
|
+
# @api public
|
249
|
+
def wrap(*names)
|
250
|
+
wrap_class.new(self, names.map { |n| associations[n].wrap })
|
251
|
+
end
|
252
|
+
|
253
|
+
# Loads relation
|
254
|
+
#
|
255
|
+
# @return [Relation::Loaded]
|
256
|
+
#
|
257
|
+
# @api public
|
258
|
+
def call
|
259
|
+
Loaded.new(self)
|
260
|
+
end
|
261
|
+
|
262
|
+
# Materializes a relation into an array
|
263
|
+
#
|
264
|
+
# @return [Array<Hash>]
|
265
|
+
#
|
266
|
+
# @api public
|
267
|
+
def to_a
|
268
|
+
to_enum.to_a
|
269
|
+
end
|
270
|
+
|
271
|
+
# Returns if this relation is curried
|
272
|
+
#
|
273
|
+
# @return [false]
|
274
|
+
#
|
275
|
+
# @api private
|
276
|
+
def curried?
|
277
|
+
false
|
278
|
+
end
|
279
|
+
|
280
|
+
# Returns if this relation is a graph
|
281
|
+
#
|
282
|
+
# @return [false]
|
283
|
+
#
|
284
|
+
# @api private
|
285
|
+
def graph?
|
286
|
+
false
|
287
|
+
end
|
288
|
+
|
289
|
+
# Return if this is a wrap relation
|
290
|
+
#
|
291
|
+
# @return [false]
|
292
|
+
#
|
293
|
+
# @api private
|
294
|
+
def wrap?
|
295
|
+
false
|
296
|
+
end
|
297
|
+
|
298
|
+
# Returns true if a relation has schema defined
|
299
|
+
#
|
300
|
+
# @return [TrueClass, FalseClass]
|
301
|
+
#
|
302
|
+
# @api private
|
303
|
+
def schema?
|
304
|
+
! schema.empty?
|
305
|
+
end
|
306
|
+
|
307
|
+
# Return a new relation with provided dataset and additional options
|
308
|
+
#
|
309
|
+
# Use this method whenever you need to use dataset API to get a new dataset
|
310
|
+
# and you want to return a relation back. Typically relation API should be
|
311
|
+
# enough though. If you find yourself using this method, it might be worth
|
312
|
+
# to consider reporting an issue that some dataset functionality is not available
|
313
|
+
# through relation API.
|
314
|
+
#
|
315
|
+
# @example with a new dataset
|
316
|
+
# users.new(users.dataset.some_method)
|
317
|
+
#
|
318
|
+
# @example with a new dataset and options
|
319
|
+
# users.new(users.dataset.some_method, other: 'options')
|
320
|
+
#
|
321
|
+
# @param [Object] dataset
|
322
|
+
# @param [Hash] new_opts Additional options
|
323
|
+
#
|
324
|
+
# @api public
|
325
|
+
def new(dataset, new_opts = EMPTY_HASH)
|
326
|
+
if new_opts.empty?
|
327
|
+
opts = options
|
328
|
+
elsif new_opts.key?(:schema)
|
329
|
+
opts = options.reject { |k, _| k == :input_schema || k == :output_schema }.merge(new_opts)
|
330
|
+
else
|
331
|
+
opts = options.merge(new_opts)
|
332
|
+
end
|
333
|
+
|
334
|
+
self.class.new(dataset, opts)
|
335
|
+
end
|
336
|
+
|
337
|
+
# Returns a new instance with the same dataset but new options
|
338
|
+
#
|
339
|
+
# @example
|
340
|
+
# users.with(output_schema: -> tuple { .. })
|
341
|
+
#
|
342
|
+
# @param new_options [Hash]
|
343
|
+
#
|
344
|
+
# @return [Relation]
|
345
|
+
#
|
346
|
+
# @api private
|
347
|
+
def with(opts)
|
348
|
+
new_options =
|
349
|
+
if opts.key?(:meta)
|
350
|
+
opts.merge(meta: meta.merge(opts[:meta]))
|
351
|
+
else
|
352
|
+
opts
|
353
|
+
end
|
354
|
+
|
355
|
+
new(dataset, options.merge(new_options))
|
356
|
+
end
|
357
|
+
|
358
|
+
# Return schema's association set (empty by default)
|
359
|
+
#
|
360
|
+
# @return [AssociationSet] Schema's association set (empty by default)
|
361
|
+
#
|
362
|
+
# @api public
|
363
|
+
def associations
|
364
|
+
schema.associations
|
365
|
+
end
|
366
|
+
|
367
|
+
# Returns AST for the wrapped relation
|
368
|
+
#
|
369
|
+
# @return [Array]
|
370
|
+
#
|
371
|
+
# @api public
|
372
|
+
def to_ast
|
373
|
+
[:relation, [name.relation, attr_ast, meta_ast]]
|
374
|
+
end
|
375
|
+
|
376
|
+
# @api private
|
377
|
+
def attr_ast
|
378
|
+
schema.map { |t| t.to_read_ast }
|
379
|
+
end
|
380
|
+
|
381
|
+
# @api private
|
382
|
+
def meta_ast
|
383
|
+
meta = self.meta.merge(dataset: name.dataset, alias: name.aliaz, struct_namespace: options[:struct_namespace])
|
384
|
+
meta[:model] = false unless auto_struct? || meta[:model]
|
385
|
+
meta
|
386
|
+
end
|
387
|
+
|
388
|
+
# @api private
|
389
|
+
def auto_map?
|
390
|
+
(auto_map || auto_struct) && !meta[:combine_type]
|
391
|
+
end
|
392
|
+
|
393
|
+
# @api private
|
394
|
+
def auto_struct?
|
395
|
+
auto_struct && !meta[:combine_type]
|
396
|
+
end
|
397
|
+
|
398
|
+
# @api private
|
399
|
+
def mapper
|
400
|
+
mappers[to_ast]
|
401
|
+
end
|
402
|
+
|
403
|
+
# Maps the wrapped relation with other mappers available in the registry
|
404
|
+
#
|
405
|
+
# @overload map_with(model)
|
406
|
+
# Map tuples to the provided custom model class
|
407
|
+
#
|
408
|
+
# @example
|
409
|
+
# users.as(MyUserModel)
|
410
|
+
#
|
411
|
+
# @param [Class>] model Your custom model class
|
412
|
+
#
|
413
|
+
# @overload map_with(*mappers)
|
414
|
+
# Map tuples using registered mappers
|
415
|
+
#
|
416
|
+
# @example
|
417
|
+
# users.map_with(:my_mapper, :my_other_mapper)
|
418
|
+
#
|
419
|
+
# @param [Array<Symbol>] mappers A list of mapper identifiers
|
420
|
+
#
|
421
|
+
# @overload map_with(*mappers, auto_map: true)
|
422
|
+
# Map tuples using auto-mapping and custom registered mappers
|
423
|
+
#
|
424
|
+
# If `auto_map` is enabled, your mappers will be applied after performing
|
425
|
+
# default auto-mapping. This means that you can compose complex relations
|
426
|
+
# and have them auto-mapped, and use much simpler custom mappers to adjust
|
427
|
+
# resulting data according to your requirements.
|
428
|
+
#
|
429
|
+
# @example
|
430
|
+
# users.map_with(:my_mapper, :my_other_mapper, auto_map: true)
|
431
|
+
#
|
432
|
+
# @param [Array<Symbol>] mappers A list of mapper identifiers
|
433
|
+
#
|
434
|
+
# @return [RelationProxy] A new relation proxy with pipelined relation
|
435
|
+
#
|
436
|
+
# @api public
|
437
|
+
def map_with(*names, **opts)
|
438
|
+
super(*names).with(opts)
|
439
|
+
end
|
440
|
+
|
441
|
+
# Return a new relation that will map its tuples to instance of the provided class
|
442
|
+
#
|
443
|
+
# @example
|
444
|
+
# users.map_to(MyUserModel)
|
445
|
+
#
|
446
|
+
# @param [Class] klass Your custom model class
|
447
|
+
#
|
448
|
+
# @return [Relation::Composite]
|
449
|
+
#
|
450
|
+
# @api public
|
451
|
+
def map_to(klass, **opts)
|
452
|
+
with(opts.merge(meta: { model: klass }))
|
453
|
+
end
|
454
|
+
|
455
|
+
# Return a new relation with an aliased name
|
456
|
+
#
|
457
|
+
# @example
|
458
|
+
# users.as(:people)
|
459
|
+
#
|
460
|
+
# @param [Class] klass Your custom model class
|
461
|
+
#
|
462
|
+
# @return [Relation::Composite]
|
463
|
+
#
|
464
|
+
# @api public
|
465
|
+
def as(aliaz)
|
466
|
+
with(name: name.as(aliaz))
|
467
|
+
end
|
468
|
+
|
469
|
+
# @return [Symbol] The wrapped relation's adapter identifier ie :sql or :http
|
470
|
+
#
|
471
|
+
# @api private
|
472
|
+
def adapter
|
473
|
+
self.class.adapter
|
474
|
+
end
|
475
|
+
|
476
|
+
# Return name of the source gateway of this relation
|
477
|
+
#
|
478
|
+
# @return [Symbol]
|
479
|
+
#
|
480
|
+
# @api private
|
481
|
+
def gateway
|
482
|
+
self.class.gateway
|
483
|
+
end
|
484
|
+
|
485
|
+
# Return all registered relation schemas
|
486
|
+
#
|
487
|
+
# This holds all schemas defined via `view` DSL
|
488
|
+
#
|
489
|
+
# @return [Hash<Symbol=>Schema>]
|
490
|
+
#
|
491
|
+
# @api public
|
492
|
+
def schemas
|
493
|
+
self.class.schemas
|
494
|
+
end
|
495
|
+
|
496
|
+
# Return a foreign key name for the provided relation name
|
497
|
+
#
|
498
|
+
# @param [Name] name The relation name object
|
499
|
+
#
|
500
|
+
# @return [Symbol]
|
501
|
+
#
|
502
|
+
# @api private
|
503
|
+
def foreign_key(name)
|
504
|
+
attr = schema.foreign_key(name.dataset)
|
505
|
+
|
506
|
+
if attr
|
507
|
+
attr.name
|
508
|
+
else
|
509
|
+
:"#{Dry::Core::Inflector.singularize(name.dataset)}_id"
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
# Return a new relation configured with the provided struct namespace
|
514
|
+
#
|
515
|
+
# @param [Module] namespace
|
516
|
+
#
|
517
|
+
# @return [Relation]
|
518
|
+
#
|
519
|
+
# @api public
|
520
|
+
def struct_namespace(ns)
|
521
|
+
options[:struct_namespace] == ns ? self : with(struct_namespace: ns)
|
522
|
+
end
|
523
|
+
|
524
|
+
memoize :to_ast, :auto_map?, :auto_struct?, :foreign_key, :combine, :wrap, :node
|
525
|
+
|
526
|
+
# we do it here because we want to avoid previous methods to be auto_curried
|
527
|
+
# via method_added hook, which is what AutoCurry uses
|
528
|
+
extend AutoCurry
|
529
|
+
|
530
|
+
auto_curry :preload_assoc
|
531
|
+
|
532
|
+
private
|
533
|
+
|
534
|
+
# Hook used by `Pipeline` to get the class that should be used for composition
|
535
|
+
#
|
536
|
+
# @return [Class]
|
537
|
+
#
|
538
|
+
# @api private
|
539
|
+
def composite_class
|
540
|
+
Relation::Composite
|
541
|
+
end
|
542
|
+
|
543
|
+
# @api private
|
544
|
+
def wrap_class
|
545
|
+
self.class.wrap_class
|
546
|
+
end
|
547
|
+
end
|
548
|
+
end
|