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.
Files changed (122) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +603 -0
  3. data/LICENSE +20 -0
  4. data/README.md +18 -0
  5. data/lib/rom-core.rb +1 -0
  6. data/lib/rom/array_dataset.rb +44 -0
  7. data/lib/rom/association_set.rb +16 -0
  8. data/lib/rom/associations/abstract.rb +135 -0
  9. data/lib/rom/associations/definitions.rb +5 -0
  10. data/lib/rom/associations/definitions/abstract.rb +116 -0
  11. data/lib/rom/associations/definitions/many_to_many.rb +24 -0
  12. data/lib/rom/associations/definitions/many_to_one.rb +11 -0
  13. data/lib/rom/associations/definitions/one_to_many.rb +11 -0
  14. data/lib/rom/associations/definitions/one_to_one.rb +11 -0
  15. data/lib/rom/associations/definitions/one_to_one_through.rb +11 -0
  16. data/lib/rom/associations/many_to_many.rb +81 -0
  17. data/lib/rom/associations/many_to_one.rb +37 -0
  18. data/lib/rom/associations/one_to_many.rb +37 -0
  19. data/lib/rom/associations/one_to_one.rb +8 -0
  20. data/lib/rom/associations/one_to_one_through.rb +8 -0
  21. data/lib/rom/associations/through_identifier.rb +39 -0
  22. data/lib/rom/auto_curry.rb +55 -0
  23. data/lib/rom/cache.rb +46 -0
  24. data/lib/rom/command.rb +488 -0
  25. data/lib/rom/command_compiler.rb +239 -0
  26. data/lib/rom/command_proxy.rb +24 -0
  27. data/lib/rom/command_registry.rb +141 -0
  28. data/lib/rom/commands.rb +3 -0
  29. data/lib/rom/commands/class_interface.rb +270 -0
  30. data/lib/rom/commands/composite.rb +53 -0
  31. data/lib/rom/commands/create.rb +13 -0
  32. data/lib/rom/commands/delete.rb +14 -0
  33. data/lib/rom/commands/graph.rb +88 -0
  34. data/lib/rom/commands/graph/class_interface.rb +62 -0
  35. data/lib/rom/commands/graph/input_evaluator.rb +62 -0
  36. data/lib/rom/commands/lazy.rb +99 -0
  37. data/lib/rom/commands/lazy/create.rb +23 -0
  38. data/lib/rom/commands/lazy/delete.rb +27 -0
  39. data/lib/rom/commands/lazy/update.rb +34 -0
  40. data/lib/rom/commands/result.rb +96 -0
  41. data/lib/rom/commands/update.rb +14 -0
  42. data/lib/rom/configuration.rb +114 -0
  43. data/lib/rom/configuration_dsl.rb +87 -0
  44. data/lib/rom/configuration_dsl/command.rb +41 -0
  45. data/lib/rom/configuration_dsl/command_dsl.rb +35 -0
  46. data/lib/rom/configuration_dsl/relation.rb +26 -0
  47. data/lib/rom/configuration_plugin.rb +17 -0
  48. data/lib/rom/constants.rb +64 -0
  49. data/lib/rom/container.rb +147 -0
  50. data/lib/rom/core.rb +46 -0
  51. data/lib/rom/create_container.rb +60 -0
  52. data/lib/rom/data_proxy.rb +94 -0
  53. data/lib/rom/enumerable_dataset.rb +68 -0
  54. data/lib/rom/environment.rb +70 -0
  55. data/lib/rom/gateway.rb +184 -0
  56. data/lib/rom/global.rb +58 -0
  57. data/lib/rom/global/plugin_dsl.rb +47 -0
  58. data/lib/rom/initializer.rb +64 -0
  59. data/lib/rom/lint/enumerable_dataset.rb +54 -0
  60. data/lib/rom/lint/gateway.rb +120 -0
  61. data/lib/rom/lint/linter.rb +78 -0
  62. data/lib/rom/lint/spec.rb +20 -0
  63. data/lib/rom/lint/test.rb +98 -0
  64. data/lib/rom/mapper_registry.rb +24 -0
  65. data/lib/rom/memory.rb +4 -0
  66. data/lib/rom/memory/associations.rb +4 -0
  67. data/lib/rom/memory/associations/many_to_many.rb +10 -0
  68. data/lib/rom/memory/associations/many_to_one.rb +10 -0
  69. data/lib/rom/memory/associations/one_to_many.rb +10 -0
  70. data/lib/rom/memory/associations/one_to_one.rb +10 -0
  71. data/lib/rom/memory/commands.rb +56 -0
  72. data/lib/rom/memory/dataset.rb +97 -0
  73. data/lib/rom/memory/gateway.rb +64 -0
  74. data/lib/rom/memory/relation.rb +62 -0
  75. data/lib/rom/memory/schema.rb +23 -0
  76. data/lib/rom/memory/storage.rb +59 -0
  77. data/lib/rom/memory/types.rb +9 -0
  78. data/lib/rom/pipeline.rb +105 -0
  79. data/lib/rom/plugin.rb +25 -0
  80. data/lib/rom/plugin_base.rb +45 -0
  81. data/lib/rom/plugin_registry.rb +197 -0
  82. data/lib/rom/plugins/command/schema.rb +37 -0
  83. data/lib/rom/plugins/configuration/configuration_dsl.rb +21 -0
  84. data/lib/rom/plugins/relation/instrumentation.rb +51 -0
  85. data/lib/rom/plugins/relation/registry_reader.rb +44 -0
  86. data/lib/rom/plugins/schema/timestamps.rb +58 -0
  87. data/lib/rom/registry.rb +71 -0
  88. data/lib/rom/relation.rb +548 -0
  89. data/lib/rom/relation/class_interface.rb +282 -0
  90. data/lib/rom/relation/commands.rb +23 -0
  91. data/lib/rom/relation/composite.rb +46 -0
  92. data/lib/rom/relation/curried.rb +103 -0
  93. data/lib/rom/relation/graph.rb +197 -0
  94. data/lib/rom/relation/loaded.rb +127 -0
  95. data/lib/rom/relation/materializable.rb +66 -0
  96. data/lib/rom/relation/name.rb +111 -0
  97. data/lib/rom/relation/view_dsl.rb +64 -0
  98. data/lib/rom/relation/wrap.rb +83 -0
  99. data/lib/rom/relation_registry.rb +10 -0
  100. data/lib/rom/schema.rb +437 -0
  101. data/lib/rom/schema/associations_dsl.rb +195 -0
  102. data/lib/rom/schema/attribute.rb +419 -0
  103. data/lib/rom/schema/dsl.rb +164 -0
  104. data/lib/rom/schema/inferrer.rb +66 -0
  105. data/lib/rom/schema_plugin.rb +27 -0
  106. data/lib/rom/setup.rb +68 -0
  107. data/lib/rom/setup/auto_registration.rb +74 -0
  108. data/lib/rom/setup/auto_registration_strategies/base.rb +16 -0
  109. data/lib/rom/setup/auto_registration_strategies/custom_namespace.rb +63 -0
  110. data/lib/rom/setup/auto_registration_strategies/no_namespace.rb +20 -0
  111. data/lib/rom/setup/auto_registration_strategies/with_namespace.rb +18 -0
  112. data/lib/rom/setup/finalize.rb +103 -0
  113. data/lib/rom/setup/finalize/finalize_commands.rb +60 -0
  114. data/lib/rom/setup/finalize/finalize_mappers.rb +56 -0
  115. data/lib/rom/setup/finalize/finalize_relations.rb +135 -0
  116. data/lib/rom/support/configurable.rb +85 -0
  117. data/lib/rom/support/memoizable.rb +58 -0
  118. data/lib/rom/support/notifications.rb +103 -0
  119. data/lib/rom/transaction.rb +24 -0
  120. data/lib/rom/types.rb +26 -0
  121. data/lib/rom/version.rb +5 -0
  122. metadata +289 -0
@@ -0,0 +1,64 @@
1
+ module ROM
2
+ class Relation
3
+ # ViewDSL is exposed in `Relation.view` method
4
+ #
5
+ # This is used to establish pre-defined relation views with explicit schemas.
6
+ # Such views can be used to compose relations together, even from multiple
7
+ # adapters.
8
+ #
9
+ # @api public
10
+ class ViewDSL
11
+ # @!attribute [r] name
12
+ # @return [Symbol] The view name (relation method)
13
+ attr_reader :name
14
+
15
+ # @!attribute [r] relation_block
16
+ # @return [Proc] The relation block that will be evaluated by the view method
17
+ attr_reader :relation_block
18
+
19
+ # @!attribute [r] new_schema
20
+ # @return [Proc] The schema proc returned by the schema DSL
21
+ attr_reader :new_schema
22
+
23
+ # @api private
24
+ def initialize(name, schema, &block)
25
+ @name = name
26
+ @schema = schema
27
+ @new_schema = nil
28
+ @relation_block = nil
29
+ instance_eval(&block)
30
+ end
31
+
32
+ # Define a schema for a relation view
33
+ #
34
+ # @return [Proc]
35
+ #
36
+ # @see Relation::ClassInterface.view
37
+ #
38
+ # @api public
39
+ def schema(&block)
40
+ @new_schema = -> relations { @schema.with(relations: relations).instance_exec(&block) }
41
+ end
42
+
43
+ # Define a relation block for a relation view
44
+ #
45
+ # @return [Proc]
46
+ #
47
+ # @see Relation::ClassInterface.view
48
+ #
49
+ # @api public
50
+ def relation(&block)
51
+ @relation_block = lambda(&block)
52
+ end
53
+
54
+ # Return procs captured by the DSL
55
+ #
56
+ # @return [Array]
57
+ #
58
+ # @api private
59
+ def call
60
+ [name, new_schema, relation_block]
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,83 @@
1
+ require 'rom/initializer'
2
+ require 'rom/relation/loaded'
3
+ require 'rom/relation/composite'
4
+ require 'rom/relation/materializable'
5
+ require 'rom/pipeline'
6
+
7
+ module ROM
8
+ class Relation
9
+ # Relation wrapping other relations
10
+ #
11
+ # @api public
12
+ class Wrap
13
+ extend Initializer
14
+
15
+ include Materializable
16
+ include Pipeline
17
+ include Pipeline::Proxy
18
+
19
+ param :root
20
+ param :nodes
21
+
22
+ alias_method :left, :root
23
+ alias_method :right, :nodes
24
+
25
+ # @api public
26
+ def wrap(*args)
27
+ self.class.new(root, nodes + root.wrap(*args).nodes)
28
+ end
29
+
30
+ # @see Relation#call
31
+ #
32
+ # @api public
33
+ def call(*args)
34
+ if auto_map?
35
+ Loaded.new(self, mapper.(relation.with(auto_struct: false)))
36
+ else
37
+ Loaded.new(self, relation.(*args))
38
+ end
39
+ end
40
+
41
+ # @api private
42
+ def relation
43
+ raise NotImplementedError
44
+ end
45
+
46
+ # @api private
47
+ def to_ast
48
+ @__ast__ ||= [:relation, [name.relation, attr_ast + nodes_ast, meta_ast]]
49
+ end
50
+
51
+ # @api private
52
+ def attr_ast
53
+ root.attr_ast
54
+ end
55
+
56
+ # @api private
57
+ def nodes_ast
58
+ nodes.map(&:to_ast)
59
+ end
60
+
61
+ # @api private
62
+ def mapper
63
+ mappers[to_ast]
64
+ end
65
+
66
+ # Return if this is a wrap relation
67
+ #
68
+ # @return [true]
69
+ #
70
+ # @api private
71
+ def wrap?
72
+ true
73
+ end
74
+
75
+ private
76
+
77
+ # @api private
78
+ def decorate?(other)
79
+ super || other.is_a?(Composite) || other.is_a?(Curried)
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,10 @@
1
+ require 'rom/registry'
2
+
3
+ module ROM
4
+ class RelationRegistry < Registry
5
+ def initialize(elements = {}, options = {})
6
+ super
7
+ yield(self, elements) if block_given?
8
+ end
9
+ end
10
+ end
data/lib/rom/schema.rb ADDED
@@ -0,0 +1,437 @@
1
+ require 'dry/equalizer'
2
+
3
+ require 'rom/schema/attribute'
4
+ require 'rom/schema/dsl'
5
+ require 'rom/schema/inferrer'
6
+ require 'rom/association_set'
7
+ require 'rom/support/notifications'
8
+ require 'rom/support/memoizable'
9
+
10
+ module ROM
11
+ # Relation schema
12
+ #
13
+ # Schemas hold detailed information about relation tuples, including their
14
+ # primitive types (String, Integer, Hash, etc. or custom classes), as well as
15
+ # various meta information like primary/foreign key and literally any other
16
+ # information that a given database adapter may need.
17
+ #
18
+ # Adapters can extend this class and it can be used in adapter-specific relations.
19
+ # In example rom-sql extends schema with Association DSL and many additional
20
+ # SQL-specific APIs in schema types.
21
+ #
22
+ # Schemas are used for projecting canonical relations into other relations and
23
+ # every relation object maintains its schema. This means that we always have
24
+ # all information about relation tuples, even when a relation was projected and
25
+ # diverged from its canonical form.
26
+ #
27
+ # Furthermore schema attributes know their source relations, which makes it
28
+ # possible to merge schemas from multiple relations and maintain information
29
+ # about the source relations. In example when two relations are joined, their
30
+ # schemas are merged, and we know which attributes belong to which relation.
31
+ #
32
+ # @api public
33
+ class Schema
34
+ include Memoizable
35
+
36
+ extend Notifications::Listener
37
+
38
+ subscribe('configuration.relations.registry.created') do |event|
39
+ registry = event[:registry]
40
+
41
+ registry.each do |_, relation|
42
+ relation.schema.finalize_associations!(relations: registry)
43
+ relation.schema.finalize!
44
+ end
45
+ end
46
+
47
+ subscribe('configuration.relations.schema.allocated') do |event|
48
+ schema = event[:schema]
49
+ registry = event[:registry]
50
+ gateway = event[:gateway]
51
+
52
+ unless schema.frozen?
53
+ schema.finalize_attributes!(gateway: gateway, relations: registry)
54
+ end
55
+ end
56
+
57
+ EMPTY_ASSOCIATION_SET = AssociationSet.new(EMPTY_HASH).freeze
58
+
59
+ DEFAULT_INFERRER = Inferrer.new(enabled: false).freeze
60
+
61
+ AttributeAlreadyDefinedError = Class.new(StandardError)
62
+
63
+ extend Initializer
64
+
65
+ include Dry::Equalizer(:name, :attributes, :associations)
66
+ include Enumerable
67
+
68
+ # @!attribute [r] name
69
+ # @return [Symbol] The name of this schema
70
+ param :name
71
+
72
+ # @!attribute [r] attributes
73
+ # @return [Array] Array with schema attributes
74
+ option :attributes, default: -> { EMPTY_ARRAY }
75
+
76
+ # @!attribute [r] associations
77
+ # @return [AssociationSet] Optional association set (this is adapter-specific)
78
+ option :associations, default: -> { EMPTY_ASSOCIATION_SET }
79
+
80
+ # @!attribute [r] inferrer
81
+ # @return [#call] An optional inferrer object used in `finalize!`
82
+ option :inferrer, default: -> { DEFAULT_INFERRER }
83
+
84
+ # @api private
85
+ option :relations, default: -> { EMPTY_HASH }
86
+
87
+ # @api private
88
+ option :attr_class, default: -> { Attribute }
89
+
90
+ # @!attribute [r] primary_key_name
91
+ # @return [Symbol] The name of the primary key. This is set because in
92
+ # most of the cases relations don't have composite pks
93
+ attr_reader :primary_key_name
94
+
95
+ # @!attribute [r] primary_key_names
96
+ # @return [Array<Symbol>] A list of all pk names
97
+ attr_reader :primary_key_names
98
+
99
+ alias_method :to_ary, :attributes
100
+
101
+ # Define a relation schema from plain rom types
102
+ #
103
+ # Resulting schema will decorate plain rom types with adapter-specific types
104
+ # By default `Schema::Attribute` will be used
105
+ #
106
+ # @param [Relation::Name, Symbol] name The schema name, typically ROM::Relation::Name
107
+ #
108
+ # @return [Schema]
109
+ #
110
+ # @api public
111
+ def self.define(name, attributes: EMPTY_ARRAY, attr_class: Attribute, **options)
112
+ new(
113
+ name,
114
+ attributes: attributes(attributes, attr_class),
115
+ **options
116
+ ) { |schema| yield(schema) if block_given? }
117
+ end
118
+
119
+ # @api private
120
+ def self.attributes(attributes, attr_class)
121
+ attributes.map { |type| attr_class.new(type) }
122
+ end
123
+
124
+ # @api private
125
+ def initialize(*)
126
+ super
127
+
128
+ yield(self) if block_given?
129
+ end
130
+
131
+ # Abstract method for creating a new relation based on schema definition
132
+ #
133
+ # This can be used by views to generate a new relation automatically.
134
+ # In example a schema can project a relation, join any additional relations
135
+ # if it uncludes attributes from other relations etc.
136
+ #
137
+ # Default implementation is a no-op and it simply returns back untouched relation
138
+ #
139
+ # @param [Relation]
140
+ #
141
+ # @return [Relation]
142
+ #
143
+ # @api public
144
+ def call(relation)
145
+ relation
146
+ end
147
+
148
+ # Iterate over schema's attributes
149
+ #
150
+ # @yield [Schema::Attribute]
151
+ #
152
+ # @api public
153
+ def each(&block)
154
+ attributes.each(&block)
155
+ end
156
+
157
+ # Check if schema has any attributes
158
+ #
159
+ # @return [TrueClass, FalseClass]
160
+ #
161
+ # @api public
162
+ def empty?
163
+ attributes.size == 0
164
+ end
165
+
166
+ # Coerce schema into a <AttributeName=>Attribute> Hash
167
+ #
168
+ # @return [Hash]
169
+ #
170
+ # @api public
171
+ def to_h
172
+ each_with_object({}) { |attr, h| h[attr.name] = attr }
173
+ end
174
+
175
+ # Return attribute
176
+ #
177
+ # @param [Symbol] key The attribute name
178
+ # @param [Symbol, Relation::Name] src The source relation (for merged schemas)
179
+ #
180
+ # @raise KeyError
181
+ #
182
+ # @api public
183
+ def [](key, src = name.to_sym)
184
+ attr =
185
+ if count_index[key].equal?(1)
186
+ name_index[key]
187
+ else
188
+ source_index[src][key]
189
+ end
190
+
191
+ unless attr
192
+ raise(KeyError, "#{key.inspect} attribute doesn't exist in #{src} schema")
193
+ end
194
+
195
+ attr
196
+ end
197
+
198
+ # Project a schema to include only specified attributes
199
+ #
200
+ # @param [*Array<Symbol, Schema::Attribute>] names Attribute names
201
+ #
202
+ # @return [Schema]
203
+ #
204
+ # @api public
205
+ def project(*names)
206
+ new(names.map { |name| name.is_a?(Symbol) ? self[name] : name })
207
+ end
208
+
209
+ # Exclude provided attributes from a schema
210
+ #
211
+ # @param [*Array] names Attribute names
212
+ #
213
+ # @return [Schema]
214
+ #
215
+ # @api public
216
+ def exclude(*names)
217
+ project(*(map(&:name) - names))
218
+ end
219
+
220
+ # Project a schema with renamed attributes
221
+ #
222
+ # @param [Hash] mapping The attribute mappings
223
+ #
224
+ # @return [Schema]
225
+ #
226
+ # @api public
227
+ def rename(mapping)
228
+ new_attributes = map do |attr|
229
+ alias_name = mapping[attr.name]
230
+ alias_name ? attr.aliased(alias_name) : attr
231
+ end
232
+
233
+ new(new_attributes)
234
+ end
235
+
236
+ # Project a schema with renamed attributes using provided prefix
237
+ #
238
+ # @param [Symbol] prefix The name of the prefix
239
+ #
240
+ # @return [Schema]
241
+ #
242
+ # @api public
243
+ def prefix(prefix)
244
+ new(map { |attr| attr.prefixed(prefix) })
245
+ end
246
+
247
+ # Return new schema with all attributes marked as prefixed and wrapped
248
+ #
249
+ # This is useful when relations are joined and the right side should be marked
250
+ # as wrapped
251
+ #
252
+ # @param [Symbol] prefix The prefix used for aliasing wrapped attributes
253
+ #
254
+ # @return [Schema]
255
+ #
256
+ # @api public
257
+ def wrap(prefix = name.dataset)
258
+ new(map { |attr| attr.wrapped? ? attr : attr.wrapped(prefix) })
259
+ end
260
+
261
+ # Return FK attribute for a given relation name
262
+ #
263
+ # @return [Schema::Attribute]
264
+ #
265
+ # @api public
266
+ def foreign_key(relation)
267
+ detect { |attr| attr.foreign_key? && attr.target == relation }
268
+ end
269
+
270
+ # Return primary key attributes
271
+ #
272
+ # @return [Array<Schema::Attribute>]
273
+ #
274
+ # @api public
275
+ def primary_key
276
+ select(&:primary_key?)
277
+ end
278
+
279
+ # Merge with another schema
280
+ #
281
+ # @param [Schema] other Other schema
282
+ #
283
+ # @return [Schema]
284
+ #
285
+ # @api public
286
+ def merge(other)
287
+ append(*other)
288
+ end
289
+ alias_method :+, :merge
290
+
291
+ # Append more attributes to the schema
292
+ #
293
+ # This returns a new schema instance
294
+ #
295
+ # @param [*Array<Schema::Attribute>]
296
+ #
297
+ # @return [Schema]
298
+ #
299
+ # @api public
300
+ def append(*new_attributes)
301
+ new(attributes + new_attributes)
302
+ end
303
+
304
+ # Return a new schema with uniq attributes
305
+ #
306
+ # @param [*Array<Schema::Attribute>]
307
+ #
308
+ # @return [Schema]
309
+ #
310
+ # @api public
311
+ def uniq(&block)
312
+ if block
313
+ new(attributes.uniq(&block))
314
+ else
315
+ new(attributes.uniq(&:name))
316
+ end
317
+ end
318
+
319
+ # Return if a schema includes an attribute with the given name
320
+ #
321
+ # @param [Symbol] name The name of the attribute
322
+ #
323
+ # @return [Boolean]
324
+ #
325
+ # @api public
326
+ def key?(name)
327
+ ! attributes.detect { |attr| attr.name == name }.nil?
328
+ end
329
+
330
+ # @api private
331
+ def finalize!(**opts)
332
+ return self if frozen?
333
+ freeze
334
+ end
335
+
336
+ # This hook is called when relation is being build during container finalization
337
+ #
338
+ # When block is provided it'll be called just before freezing the instance
339
+ # so that additional ivars can be set
340
+ #
341
+ # @return [self]
342
+ #
343
+ # @api private
344
+ def finalize_attributes!(gateway: nil, relations: nil)
345
+ inferrer.(self, gateway).each { |key, value| set!(key, value) }
346
+
347
+ yield if block_given?
348
+
349
+ initialize_primary_key_names
350
+
351
+ self
352
+ end
353
+
354
+ # @api private
355
+ def finalize_associations!(relations:)
356
+ set!(:associations, yield) if associations.any?
357
+ self
358
+ end
359
+
360
+ # Return coercion function using attribute read types
361
+ #
362
+ # This is used for `output_schema` in relations
363
+ #
364
+ # @return [Dry::Types::Hash]
365
+ #
366
+ # @api private
367
+ def to_output_hash
368
+ Types::Coercible::Hash.schema(
369
+ map { |attr| [attr.key, attr.to_read_type] }.to_h
370
+ )
371
+ end
372
+
373
+ # Return coercion function using attribute types
374
+ #
375
+ # This is used for `input_schema` in relations, typically commands use it
376
+ # for processing input
377
+ #
378
+ # @return [Dry::Types::Hash]
379
+ #
380
+ # @api private
381
+ def to_input_hash
382
+ Types::Coercible::Hash.schema(
383
+ map { |attr| [attr.name, attr] }.to_h
384
+ )
385
+ end
386
+
387
+ # Return AST for the schema
388
+ #
389
+ # @return [Array]
390
+ #
391
+ # @api public
392
+ def to_ast
393
+ [:schema, [name, attributes.map(&:to_ast)]]
394
+ end
395
+
396
+ private
397
+
398
+ # @api private
399
+ def count_index
400
+ map(&:name).map { |name| [name, count { |attr| attr.name == name }] }.to_h
401
+ end
402
+
403
+ # @api private
404
+ def name_index
405
+ map { |attr| [attr.name, attr] }.to_h
406
+ end
407
+
408
+ # @api private
409
+ def source_index
410
+ select(&:source).
411
+ group_by(&:source).
412
+ map { |src, grp| [src.to_sym, grp.map { |attr| [attr.name, attr] }.to_h] }.
413
+ to_h
414
+ end
415
+
416
+ # @api private
417
+ def new(attributes)
418
+ self.class.new(name, **options, attributes: attributes)
419
+ end
420
+
421
+ # @api private
422
+ def initialize_primary_key_names
423
+ if primary_key.size > 0
424
+ @primary_key_name = primary_key[0].meta[:name]
425
+ @primary_key_names = primary_key.map { |type| type.meta[:name] }
426
+ end
427
+ end
428
+
429
+ # @api private
430
+ def set!(key, value)
431
+ instance_variable_set("@#{ key }", value)
432
+ options[key] = value
433
+ end
434
+
435
+ memoize :count_index, :name_index, :source_index, :to_ast, :to_input_hash, :to_output_hash
436
+ end
437
+ end