rom-core 4.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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