rom-core 4.2.1 → 5.0.0

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