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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +58 -71
- data/LICENSE +1 -1
- data/README.md +7 -6
- data/lib/rom/array_dataset.rb +46 -0
- data/lib/rom/associations/abstract.rb +217 -0
- data/lib/rom/associations/definitions/abstract.rb +150 -0
- data/lib/rom/associations/definitions/many_to_many.rb +29 -0
- data/lib/rom/associations/definitions/many_to_one.rb +14 -0
- data/lib/rom/associations/definitions/one_to_many.rb +14 -0
- data/lib/rom/associations/definitions/one_to_one.rb +14 -0
- data/lib/rom/associations/definitions/one_to_one_through.rb +14 -0
- data/lib/rom/associations/definitions.rb +7 -0
- data/lib/rom/associations/many_to_many.rb +128 -0
- data/lib/rom/associations/many_to_one.rb +65 -0
- data/lib/rom/associations/one_to_many.rb +65 -0
- data/lib/rom/associations/one_to_one.rb +13 -0
- data/lib/rom/associations/one_to_one_through.rb +13 -0
- data/lib/rom/associations/through_identifier.rb +41 -0
- data/lib/rom/attribute.rb +425 -0
- data/lib/rom/auto_curry.rb +70 -0
- data/lib/rom/cache.rb +87 -0
- data/lib/rom/changeset/associated.rb +110 -0
- data/lib/rom/changeset/create.rb +18 -0
- data/lib/rom/changeset/delete.rb +15 -0
- data/lib/rom/changeset/extensions/relation.rb +26 -0
- data/lib/rom/changeset/pipe.rb +81 -0
- data/lib/rom/changeset/pipe_registry.rb +27 -0
- data/lib/rom/changeset/stateful.rb +285 -0
- data/lib/rom/changeset/update.rb +81 -0
- data/lib/rom/changeset.rb +185 -0
- data/lib/rom/command.rb +351 -0
- data/lib/rom/command_compiler.rb +201 -0
- data/lib/rom/command_proxy.rb +36 -0
- data/lib/rom/commands/class_interface.rb +236 -0
- data/lib/rom/commands/composite.rb +55 -0
- data/lib/rom/commands/create.rb +15 -0
- data/lib/rom/commands/delete.rb +16 -0
- data/lib/rom/commands/graph/class_interface.rb +64 -0
- data/lib/rom/commands/graph/input_evaluator.rb +94 -0
- data/lib/rom/commands/graph.rb +88 -0
- data/lib/rom/commands/lazy/create.rb +35 -0
- data/lib/rom/commands/lazy/delete.rb +39 -0
- data/lib/rom/commands/lazy/update.rb +46 -0
- data/lib/rom/commands/lazy.rb +106 -0
- data/lib/rom/commands/update.rb +16 -0
- data/lib/rom/commands.rb +5 -0
- data/lib/rom/compat/auto_registration.rb +115 -0
- data/lib/rom/compat/auto_registration_strategies/base.rb +29 -0
- data/lib/rom/compat/auto_registration_strategies/custom_namespace.rb +84 -0
- data/lib/rom/compat/auto_registration_strategies/no_namespace.rb +33 -0
- data/lib/rom/compat/auto_registration_strategies/with_namespace.rb +29 -0
- data/lib/rom/compat/command.rb +74 -0
- data/lib/rom/compat/components/dsl/schema.rb +130 -0
- data/lib/rom/compat/components.rb +91 -0
- data/lib/rom/compat/global.rb +17 -0
- data/lib/rom/compat/mapper.rb +22 -0
- data/lib/rom/compat/registries.rb +47 -0
- data/lib/rom/compat/relation.rb +40 -0
- data/lib/rom/compat/schema/dsl.rb +260 -0
- data/lib/rom/compat/setting_proxy.rb +44 -0
- data/lib/rom/compat/setup.rb +151 -0
- data/lib/rom/compat/transformer.rb +49 -0
- data/lib/rom/compat.rb +22 -0
- data/lib/rom/components/association.rb +26 -0
- data/lib/rom/components/command.rb +24 -0
- data/lib/rom/components/core.rb +148 -0
- data/lib/rom/components/dataset.rb +60 -0
- data/lib/rom/components/dsl/association.rb +47 -0
- data/lib/rom/components/dsl/command.rb +60 -0
- data/lib/rom/components/dsl/core.rb +126 -0
- data/lib/rom/components/dsl/dataset.rb +33 -0
- data/lib/rom/components/dsl/gateway.rb +14 -0
- data/lib/rom/components/dsl/mapper.rb +70 -0
- data/lib/rom/components/dsl/relation.rb +49 -0
- data/lib/rom/components/dsl/schema.rb +150 -0
- data/lib/rom/components/dsl/view.rb +82 -0
- data/lib/rom/components/dsl.rb +255 -0
- data/lib/rom/components/gateway.rb +50 -0
- data/lib/rom/components/mapper.rb +29 -0
- data/lib/rom/components/provider.rb +160 -0
- data/lib/rom/components/registry.rb +154 -0
- data/lib/rom/components/relation.rb +41 -0
- data/lib/rom/components/schema.rb +61 -0
- data/lib/rom/components/view.rb +55 -0
- data/lib/rom/components.rb +55 -0
- data/lib/rom/configuration_dsl.rb +4 -0
- data/lib/rom/constants.rb +135 -0
- data/lib/rom/container.rb +182 -0
- data/lib/rom/core.rb +125 -0
- data/lib/rom/data_proxy.rb +97 -0
- data/lib/rom/enumerable_dataset.rb +70 -0
- data/lib/rom/gateway.rb +232 -0
- data/lib/rom/global.rb +56 -0
- data/lib/rom/header/attribute.rb +190 -0
- data/lib/rom/header.rb +198 -0
- data/lib/rom/inferrer.rb +55 -0
- data/lib/rom/initializer.rb +80 -0
- data/lib/rom/lint/enumerable_dataset.rb +56 -0
- data/lib/rom/lint/gateway.rb +120 -0
- data/lib/rom/lint/linter.rb +79 -0
- data/lib/rom/lint/spec.rb +22 -0
- data/lib/rom/lint/test.rb +98 -0
- data/lib/rom/loader.rb +161 -0
- data/lib/rom/mapper/attribute_dsl.rb +480 -0
- data/lib/rom/mapper/dsl.rb +107 -0
- data/lib/rom/mapper/model_dsl.rb +61 -0
- data/lib/rom/mapper.rb +99 -0
- data/lib/rom/mapper_compiler.rb +84 -0
- data/lib/rom/memory/associations/many_to_many.rb +12 -0
- data/lib/rom/memory/associations/many_to_one.rb +12 -0
- data/lib/rom/memory/associations/one_to_many.rb +12 -0
- data/lib/rom/memory/associations/one_to_one.rb +12 -0
- data/lib/rom/memory/associations.rb +6 -0
- data/lib/rom/memory/commands.rb +60 -0
- data/lib/rom/memory/dataset.rb +127 -0
- data/lib/rom/memory/gateway.rb +66 -0
- data/lib/rom/memory/mapper_compiler.rb +10 -0
- data/lib/rom/memory/relation.rb +91 -0
- data/lib/rom/memory/schema.rb +32 -0
- data/lib/rom/memory/storage.rb +61 -0
- data/lib/rom/memory/types.rb +11 -0
- data/lib/rom/memory.rb +7 -0
- data/lib/rom/model_builder.rb +103 -0
- data/lib/rom/open_struct.rb +112 -0
- data/lib/rom/pipeline.rb +111 -0
- data/lib/rom/plugin.rb +130 -0
- data/lib/rom/plugins/class_methods.rb +37 -0
- data/lib/rom/plugins/command/schema.rb +45 -0
- data/lib/rom/plugins/command/timestamps.rb +149 -0
- data/lib/rom/plugins/dsl.rb +53 -0
- data/lib/rom/plugins/relation/changeset.rb +97 -0
- data/lib/rom/plugins/relation/instrumentation.rb +66 -0
- data/lib/rom/plugins/relation/registry_reader.rb +36 -0
- data/lib/rom/plugins/schema/timestamps.rb +59 -0
- data/lib/rom/plugins.rb +100 -0
- data/lib/rom/processor/composer.rb +37 -0
- data/lib/rom/processor/transformer.rb +415 -0
- data/lib/rom/processor.rb +30 -0
- data/lib/rom/registries/associations.rb +26 -0
- data/lib/rom/registries/commands.rb +11 -0
- data/lib/rom/registries/container.rb +12 -0
- data/lib/rom/registries/datasets.rb +21 -0
- data/lib/rom/registries/gateways.rb +8 -0
- data/lib/rom/registries/mappers.rb +21 -0
- data/lib/rom/registries/nestable.rb +32 -0
- data/lib/rom/registries/relations.rb +8 -0
- data/lib/rom/registries/root.rb +203 -0
- data/lib/rom/registries/schemas.rb +44 -0
- data/lib/rom/registries/views.rb +11 -0
- data/lib/rom/relation/class_interface.rb +61 -0
- data/lib/rom/relation/combined.rb +160 -0
- data/lib/rom/relation/commands.rb +65 -0
- data/lib/rom/relation/composite.rb +53 -0
- data/lib/rom/relation/curried.rb +129 -0
- data/lib/rom/relation/graph.rb +107 -0
- data/lib/rom/relation/loaded.rb +136 -0
- data/lib/rom/relation/materializable.rb +62 -0
- data/lib/rom/relation/name.rb +122 -0
- data/lib/rom/relation/wrap.rb +64 -0
- data/lib/rom/relation.rb +625 -0
- data/lib/rom/repository/class_interface.rb +162 -0
- data/lib/rom/repository/relation_reader.rb +48 -0
- data/lib/rom/repository/root.rb +75 -0
- data/lib/rom/repository/session.rb +60 -0
- data/lib/rom/repository.rb +179 -0
- data/lib/rom/schema/associations_dsl.rb +222 -0
- data/lib/rom/schema/inferrer.rb +106 -0
- data/lib/rom/schema.rb +471 -0
- data/lib/rom/settings.rb +141 -0
- data/lib/rom/setup.rb +297 -0
- data/lib/rom/struct.rb +99 -0
- data/lib/rom/struct_compiler.rb +114 -0
- data/lib/rom/support/configurable.rb +213 -0
- data/lib/rom/support/inflector.rb +31 -0
- data/lib/rom/support/memoizable.rb +61 -0
- data/lib/rom/support/notifications.rb +238 -0
- data/lib/rom/transaction.rb +26 -0
- data/lib/rom/transformer.rb +46 -0
- data/lib/rom/types.rb +74 -0
- data/lib/rom/version.rb +1 -1
- data/lib/rom-changeset.rb +4 -0
- data/lib/rom-core.rb +3 -0
- data/lib/rom-repository.rb +4 -0
- data/lib/rom.rb +3 -3
- metadata +302 -23
@@ -0,0 +1,425 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/core/equalizer"
|
4
|
+
|
5
|
+
require "rom/initializer"
|
6
|
+
require "rom/support/memoizable"
|
7
|
+
require "rom/types"
|
8
|
+
|
9
|
+
module ROM
|
10
|
+
# Schema attributes provide meta information about types and an API
|
11
|
+
# for additional operations. This class can be extended by adapters to provide
|
12
|
+
# database-specific features. In example rom-sql provides SQL::Attribute
|
13
|
+
# with more features like creating SQL expressions for queries.
|
14
|
+
#
|
15
|
+
# Schema attributes are accessible through canonical relation schemas and
|
16
|
+
# instance-level schemas.
|
17
|
+
#
|
18
|
+
# @api public
|
19
|
+
class Attribute
|
20
|
+
include Dry::Equalizer(:type, :options)
|
21
|
+
include Memoizable
|
22
|
+
|
23
|
+
extend Initializer
|
24
|
+
|
25
|
+
META_OPTIONS = %i[primary_key foreign_key source target relation].freeze
|
26
|
+
|
27
|
+
# @!attribute [r] type
|
28
|
+
# @return [Dry::Types::Nominal, Dry::Types::Sum, Dry::Types::Constrained] The attribute's type object
|
29
|
+
param :type
|
30
|
+
|
31
|
+
# @!attribute [r] name
|
32
|
+
#
|
33
|
+
# Return the canonical name of this attribute name
|
34
|
+
#
|
35
|
+
# This *always* returns the name that is used in the datastore, even when
|
36
|
+
# an attribute is aliased
|
37
|
+
#
|
38
|
+
# @example
|
39
|
+
# class Users < ROM::Relation[:memory]
|
40
|
+
# schema do
|
41
|
+
# attribute :user_id, Types::Integer, alias: :id
|
42
|
+
# attribute :email, Types::String
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# users[:user_id].name
|
47
|
+
# # => :user_id
|
48
|
+
#
|
49
|
+
# users[:email].name
|
50
|
+
# # => :email
|
51
|
+
#
|
52
|
+
# @return [Symbol]
|
53
|
+
#
|
54
|
+
# @api public
|
55
|
+
option :name, optional: true, type: Types::Strict::Symbol
|
56
|
+
|
57
|
+
# @!attribute [r] type
|
58
|
+
# @return [Symbol, nil] Alias to use instead of attribute name
|
59
|
+
option :alias, optional: true, type: Types::Strict::Symbol.optional
|
60
|
+
|
61
|
+
# @api private
|
62
|
+
def [](value = Undefined)
|
63
|
+
type[value]
|
64
|
+
end
|
65
|
+
|
66
|
+
# Return true if this attribute type is a primary key
|
67
|
+
#
|
68
|
+
# @example
|
69
|
+
# class Users < ROM::Relation[:memory]
|
70
|
+
# schema do
|
71
|
+
# attribute :id, Types::Integer
|
72
|
+
# attribute :name, Types::String
|
73
|
+
#
|
74
|
+
# primary_key :id
|
75
|
+
# end
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# Users.schema[:id].primary_key?
|
79
|
+
# # => true
|
80
|
+
#
|
81
|
+
# Users.schema[:name].primary_key?
|
82
|
+
# # => false
|
83
|
+
#
|
84
|
+
# @return [TrueClass,FalseClass]
|
85
|
+
#
|
86
|
+
# @api public
|
87
|
+
def primary_key?
|
88
|
+
meta[:primary_key].equal?(true)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Return true if this attribute type is a foreign key
|
92
|
+
#
|
93
|
+
# @example
|
94
|
+
# class Tasks < ROM::Relation[:memory]
|
95
|
+
# schema do
|
96
|
+
# attribute :id, Types::Integer
|
97
|
+
# attribute :user_id, Types.ForeignKey(:users)
|
98
|
+
# end
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# Users.schema[:user_id].foreign_key?
|
102
|
+
# # => true
|
103
|
+
#
|
104
|
+
# Users.schema[:id].foreign_key?
|
105
|
+
# # => false
|
106
|
+
#
|
107
|
+
# @return [TrueClass,FalseClass]
|
108
|
+
#
|
109
|
+
# @api public
|
110
|
+
def foreign_key?
|
111
|
+
meta[:foreign_key].equal?(true)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Return true if this attribute has a configured alias
|
115
|
+
#
|
116
|
+
# @example
|
117
|
+
# class Tasks < ROM::Relation[:memory]
|
118
|
+
# schema do
|
119
|
+
# attribute :user_id, Types::Integer, alias: :id
|
120
|
+
# attribute :name, Types::String
|
121
|
+
# end
|
122
|
+
# end
|
123
|
+
#
|
124
|
+
# Users.schema[:user_id].aliased?
|
125
|
+
# # => true
|
126
|
+
#
|
127
|
+
# Users.schema[:name].aliased?
|
128
|
+
# # => false
|
129
|
+
#
|
130
|
+
# @return [TrueClass,FalseClass]
|
131
|
+
#
|
132
|
+
# @api public
|
133
|
+
def aliased?
|
134
|
+
!self.alias.nil?
|
135
|
+
end
|
136
|
+
|
137
|
+
# Return source relation of this attribute type
|
138
|
+
#
|
139
|
+
# @example
|
140
|
+
# class Tasks < ROM::Relation[:memory]
|
141
|
+
# schema do
|
142
|
+
# attribute :id, Types::Integer
|
143
|
+
# attribute :user_id, Types.ForeignKey(:users)
|
144
|
+
# end
|
145
|
+
# end
|
146
|
+
#
|
147
|
+
# Users.schema[:id].source
|
148
|
+
# # => :tasks
|
149
|
+
#
|
150
|
+
# Users.schema[:user_id].source
|
151
|
+
# # => :tasks
|
152
|
+
#
|
153
|
+
# @return [Symbol, Relation::Name]
|
154
|
+
#
|
155
|
+
# @api public
|
156
|
+
def source
|
157
|
+
meta[:source]
|
158
|
+
end
|
159
|
+
|
160
|
+
# Return target relation of this attribute type
|
161
|
+
#
|
162
|
+
# @example
|
163
|
+
# class Tasks < ROM::Relation[:memory]
|
164
|
+
# schema do
|
165
|
+
# attribute :id, Types::Integer
|
166
|
+
# attribute :user_id, Types.ForeignKey(:users)
|
167
|
+
# end
|
168
|
+
# end
|
169
|
+
#
|
170
|
+
# Users.schema[:id].target
|
171
|
+
# # => nil
|
172
|
+
#
|
173
|
+
# Users.schema[:user_id].target
|
174
|
+
# # => :users
|
175
|
+
#
|
176
|
+
# @return [NilClass, Symbol, Relation::Name]
|
177
|
+
#
|
178
|
+
# @api public
|
179
|
+
def target
|
180
|
+
meta[:target]
|
181
|
+
end
|
182
|
+
|
183
|
+
# Return tuple key
|
184
|
+
#
|
185
|
+
# When schemas are projected with aliased attributes, we need a simple access to tuple keys
|
186
|
+
#
|
187
|
+
# @example
|
188
|
+
# class Tasks < ROM::Relation[:memory]
|
189
|
+
# schema do
|
190
|
+
# attribute :user_id, Types::Integer, alias: :id
|
191
|
+
# attribute :name, Types::String
|
192
|
+
# end
|
193
|
+
# end
|
194
|
+
#
|
195
|
+
# Users.schema[:id].key
|
196
|
+
# # :id
|
197
|
+
#
|
198
|
+
# Users.schema.project(Users.schema[:id].aliased(:user_id)).key
|
199
|
+
# # :user_id
|
200
|
+
#
|
201
|
+
# @return [Symbol]
|
202
|
+
#
|
203
|
+
# @api public
|
204
|
+
def key
|
205
|
+
self.alias || name
|
206
|
+
end
|
207
|
+
|
208
|
+
# Return new attribute type with provided alias
|
209
|
+
#
|
210
|
+
# @example
|
211
|
+
# class Tasks < ROM::Relation[:memory]
|
212
|
+
# schema do
|
213
|
+
# attribute :user_id, Types::Integer
|
214
|
+
# attribute :name, Types::String
|
215
|
+
# end
|
216
|
+
# end
|
217
|
+
#
|
218
|
+
# aliased_user_id = Users.schema[:user_id].aliased(:id)
|
219
|
+
#
|
220
|
+
# aliased_user_id.aliased?
|
221
|
+
# # => true
|
222
|
+
#
|
223
|
+
# aliased_user_id.name
|
224
|
+
# # => :user_id
|
225
|
+
#
|
226
|
+
# aliased_user_id.alias
|
227
|
+
# # => :id
|
228
|
+
#
|
229
|
+
# @param [Symbol] name The alias
|
230
|
+
#
|
231
|
+
# @return [Attribute]
|
232
|
+
#
|
233
|
+
# @api public
|
234
|
+
def aliased(name)
|
235
|
+
with(alias: name)
|
236
|
+
end
|
237
|
+
alias_method :as, :aliased
|
238
|
+
|
239
|
+
# Return new attribute type with an alias using provided prefix
|
240
|
+
#
|
241
|
+
# @example
|
242
|
+
# class Users < ROM::Relation[:memory]
|
243
|
+
# schema do
|
244
|
+
# attribute :id, Types::Integer
|
245
|
+
# attribute :name, Types::String
|
246
|
+
# end
|
247
|
+
# end
|
248
|
+
#
|
249
|
+
# prefixed_id = Users.schema[:id].prefixed
|
250
|
+
#
|
251
|
+
# prefixed_id.aliased?
|
252
|
+
# # => true
|
253
|
+
#
|
254
|
+
# prefixed_id.name
|
255
|
+
# # => :id
|
256
|
+
#
|
257
|
+
# prefixed_id.alias
|
258
|
+
# # => :users_id
|
259
|
+
#
|
260
|
+
# prefixed_id = Users.schema[:id].prefixed(:user)
|
261
|
+
#
|
262
|
+
# prefixed_id.alias
|
263
|
+
# # => :user_id
|
264
|
+
#
|
265
|
+
# @param [Symbol] prefix The prefix (defaults to source.dataset)
|
266
|
+
#
|
267
|
+
# @return [Attribute]
|
268
|
+
#
|
269
|
+
# @api public
|
270
|
+
def prefixed(prefix = source.dataset)
|
271
|
+
aliased(:"#{prefix}_#{name}")
|
272
|
+
end
|
273
|
+
|
274
|
+
# Return if the attribute type is from a wrapped relation
|
275
|
+
#
|
276
|
+
# Wrapped attributes are used when two schemas from different relations
|
277
|
+
# are merged together. This way we can identify them easily and handle
|
278
|
+
# correctly in places like auto-mapping.
|
279
|
+
#
|
280
|
+
# @api public
|
281
|
+
def wrapped?
|
282
|
+
meta[:wrapped].equal?(true)
|
283
|
+
end
|
284
|
+
|
285
|
+
# Return attribute type wrapped for the specified relation name
|
286
|
+
#
|
287
|
+
# @param [Symbol] name The name of the source relation (defaults to source.dataset)
|
288
|
+
#
|
289
|
+
# @return [Attribute]
|
290
|
+
#
|
291
|
+
# @api public
|
292
|
+
def wrapped(name = source.dataset)
|
293
|
+
prefixed(name).meta(wrapped: true)
|
294
|
+
end
|
295
|
+
|
296
|
+
# Return attribute type with additional meta information
|
297
|
+
#
|
298
|
+
# Return meta information hash if no opts are provided
|
299
|
+
#
|
300
|
+
# @param [Hash] opts The meta options
|
301
|
+
#
|
302
|
+
# @return [Attribute]
|
303
|
+
#
|
304
|
+
# @api public
|
305
|
+
def meta(opts = nil)
|
306
|
+
if opts
|
307
|
+
self.class.new(type.meta(opts), **options)
|
308
|
+
else
|
309
|
+
type.meta
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
# Return string representation of the attribute type
|
314
|
+
#
|
315
|
+
# @return [String]
|
316
|
+
#
|
317
|
+
# @api public
|
318
|
+
def inspect
|
319
|
+
opts = options.reject { |k| %i[type name].include?(k) }
|
320
|
+
meta_and_opts = meta.merge(opts).map { |k, v| "#{k}=#{v.inspect}" }
|
321
|
+
%(#<#{self.class}[#{type.name}] name=#{name.inspect} #{meta_and_opts.join(" ")}>)
|
322
|
+
end
|
323
|
+
alias_method :pretty_inspect, :inspect
|
324
|
+
|
325
|
+
# Check if the attribute type is equal to another
|
326
|
+
#
|
327
|
+
# @param [Dry::Type, Attribute] other
|
328
|
+
#
|
329
|
+
# @return [TrueClass,FalseClass]
|
330
|
+
#
|
331
|
+
# @api public
|
332
|
+
def eql?(other)
|
333
|
+
other.is_a?(self.class) ? super : type.eql?(other)
|
334
|
+
end
|
335
|
+
|
336
|
+
# Return if this attribute type has additional attribute type for reading
|
337
|
+
# tuple values
|
338
|
+
#
|
339
|
+
# @return [TrueClass, FalseClass]
|
340
|
+
#
|
341
|
+
# @api private
|
342
|
+
def read?
|
343
|
+
!meta[:read].nil?
|
344
|
+
end
|
345
|
+
|
346
|
+
# Return read type
|
347
|
+
#
|
348
|
+
# @return [Dry::Types::Type]
|
349
|
+
#
|
350
|
+
# @api private
|
351
|
+
def to_read_type
|
352
|
+
read? ? meta[:read] : type
|
353
|
+
end
|
354
|
+
|
355
|
+
# Return write type
|
356
|
+
#
|
357
|
+
# @return [Dry::Types::Type]
|
358
|
+
#
|
359
|
+
# @api private
|
360
|
+
def to_write_type
|
361
|
+
type
|
362
|
+
end
|
363
|
+
|
364
|
+
# Return nullable attribute
|
365
|
+
#
|
366
|
+
# @return [Attribute]
|
367
|
+
#
|
368
|
+
# @api public
|
369
|
+
def optional
|
370
|
+
sum = self.class.new(super, **options)
|
371
|
+
read? ? sum.meta(read: meta[:read].optional) : sum
|
372
|
+
end
|
373
|
+
|
374
|
+
# @api private
|
375
|
+
def respond_to_missing?(name, include_private = false)
|
376
|
+
type.respond_to?(name) || super
|
377
|
+
end
|
378
|
+
|
379
|
+
# Return AST for the type
|
380
|
+
#
|
381
|
+
# @return [Array]
|
382
|
+
#
|
383
|
+
# @api public
|
384
|
+
def to_ast
|
385
|
+
[:attribute, [name, type.to_ast(meta: false), meta_options_ast]]
|
386
|
+
end
|
387
|
+
|
388
|
+
# Return AST for the read type
|
389
|
+
#
|
390
|
+
# @return [Array]
|
391
|
+
#
|
392
|
+
# @api public
|
393
|
+
def to_read_ast
|
394
|
+
[:attribute, [name, to_read_type.to_ast(meta: false), meta_options_ast]]
|
395
|
+
end
|
396
|
+
|
397
|
+
# @api private
|
398
|
+
def meta_options_ast
|
399
|
+
keys = %i[wrapped primary_key alias]
|
400
|
+
ast = meta.merge(options).select { |k, _| keys.include?(k) }
|
401
|
+
ast[:source] = source.to_sym if source
|
402
|
+
ast
|
403
|
+
end
|
404
|
+
|
405
|
+
memoize :to_ast, :to_read_ast, :meta_options_ast
|
406
|
+
|
407
|
+
private
|
408
|
+
|
409
|
+
# @api private
|
410
|
+
def method_missing(meth, *args, &block)
|
411
|
+
if type.respond_to?(meth)
|
412
|
+
response = type.__send__(meth, *args, &block)
|
413
|
+
|
414
|
+
if response.is_a?(type.class)
|
415
|
+
self.class.new(response, **options)
|
416
|
+
else
|
417
|
+
response
|
418
|
+
end
|
419
|
+
else
|
420
|
+
super
|
421
|
+
end
|
422
|
+
end
|
423
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
424
|
+
end
|
425
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
# Relation extension which provides auto-currying of relation view methods
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
module AutoCurry
|
8
|
+
def self.extended(klass)
|
9
|
+
klass.define_singleton_method(:method_added) do |name|
|
10
|
+
return if auto_curry_busy?
|
11
|
+
|
12
|
+
auto_curry_guard { auto_curry(name) }
|
13
|
+
super(name)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# @api private
|
18
|
+
def auto_curry_guard
|
19
|
+
@__auto_curry_busy__ = true
|
20
|
+
yield
|
21
|
+
ensure
|
22
|
+
@__auto_curry_busy__ = false
|
23
|
+
end
|
24
|
+
|
25
|
+
# @api private
|
26
|
+
def auto_curry_busy?
|
27
|
+
@__auto_curry_busy__ ||= false
|
28
|
+
end
|
29
|
+
|
30
|
+
# @api private
|
31
|
+
def auto_curried_methods
|
32
|
+
@__auto_curried_methods__ ||= Set.new
|
33
|
+
end
|
34
|
+
|
35
|
+
# Auto-curry a method
|
36
|
+
#
|
37
|
+
# @param [Symbol] name The name of a method
|
38
|
+
#
|
39
|
+
# @api private
|
40
|
+
def auto_curry(name, &block)
|
41
|
+
arity = instance_method(name).arity
|
42
|
+
|
43
|
+
return unless public_instance_methods.include?(name) && arity != 0
|
44
|
+
|
45
|
+
mod = Module.new
|
46
|
+
|
47
|
+
mod.module_eval do
|
48
|
+
define_method(name) do |*args, &mblock|
|
49
|
+
response =
|
50
|
+
if arity.negative? || arity == args.size
|
51
|
+
super(*args, &mblock)
|
52
|
+
else
|
53
|
+
self.class.curried.new(self, view: name, curry_args: args, arity: arity)
|
54
|
+
end
|
55
|
+
|
56
|
+
if block
|
57
|
+
response.instance_exec(&block)
|
58
|
+
else
|
59
|
+
response
|
60
|
+
end
|
61
|
+
end
|
62
|
+
ruby2_keywords(name) if respond_to?(:ruby2_keywords, true)
|
63
|
+
end
|
64
|
+
|
65
|
+
auto_curried_methods << name
|
66
|
+
|
67
|
+
prepend(mod)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/lib/rom/cache.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "concurrent/map"
|
4
|
+
|
5
|
+
module ROM
|
6
|
+
# Thread-safe cache used by various rom components
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
class Cache
|
10
|
+
attr_reader :objects
|
11
|
+
|
12
|
+
# @api private
|
13
|
+
class Namespaced
|
14
|
+
# @api private
|
15
|
+
attr_reader :cache
|
16
|
+
|
17
|
+
# @api private
|
18
|
+
attr_reader :namespace
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
attr_reader :parent
|
22
|
+
|
23
|
+
# @api private
|
24
|
+
def initialize(cache, namespace, parent:)
|
25
|
+
@cache = cache
|
26
|
+
@namespace = namespace
|
27
|
+
@parent = parent
|
28
|
+
end
|
29
|
+
|
30
|
+
# @api private
|
31
|
+
def namespaced(namespace)
|
32
|
+
parent.namespaced(namespace)
|
33
|
+
end
|
34
|
+
|
35
|
+
# @api private
|
36
|
+
def [](key)
|
37
|
+
cache[[namespace, key].hash]
|
38
|
+
end
|
39
|
+
|
40
|
+
# @api private
|
41
|
+
def fetch_or_store(*args, &block)
|
42
|
+
cache.fetch_or_store([namespace, args.hash].hash, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
# @api private
|
46
|
+
def size
|
47
|
+
cache.size
|
48
|
+
end
|
49
|
+
|
50
|
+
# @api private
|
51
|
+
def inspect
|
52
|
+
%(#<#{self.class} size=#{size}>)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# @api private
|
57
|
+
def initialize
|
58
|
+
@objects = Concurrent::Map.new
|
59
|
+
@namespaced = {}
|
60
|
+
end
|
61
|
+
|
62
|
+
# @api private
|
63
|
+
def [](key)
|
64
|
+
cache[key]
|
65
|
+
end
|
66
|
+
|
67
|
+
# @api private
|
68
|
+
def fetch_or_store(*args, &block)
|
69
|
+
objects.fetch_or_store(args.hash, &block)
|
70
|
+
end
|
71
|
+
|
72
|
+
# @api private
|
73
|
+
def size
|
74
|
+
objects.size
|
75
|
+
end
|
76
|
+
|
77
|
+
# @api private
|
78
|
+
def namespaced(namespace)
|
79
|
+
@namespaced[namespace] ||= Namespaced.new(objects, namespace, parent: self)
|
80
|
+
end
|
81
|
+
|
82
|
+
# @api private
|
83
|
+
def inspect
|
84
|
+
%(#<#{self.class} size=#{size} namespaced=#{@namespaced.inspect}>)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rom/initializer"
|
4
|
+
|
5
|
+
module ROM
|
6
|
+
class Changeset
|
7
|
+
# Associated changesets automatically set up FKs
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
class Associated
|
11
|
+
extend Initializer
|
12
|
+
|
13
|
+
# @!attribute [r] left
|
14
|
+
# @return [Changeset::Create] Child changeset
|
15
|
+
param :left
|
16
|
+
|
17
|
+
# @!attribute [r] associations
|
18
|
+
# @return [Array] List of association identifiers from relation schema
|
19
|
+
option :associations
|
20
|
+
|
21
|
+
# Infer association name from an object with a schema
|
22
|
+
#
|
23
|
+
# This expects other to be an object with a schema that includes a primary key
|
24
|
+
# attribute with :source meta information. This makes it work with both structs
|
25
|
+
# and relations
|
26
|
+
#
|
27
|
+
# @see Stateful#associate
|
28
|
+
#
|
29
|
+
# @api private
|
30
|
+
def self.infer_assoc_name(other)
|
31
|
+
schema = other.class.schema
|
32
|
+
attrs = schema.is_a?(Hash) ? schema.values : schema
|
33
|
+
pk = attrs.detect { |attr| attr.meta[:primary_key] }
|
34
|
+
|
35
|
+
if pk
|
36
|
+
pk.meta[:source]
|
37
|
+
else
|
38
|
+
raise ArgumentError, "can't infer association name for #{other}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Commit changeset's composite command
|
43
|
+
#
|
44
|
+
# @example
|
45
|
+
# task_changeset = tasks.
|
46
|
+
# changeset(title: 'Task One').
|
47
|
+
# associate(user, :user).
|
48
|
+
# commit
|
49
|
+
# # {:id => 1, :user_id => 1, title: 'Task One'}
|
50
|
+
#
|
51
|
+
# @return [Array<Hash>, Hash]
|
52
|
+
#
|
53
|
+
# @api public
|
54
|
+
def commit
|
55
|
+
command.call
|
56
|
+
end
|
57
|
+
|
58
|
+
# Associate with other changesets
|
59
|
+
#
|
60
|
+
# @see Changeset#associate
|
61
|
+
#
|
62
|
+
# @return [Associated]
|
63
|
+
#
|
64
|
+
# @api public
|
65
|
+
def associate(other, name = Associated.infer_assoc_name(other))
|
66
|
+
self.class.new(left, associations: associations.merge(name => other))
|
67
|
+
end
|
68
|
+
|
69
|
+
# Create a composed command
|
70
|
+
#
|
71
|
+
# @example using existing parent data
|
72
|
+
# user_changeset = users.changeset(name: 'Jane')
|
73
|
+
# task_changeset = tasks.changeset(title: 'Task One')
|
74
|
+
#
|
75
|
+
# user = users.create(user_changeset)
|
76
|
+
# task = tasks.create(task_changeset.associate(user, :user))
|
77
|
+
#
|
78
|
+
# @example saving both parent and child in one go
|
79
|
+
# user_changeset = users.changeset(name: 'Jane')
|
80
|
+
# task_changeset = tasks.changeset(title: 'Task One')
|
81
|
+
#
|
82
|
+
# task = tasks.create(task_changeset.associate(user, :user))
|
83
|
+
#
|
84
|
+
# This works *only* with parent => child(ren) changeset hierarchy
|
85
|
+
#
|
86
|
+
# @return [ROM::Command::Composite]
|
87
|
+
#
|
88
|
+
# @api public
|
89
|
+
def command
|
90
|
+
associations.reduce(left.command.curry(left)) do |a, (assoc, other)|
|
91
|
+
case other
|
92
|
+
when Changeset
|
93
|
+
a >> other.command.with_association(assoc).curry(other)
|
94
|
+
when Associated
|
95
|
+
a >> other.command.with_association(assoc)
|
96
|
+
when Array
|
97
|
+
raise NotImplementedError, "Changeset::Associate does not support arrays yet"
|
98
|
+
else
|
99
|
+
a.with_association(assoc, parent: other)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# @api private
|
105
|
+
def relation
|
106
|
+
left.relation
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
class Changeset
|
5
|
+
# Changeset specialization for create commands
|
6
|
+
#
|
7
|
+
# @see Changeset::Stateful
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
class Create < Stateful
|
11
|
+
command_type :create
|
12
|
+
|
13
|
+
def command
|
14
|
+
super.new(relation)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
class Changeset
|
5
|
+
# Changeset specialization for delete commands
|
6
|
+
#
|
7
|
+
# Delete changesets will execute delete command for its relation, which
|
8
|
+
# means proper restricted relations should be used with this changeset.
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
class Delete < Changeset
|
12
|
+
command_type :delete
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|