rom-core 4.2.1 → 5.0.0

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 (139) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +0 -2
  3. data/lib/rom-core.rb +2 -0
  4. data/lib/rom/array_dataset.rb +2 -0
  5. data/lib/rom/association_set.rb +2 -0
  6. data/lib/rom/associations/abstract.rb +2 -0
  7. data/lib/rom/associations/definitions.rb +2 -0
  8. data/lib/rom/associations/definitions/abstract.rb +2 -0
  9. data/lib/rom/associations/definitions/many_to_many.rb +2 -0
  10. data/lib/rom/associations/definitions/many_to_one.rb +2 -0
  11. data/lib/rom/associations/definitions/one_to_many.rb +2 -0
  12. data/lib/rom/associations/definitions/one_to_one.rb +2 -0
  13. data/lib/rom/associations/definitions/one_to_one_through.rb +2 -0
  14. data/lib/rom/associations/many_to_many.rb +2 -0
  15. data/lib/rom/associations/many_to_one.rb +2 -0
  16. data/lib/rom/associations/one_to_many.rb +2 -0
  17. data/lib/rom/associations/one_to_one.rb +2 -0
  18. data/lib/rom/associations/one_to_one_through.rb +2 -0
  19. data/lib/rom/associations/through_identifier.rb +2 -0
  20. data/lib/rom/attribute.rb +58 -72
  21. data/lib/rom/auto_curry.rb +2 -0
  22. data/lib/rom/cache.rb +2 -0
  23. data/lib/rom/command.rb +7 -5
  24. data/lib/rom/command_compiler.rb +2 -0
  25. data/lib/rom/command_proxy.rb +2 -0
  26. data/lib/rom/command_registry.rb +13 -7
  27. data/lib/rom/commands.rb +2 -0
  28. data/lib/rom/commands/class_interface.rb +4 -2
  29. data/lib/rom/commands/composite.rb +2 -0
  30. data/lib/rom/commands/create.rb +2 -0
  31. data/lib/rom/commands/delete.rb +2 -0
  32. data/lib/rom/commands/graph.rb +2 -0
  33. data/lib/rom/commands/graph/class_interface.rb +2 -0
  34. data/lib/rom/commands/graph/input_evaluator.rb +2 -0
  35. data/lib/rom/commands/lazy.rb +2 -0
  36. data/lib/rom/commands/lazy/create.rb +2 -0
  37. data/lib/rom/commands/lazy/delete.rb +2 -0
  38. data/lib/rom/commands/lazy/update.rb +2 -0
  39. data/lib/rom/commands/update.rb +2 -0
  40. data/lib/rom/configuration.rb +2 -0
  41. data/lib/rom/configuration_dsl.rb +2 -0
  42. data/lib/rom/configuration_dsl/command.rb +2 -0
  43. data/lib/rom/configuration_dsl/command_dsl.rb +2 -0
  44. data/lib/rom/configuration_dsl/relation.rb +2 -0
  45. data/lib/rom/configuration_plugin.rb +2 -0
  46. data/lib/rom/constants.rb +3 -0
  47. data/lib/rom/container.rb +2 -0
  48. data/lib/rom/core.rb +4 -1
  49. data/lib/rom/create_container.rb +2 -0
  50. data/lib/rom/data_proxy.rb +2 -0
  51. data/lib/rom/enumerable_dataset.rb +2 -0
  52. data/lib/rom/environment.rb +2 -0
  53. data/lib/rom/gateway.rb +2 -0
  54. data/lib/rom/global.rb +2 -0
  55. data/lib/rom/global/plugin_dsl.rb +2 -0
  56. data/lib/rom/header.rb +198 -0
  57. data/lib/rom/header/attribute.rb +192 -0
  58. data/lib/rom/initializer.rb +2 -0
  59. data/lib/rom/lint/enumerable_dataset.rb +2 -0
  60. data/lib/rom/lint/gateway.rb +2 -0
  61. data/lib/rom/lint/linter.rb +2 -0
  62. data/lib/rom/lint/spec.rb +2 -0
  63. data/lib/rom/lint/test.rb +2 -0
  64. data/lib/rom/mapper.rb +100 -0
  65. data/lib/rom/mapper/attribute_dsl.rb +480 -0
  66. data/lib/rom/mapper/builder.rb +39 -0
  67. data/lib/rom/mapper/configuration_plugin.rb +28 -0
  68. data/lib/rom/mapper/dsl.rb +123 -0
  69. data/lib/rom/mapper/mapper_dsl.rb +45 -0
  70. data/lib/rom/mapper/model_dsl.rb +60 -0
  71. data/lib/rom/mapper_compiler.rb +84 -0
  72. data/lib/rom/mapper_registry.rb +2 -0
  73. data/lib/rom/memory.rb +2 -0
  74. data/lib/rom/memory/associations.rb +2 -0
  75. data/lib/rom/memory/associations/many_to_many.rb +2 -0
  76. data/lib/rom/memory/associations/many_to_one.rb +2 -0
  77. data/lib/rom/memory/associations/one_to_many.rb +2 -0
  78. data/lib/rom/memory/associations/one_to_one.rb +2 -0
  79. data/lib/rom/memory/commands.rb +2 -0
  80. data/lib/rom/memory/dataset.rb +2 -0
  81. data/lib/rom/memory/gateway.rb +2 -0
  82. data/lib/rom/memory/mapper_compiler.rb +2 -0
  83. data/lib/rom/memory/relation.rb +2 -0
  84. data/lib/rom/memory/schema.rb +2 -0
  85. data/lib/rom/memory/storage.rb +2 -0
  86. data/lib/rom/memory/types.rb +2 -0
  87. data/lib/rom/model_builder.rb +103 -0
  88. data/lib/rom/open_struct.rb +37 -0
  89. data/lib/rom/pipeline.rb +2 -0
  90. data/lib/rom/plugin.rb +2 -0
  91. data/lib/rom/plugin_base.rb +2 -0
  92. data/lib/rom/plugin_registry.rb +2 -0
  93. data/lib/rom/plugins/command/schema.rb +2 -0
  94. data/lib/rom/plugins/command/timestamps.rb +2 -0
  95. data/lib/rom/plugins/relation/instrumentation.rb +2 -0
  96. data/lib/rom/plugins/relation/registry_reader.rb +2 -0
  97. data/lib/rom/plugins/schema/timestamps.rb +8 -1
  98. data/lib/rom/processor.rb +30 -0
  99. data/lib/rom/processor/transproc.rb +417 -0
  100. data/lib/rom/registry.rb +2 -0
  101. data/lib/rom/relation.rb +4 -2
  102. data/lib/rom/relation/class_interface.rb +2 -0
  103. data/lib/rom/relation/combined.rb +2 -0
  104. data/lib/rom/relation/commands.rb +2 -0
  105. data/lib/rom/relation/composite.rb +2 -0
  106. data/lib/rom/relation/curried.rb +3 -1
  107. data/lib/rom/relation/graph.rb +2 -0
  108. data/lib/rom/relation/loaded.rb +2 -0
  109. data/lib/rom/relation/materializable.rb +2 -0
  110. data/lib/rom/relation/name.rb +2 -0
  111. data/lib/rom/relation/view_dsl.rb +2 -0
  112. data/lib/rom/relation/wrap.rb +2 -0
  113. data/lib/rom/relation_registry.rb +2 -0
  114. data/lib/rom/schema.rb +39 -6
  115. data/lib/rom/schema/associations_dsl.rb +5 -3
  116. data/lib/rom/schema/dsl.rb +41 -11
  117. data/lib/rom/schema/inferrer.rb +21 -3
  118. data/lib/rom/schema_plugin.rb +2 -0
  119. data/lib/rom/setup.rb +2 -0
  120. data/lib/rom/setup/auto_registration.rb +2 -0
  121. data/lib/rom/setup/auto_registration_strategies/base.rb +3 -1
  122. data/lib/rom/setup/auto_registration_strategies/custom_namespace.rb +2 -0
  123. data/lib/rom/setup/auto_registration_strategies/no_namespace.rb +2 -0
  124. data/lib/rom/setup/auto_registration_strategies/with_namespace.rb +2 -0
  125. data/lib/rom/setup/finalize.rb +2 -0
  126. data/lib/rom/setup/finalize/finalize_commands.rb +2 -0
  127. data/lib/rom/setup/finalize/finalize_mappers.rb +2 -0
  128. data/lib/rom/setup/finalize/finalize_relations.rb +2 -0
  129. data/lib/rom/struct.rb +108 -0
  130. data/lib/rom/struct_compiler.rb +110 -0
  131. data/lib/rom/support/configurable.rb +2 -0
  132. data/lib/rom/support/inflector.rb +2 -0
  133. data/lib/rom/support/memoizable.rb +2 -0
  134. data/lib/rom/support/notifications.rb +2 -0
  135. data/lib/rom/transaction.rb +2 -0
  136. data/lib/rom/transformer.rb +34 -0
  137. data/lib/rom/types.rb +10 -3
  138. data/lib/rom/version.rb +3 -1
  139. metadata +37 -21
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/gateway'
2
4
  require 'rom/memory/storage'
3
5
  require 'rom/memory/commands'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/mapper_compiler'
2
4
 
3
5
  module ROM
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/memory/types'
2
4
  require 'rom/memory/schema'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/schema'
2
4
  require 'rom/memory/associations'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'concurrent/hash'
2
4
  require 'concurrent/array'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/types'
2
4
 
3
5
  module ROM
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rom/support/inflector'
4
+
5
+ module ROM
6
+ # Model builders can be used to build model classes for mappers
7
+ #
8
+ # This is used when you define a mapper and setup a model using :name option.
9
+ #
10
+ # @example
11
+ # # this will define User model for you
12
+ # class UserMapper < ROM::Mapper
13
+ # model name: 'User'
14
+ # attribute :id
15
+ # attribute :name
16
+ # end
17
+ #
18
+ # @private
19
+ class ModelBuilder
20
+ attr_reader :name
21
+
22
+ attr_reader :const_name, :namespace, :klass
23
+
24
+ # Return model builder subclass based on type
25
+ #
26
+ # @param [Symbol] type
27
+ #
28
+ # @return [Class]
29
+ #
30
+ # @api private
31
+ def self.[](type)
32
+ case type
33
+ when :poro then PORO
34
+ else
35
+ raise ArgumentError, "#{type.inspect} is not a supported model type"
36
+ end
37
+ end
38
+
39
+ # Build a model class
40
+ #
41
+ # @return [Class]
42
+ #
43
+ # @api private
44
+ def self.call(*args)
45
+ new(*args).call
46
+ end
47
+
48
+ # @api private
49
+ def initialize(options = {})
50
+ @name = options[:name]
51
+
52
+ if name
53
+ parts = name.split('::')
54
+
55
+ @const_name = parts.pop
56
+
57
+ @namespace =
58
+ if parts.any?
59
+ Inflector.constantize(parts.join('::'))
60
+ else
61
+ Object
62
+ end
63
+ end
64
+ end
65
+
66
+ # Define a model class constant
67
+ #
68
+ # @api private
69
+ def define_const
70
+ namespace.const_set(const_name, klass)
71
+ end
72
+
73
+ # Build a model class supporting specific attributes
74
+ #
75
+ # @return [Class]
76
+ #
77
+ # @api private
78
+ def call(attrs)
79
+ define_class(attrs)
80
+ define_const if const_name
81
+ @klass
82
+ end
83
+
84
+ # PORO model class builder
85
+ #
86
+ # @private
87
+ class PORO < ModelBuilder
88
+ def define_class(attrs)
89
+ @klass = Class.new
90
+
91
+ @klass.send(:attr_reader, *attrs)
92
+
93
+ @klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
94
+ def initialize(params)
95
+ #{attrs.map { |name| "@#{name} = params[:#{name}]" }.join("\n")}
96
+ end
97
+ RUBY
98
+
99
+ self
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ # ROM's open structs are used for relations with empty schemas.
5
+ # Such relations may exist in cases like using raw SQL strings
6
+ # where schema was not explicitly defined using `view` DSL.
7
+ #
8
+ # @api public
9
+ class OpenStruct
10
+ IVAR = -> v { :"@#{v}" }
11
+
12
+ # @api private
13
+ def initialize(attributes)
14
+ attributes.each do |key, value|
15
+ instance_variable_set(IVAR[key], value)
16
+ end
17
+ end
18
+
19
+ # @api private
20
+ def respond_to_missing?(meth, include_private = false)
21
+ super || instance_variables.include?(IVAR[meth])
22
+ end
23
+
24
+ private
25
+
26
+ # @api private
27
+ def method_missing(meth, *args, &block)
28
+ ivar = IVAR[meth]
29
+
30
+ if instance_variables.include?(ivar)
31
+ instance_variable_get(ivar)
32
+ else
33
+ super
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ROM
2
4
  # Data pipeline common interface
3
5
  #
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/constants'
2
4
  require 'rom/plugin_base'
3
5
  require 'rom/support/configurable'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ROM
2
4
  # Abstract plugin base
3
5
  #
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/registry'
2
4
 
3
5
  module ROM
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ROM
2
4
  module Plugins
3
5
  module Command
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'set'
2
4
 
3
5
  module ROM
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dry/core/class_attributes'
2
4
 
3
5
  module ROM
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/constants'
2
4
 
3
5
  module ROM
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ROM
2
4
  module Plugins
3
5
  module Schema
@@ -27,7 +29,12 @@ module ROM
27
29
  def self.apply(schema, options)
28
30
  type = options.fetch(:type, Types::Time)
29
31
  names = options.fetch(:attributes, DEFAULT_TIMESTAMPS)
30
- attributes = names.map { |name| type.meta(name: name, source: schema.name) }
32
+ attributes = names.map do |name|
33
+ ROM::Schema.build_attribute_info(
34
+ type.meta(source: schema.name),
35
+ name: name
36
+ )
37
+ end
31
38
 
32
39
  schema.attributes.concat(
33
40
  schema.class.attributes(attributes, schema.attr_class)
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rom/mapper'
4
+
5
+ module ROM
6
+ # Abstract processor class
7
+ #
8
+ # Every ROM processor should inherit from this class
9
+ #
10
+ # @api public
11
+ class Processor
12
+ # Hook used to auto-register a processor class
13
+ #
14
+ # @api private
15
+ def self.inherited(processor)
16
+ Mapper.register_processor(processor)
17
+ end
18
+
19
+ # Required interface to be implemented by descendants
20
+ #
21
+ # @return [Processor]
22
+ #
23
+ # @abstract
24
+ #
25
+ # @api private
26
+ def self.build
27
+ raise NotImplementedError, "+build+ must be implemented"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,417 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'transproc/all'
4
+
5
+ require 'rom/processor'
6
+
7
+ module ROM
8
+ class Processor
9
+ # Data mapping transformer builder using Transproc
10
+ #
11
+ # This builds a transproc function that is used to map a whole relation
12
+ #
13
+ # @see https://github.com/solnic/transproc too
14
+ #
15
+ # @private
16
+ class Transproc < Processor
17
+ include ::Transproc::Composer
18
+
19
+ module Functions
20
+ extend ::Transproc::Registry
21
+
22
+ import ::Transproc::Coercions
23
+ import ::Transproc::ArrayTransformations
24
+ import ::Transproc::HashTransformations
25
+ import ::Transproc::ClassTransformations
26
+ import ::Transproc::ProcTransformations
27
+ INVALID_INJECT_UNION_VALUE = "%s attribute: block is required for :from with union value.".freeze
28
+
29
+ def self.identity(tuple)
30
+ tuple
31
+ end
32
+
33
+ def self.get(arr, idx)
34
+ arr[idx]
35
+ end
36
+
37
+ def self.filter_empty(arr)
38
+ arr.reject { |row| row.values.all?(&:nil?) }
39
+ end
40
+
41
+ def self.inject_union_value(tuple, name, keys, coercer)
42
+ raise ROM::MapperMisconfiguredError, INVALID_INJECT_UNION_VALUE % [name] if !coercer
43
+
44
+ values = tuple.values_at(*keys)
45
+ result = coercer.call(*values)
46
+
47
+ tuple.merge(name => result)
48
+ end
49
+ end
50
+
51
+ # @return [Mapper] mapper that this processor belongs to
52
+ #
53
+ # @api private
54
+ attr_reader :mapper
55
+
56
+ # @return [Header] header from a mapper
57
+ #
58
+ # @api private
59
+ attr_reader :header
60
+
61
+ # @return [Class] model class from a mapper
62
+ #
63
+ # @api private
64
+ attr_reader :model
65
+
66
+ # @return [Hash] header's attribute mapping
67
+ #
68
+ # @api private
69
+ attr_reader :mapping
70
+
71
+ # @return [Proc] row-processing proc
72
+ #
73
+ # @api private
74
+ attr_reader :row_proc
75
+
76
+ # Build a transproc function from the header
77
+ #
78
+ # @param [ROM::Header] header
79
+ #
80
+ # @return [Transproc::Function]
81
+ #
82
+ # @api private
83
+ def self.build(mapper, header)
84
+ new(mapper, header).to_transproc
85
+ end
86
+
87
+ # @api private
88
+ def initialize(mapper, header)
89
+ @mapper = mapper
90
+ @header = header
91
+ @model = header.model
92
+ @mapping = header.mapping
93
+ initialize_row_proc
94
+ end
95
+
96
+ # Coerce mapper header to a transproc data mapping function
97
+ #
98
+ # @return [Transproc::Function]
99
+ #
100
+ # @api private
101
+ def to_transproc
102
+ compose(t(:identity)) do |ops|
103
+ combined = header.combined
104
+ ops << t(:combine, combined.map(&method(:combined_args))) if combined.any?
105
+ ops << header.preprocessed.map { |attr| visit(attr, true) }
106
+ ops << t(:map_array, row_proc) if row_proc
107
+ ops << header.postprocessed.map { |attr| visit(attr, true) }
108
+ end
109
+ end
110
+
111
+ private
112
+
113
+ # Visit an attribute from the header
114
+ #
115
+ # This forwards to a specialized visitor based on the attribute type
116
+ #
117
+ # @param [Header::Attribute] attribute
118
+ # @param [Array] args Allows to send `preprocess: true`
119
+ #
120
+ # @api private
121
+ def visit(attribute, *args)
122
+ type = attribute.class.name.split('::').last.downcase
123
+ send("visit_#{type}", attribute, *args)
124
+ end
125
+
126
+ # Visit plain attribute
127
+ #
128
+ # It will call block transformation if it's used
129
+ #
130
+ # If it's a typed attribute a coercion transformation is added
131
+ #
132
+ # @param [Header::Attribute] attribute
133
+ #
134
+ # @api private
135
+ def visit_attribute(attribute)
136
+ coercer = attribute.meta[:coercer]
137
+ if attribute.union?
138
+ compose do |ops|
139
+ ops << t(:inject_union_value, attribute.name, attribute.key, coercer)
140
+ ops << t(:reject_keys, attribute.key) unless header.copy_keys
141
+ end
142
+ elsif coercer
143
+ t(:map_value, attribute.name, t(:bind, mapper, coercer))
144
+ elsif attribute.typed?
145
+ t(:map_value, attribute.name, t(:"to_#{attribute.type}"))
146
+ end
147
+ end
148
+
149
+ # Visit hash attribute
150
+ #
151
+ # @param [Header::Attribute::Hash] attribute
152
+ #
153
+ # @api private
154
+ def visit_hash(attribute)
155
+ with_row_proc(attribute) do |row_proc|
156
+ t(:map_value, attribute.name, row_proc)
157
+ end
158
+ end
159
+
160
+ # Visit combined attribute
161
+ #
162
+ # @api private
163
+ def visit_combined(attribute)
164
+ op = with_row_proc(attribute) do |row_proc|
165
+ array_proc =
166
+ if attribute.type == :hash
167
+ t(:map_array, row_proc) >> t(:get, 0)
168
+ else
169
+ t(:map_array, row_proc)
170
+ end
171
+
172
+ t(:map_value, attribute.name, array_proc)
173
+ end
174
+
175
+ if op
176
+ op
177
+ elsif attribute.type == :hash
178
+ t(:map_value, attribute.name, t(:get, 0))
179
+ end
180
+ end
181
+
182
+ # Visit array attribute
183
+ #
184
+ # @param [Header::Attribute::Array] attribute
185
+ #
186
+ # @api private
187
+ def visit_array(attribute)
188
+ with_row_proc(attribute) do |row_proc|
189
+ t(:map_value, attribute.name, t(:map_array, row_proc))
190
+ end
191
+ end
192
+
193
+ # Visit wrapped hash attribute
194
+ #
195
+ # :nest transformation is added to handle wrapping
196
+ #
197
+ # @param [Header::Attribute::Wrap] attribute
198
+ #
199
+ # @api private
200
+ def visit_wrap(attribute)
201
+ name = attribute.name
202
+ keys = attribute.tuple_keys
203
+
204
+ compose do |ops|
205
+ ops << t(:nest, name, keys)
206
+ ops << visit_hash(attribute)
207
+ end
208
+ end
209
+
210
+ # Visit unwrap attribute
211
+ #
212
+ # :unwrap transformation is added to handle unwrapping
213
+ #
214
+ # @param [Header::Attributes::Unwrap] attribute
215
+ #
216
+ # @api private
217
+ def visit_unwrap(attribute)
218
+ name = attribute.name
219
+ keys = attribute.pop_keys
220
+
221
+ compose do |ops|
222
+ ops << visit_hash(attribute)
223
+ ops << t(:unwrap, name, keys)
224
+ end
225
+ end
226
+
227
+ # Visit group hash attribute
228
+ #
229
+ # :group transformation is added to handle grouping during preprocessing.
230
+ # Otherwise we simply use array visitor for the attribute.
231
+ #
232
+ # @param [Header::Attribute::Group] attribute
233
+ # @param [Boolean] preprocess true if we are building a relation preprocessing
234
+ # function that is applied to the whole relation
235
+ #
236
+ # @api private
237
+ def visit_group(attribute, preprocess = false)
238
+ if preprocess
239
+ name = attribute.name
240
+ header = attribute.header
241
+ keys = attribute.tuple_keys
242
+
243
+ others = header.preprocessed
244
+
245
+ compose do |ops|
246
+ ops << t(:group, name, keys)
247
+ ops << t(:map_array, t(:map_value, name, t(:filter_empty)))
248
+ ops << others.map { |attr|
249
+ t(:map_array, t(:map_value, name, visit(attr, true)))
250
+ }
251
+ end
252
+ else
253
+ visit_array(attribute)
254
+ end
255
+ end
256
+
257
+ # Visit ungroup attribute
258
+ #
259
+ # :ungroup transforation is added to handle ungrouping during preprocessing.
260
+ # Otherwise we simply use array visitor for the attribute.
261
+ #
262
+ # @param [Header::Attribute::Ungroup] attribute
263
+ # @param [Boolean] preprocess true if we are building a relation preprocessing
264
+ # function that is applied to the whole relation
265
+ #
266
+ # @api private
267
+ def visit_ungroup(attribute, preprocess = false)
268
+ if preprocess
269
+ name = attribute.name
270
+ header = attribute.header
271
+ keys = attribute.pop_keys
272
+
273
+ others = header.postprocessed
274
+
275
+ compose do |ops|
276
+ ops << others.map { |attr|
277
+ t(:map_array, t(:map_value, name, visit(attr, true)))
278
+ }
279
+ ops << t(:ungroup, name, keys)
280
+ end
281
+ else
282
+ visit_array(attribute)
283
+ end
284
+ end
285
+
286
+ # Visit fold hash attribute
287
+ #
288
+ # :fold transformation is added to handle folding during preprocessing.
289
+ #
290
+ # @param [Header::Attribute::Fold] attribute
291
+ # @param [Boolean] preprocess true if we are building a relation preprocessing
292
+ # function that is applied to the whole relation
293
+ #
294
+ # @api private
295
+ def visit_fold(attribute, preprocess = false)
296
+ if preprocess
297
+ name = attribute.name
298
+ keys = attribute.tuple_keys
299
+
300
+ compose do |ops|
301
+ ops << t(:group, name, keys)
302
+ ops << t(:map_array, t(:map_value, name, t(:filter_empty)))
303
+ ops << t(:map_array, t(:fold, name, keys.first))
304
+ end
305
+ end
306
+ end
307
+
308
+ # Visit unfold hash attribute
309
+ #
310
+ # :unfold transformation is added to handle unfolding during preprocessing.
311
+ #
312
+ # @param [Header::Attribute::Unfold] attribute
313
+ # @param [Boolean] preprocess true if we are building a relation preprocessing
314
+ # function that is applied to the whole relation
315
+ #
316
+ # @api private
317
+ def visit_unfold(attribute, preprocess = false)
318
+ if preprocess
319
+ name = attribute.name
320
+ header = attribute.header
321
+ keys = attribute.pop_keys
322
+ key = keys.first
323
+
324
+ others = header.postprocessed
325
+
326
+ compose do |ops|
327
+ ops << others.map { |attr|
328
+ t(:map_array, t(:map_value, name, visit(attr, true)))
329
+ }
330
+ ops << t(:map_array, t(:map_value, name, t(:insert_key, key)))
331
+ ops << t(:map_array, t(:reject_keys, [key] - [name]))
332
+ ops << t(:ungroup, name, [key])
333
+ end
334
+ end
335
+ end
336
+
337
+ # Visit excluded attribute
338
+ #
339
+ # @param [Header::Attribute::Exclude] attribute
340
+ #
341
+ # @api private
342
+ def visit_exclude(attribute)
343
+ t(:reject_keys, [attribute.name])
344
+ end
345
+
346
+ # @api private
347
+ def combined_args(attribute)
348
+ other = attribute.header.combined
349
+
350
+ if other.any?
351
+ children = other.map(&method(:combined_args))
352
+ [attribute.name, attribute.meta[:keys], children]
353
+ else
354
+ [attribute.name, attribute.meta[:keys]]
355
+ end
356
+ end
357
+
358
+ # Build row_proc
359
+ #
360
+ # This transproc function is applied to each row in a dataset
361
+ #
362
+ # @api private
363
+ def initialize_row_proc
364
+ @row_proc = compose { |ops|
365
+ alias_handler = header.copy_keys ? :copy_keys : :rename_keys
366
+ process_header_keys(ops)
367
+
368
+ ops << t(alias_handler, mapping) if header.aliased?
369
+ ops << header.map { |attr| visit(attr) }
370
+ ops << t(:constructor_inject, model) if model
371
+ }
372
+ end
373
+
374
+ # Process row_proc header keys
375
+ #
376
+ # @api private
377
+ def process_header_keys(ops)
378
+ if header.reject_keys
379
+ all_keys = header.tuple_keys + header.non_primitives.map(&:key)
380
+ ops << t(:accept_keys, all_keys)
381
+ end
382
+ ops
383
+ end
384
+
385
+ # Yield row proc for a given attribute if any
386
+ #
387
+ # @param [Header::Attribute] attribute
388
+ #
389
+ # @api private
390
+ def with_row_proc(attribute)
391
+ row_proc = row_proc_from(attribute)
392
+ yield(row_proc) if row_proc
393
+ end
394
+
395
+ # Build a row_proc from a given attribute
396
+ #
397
+ # This is used by embedded attribute visitors
398
+ #
399
+ # @api private
400
+ def row_proc_from(attribute)
401
+ new(mapper, attribute.header).row_proc
402
+ end
403
+
404
+ # Return a new instance of the processor
405
+ #
406
+ # @api private
407
+ def new(*args)
408
+ self.class.new(*args)
409
+ end
410
+
411
+ # @api private
412
+ def t(*args)
413
+ Functions[*args]
414
+ end
415
+ end
416
+ end
417
+ end