rom-mapper 0.1.1 → 0.2.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 (66) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.rspec +1 -1
  4. data/.travis.yml +19 -13
  5. data/{Changelog.md → CHANGELOG.md} +6 -0
  6. data/Gemfile +23 -10
  7. data/README.md +17 -12
  8. data/Rakefile +12 -4
  9. data/lib/rom-mapper.rb +6 -15
  10. data/lib/rom/header.rb +195 -0
  11. data/lib/rom/header/attribute.rb +184 -0
  12. data/lib/rom/mapper.rb +63 -100
  13. data/lib/rom/mapper/attribute_dsl.rb +477 -0
  14. data/lib/rom/mapper/dsl.rb +120 -0
  15. data/lib/rom/mapper/model_dsl.rb +55 -0
  16. data/lib/rom/mapper/version.rb +3 -7
  17. data/lib/rom/model_builder.rb +99 -0
  18. data/lib/rom/processor.rb +28 -0
  19. data/lib/rom/processor/transproc.rb +388 -0
  20. data/rakelib/benchmark.rake +15 -0
  21. data/rakelib/mutant.rake +16 -0
  22. data/rakelib/rubocop.rake +18 -0
  23. data/rom-mapper.gemspec +7 -6
  24. data/spec/spec_helper.rb +32 -33
  25. data/spec/support/constant_leak_finder.rb +14 -0
  26. data/spec/support/mutant.rb +10 -0
  27. data/spec/unit/rom/mapper/dsl_spec.rb +467 -0
  28. data/spec/unit/rom/mapper_spec.rb +83 -0
  29. data/spec/unit/rom/processor/transproc_spec.rb +448 -0
  30. metadata +68 -89
  31. data/.ruby-version +0 -1
  32. data/Gemfile.devtools +0 -55
  33. data/config/devtools.yml +0 -2
  34. data/config/flay.yml +0 -3
  35. data/config/flog.yml +0 -2
  36. data/config/mutant.yml +0 -3
  37. data/config/reek.yml +0 -103
  38. data/config/rubocop.yml +0 -45
  39. data/lib/rom/mapper/attribute.rb +0 -31
  40. data/lib/rom/mapper/dumper.rb +0 -27
  41. data/lib/rom/mapper/loader.rb +0 -22
  42. data/lib/rom/mapper/loader/allocator.rb +0 -32
  43. data/lib/rom/mapper/loader/attribute_writer.rb +0 -23
  44. data/lib/rom/mapper/loader/object_builder.rb +0 -28
  45. data/spec/shared/unit/loader_call.rb +0 -13
  46. data/spec/shared/unit/loader_identity.rb +0 -13
  47. data/spec/shared/unit/mapper_context.rb +0 -13
  48. data/spec/unit/rom/mapper/call_spec.rb +0 -32
  49. data/spec/unit/rom/mapper/class_methods/build_spec.rb +0 -64
  50. data/spec/unit/rom/mapper/dump_spec.rb +0 -15
  51. data/spec/unit/rom/mapper/dumper/call_spec.rb +0 -29
  52. data/spec/unit/rom/mapper/dumper/identity_spec.rb +0 -28
  53. data/spec/unit/rom/mapper/header/each_spec.rb +0 -28
  54. data/spec/unit/rom/mapper/header/element_reader_spec.rb +0 -25
  55. data/spec/unit/rom/mapper/header/keys_spec.rb +0 -32
  56. data/spec/unit/rom/mapper/identity_from_tuple_spec.rb +0 -15
  57. data/spec/unit/rom/mapper/identity_spec.rb +0 -15
  58. data/spec/unit/rom/mapper/load_spec.rb +0 -15
  59. data/spec/unit/rom/mapper/loader/allocator/call_spec.rb +0 -7
  60. data/spec/unit/rom/mapper/loader/allocator/identity_spec.rb +0 -7
  61. data/spec/unit/rom/mapper/loader/attribute_writer/call_spec.rb +0 -7
  62. data/spec/unit/rom/mapper/loader/attribute_writer/identity_spec.rb +0 -7
  63. data/spec/unit/rom/mapper/loader/object_builder/call_spec.rb +0 -7
  64. data/spec/unit/rom/mapper/loader/object_builder/identity_spec.rb +0 -7
  65. data/spec/unit/rom/mapper/model_spec.rb +0 -11
  66. data/spec/unit/rom/mapper/new_object_spec.rb +0 -14
@@ -0,0 +1,120 @@
1
+ require 'rom/support/class_macros'
2
+ require 'rom/mapper/attribute_dsl'
3
+
4
+ module ROM
5
+ class Mapper
6
+ # Mapper class-level DSL including Attribute DSL and Model DSL
7
+ module DSL
8
+ # Extend mapper class with macros and DSL methods
9
+ #
10
+ # @api private
11
+ def self.included(klass)
12
+ klass.extend(ClassMacros)
13
+ klass.extend(ClassMethods)
14
+ end
15
+
16
+ # Class methods for all mappers
17
+ #
18
+ # @private
19
+ module ClassMethods
20
+ # Set base ivars for the mapper class
21
+ #
22
+ # @api private
23
+ def inherited(klass)
24
+ super
25
+
26
+ klass.instance_variable_set('@attributes', nil)
27
+ klass.instance_variable_set('@header', nil)
28
+ klass.instance_variable_set('@dsl', nil)
29
+ end
30
+
31
+ # include a registered plugin in this mapper
32
+ #
33
+ # @param [Symbol] plugin
34
+ # @param [Hash] options
35
+ # @option options [Symbol] :adapter (:default) first adapter to check for plugin
36
+ #
37
+ # @api public
38
+ def use(plugin, options = {})
39
+ adapter = options.fetch(:adapter, :default)
40
+
41
+ ROM.plugin_registry.mappers.fetch(plugin, adapter).apply_to(self)
42
+ end
43
+
44
+ # Return base_relation used for creating mapper registry
45
+ #
46
+ # This is used to "gather" mappers under same root name
47
+ #
48
+ # @api private
49
+ def base_relation
50
+ if superclass.relation
51
+ superclass.relation
52
+ else
53
+ relation
54
+ end
55
+ end
56
+
57
+ # Return header of the mapper
58
+ #
59
+ # This is memoized so mutating mapper class won't have an effect wrt
60
+ # header after it was initialized for the first time.
61
+ #
62
+ # TODO: freezing mapper class here is probably a good idea
63
+ #
64
+ # @api private
65
+ def header
66
+ @header ||= dsl.header
67
+ end
68
+
69
+ # @api private
70
+ def respond_to_missing?(name, _include_private = false)
71
+ dsl.respond_to?(name) || super
72
+ end
73
+
74
+ private
75
+
76
+ # Return default Attribute DSL options based on settings of the mapper
77
+ # class
78
+ #
79
+ # @api private
80
+ def options
81
+ { prefix: prefix,
82
+ prefix_separator: prefix_separator,
83
+ symbolize_keys: symbolize_keys,
84
+ reject_keys: reject_keys }
85
+ end
86
+
87
+ # Return default attributes that might have been inherited from the
88
+ # superclass
89
+ #
90
+ # @api private
91
+ def attributes
92
+ @attributes ||=
93
+ if superclass.respond_to?(:attributes, true) && inherit_header
94
+ superclass.attributes.dup
95
+ else
96
+ []
97
+ end
98
+ end
99
+
100
+ # Create the attribute DSL instance used by the mapper class
101
+ #
102
+ # @api private
103
+ def dsl
104
+ @dsl ||= AttributeDSL.new(attributes, options)
105
+ end
106
+
107
+ # Delegate Attribute DSL method to the dsl instance
108
+ #
109
+ # @api private
110
+ def method_missing(name, *args, &block)
111
+ if dsl.respond_to?(name)
112
+ dsl.public_send(name, *args, &block)
113
+ else
114
+ super
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,55 @@
1
+ require 'rom/model_builder'
2
+
3
+ module ROM
4
+ class Mapper
5
+ # Model DSL allows setting a model class
6
+ #
7
+ # @private
8
+ module ModelDSL
9
+ attr_reader :attributes, :builder, :klass
10
+
11
+ DEFAULT_TYPE = :poro
12
+
13
+ # Set or generate a model
14
+ #
15
+ # @example
16
+ # class MyDefinition
17
+ # include ROM::Mapper::ModelDSL
18
+ #
19
+ # def initialize
20
+ # @attributes = [[:name], [:title]]
21
+ # end
22
+ # end
23
+ #
24
+ # definition = MyDefinition.new
25
+ #
26
+ # # just set a model constant
27
+ # definition.model(User)
28
+ #
29
+ # # generate model class for the attributes
30
+ # definition.model(name: 'User')
31
+ #
32
+ # @api public
33
+ def model(options = nil)
34
+ if options.is_a?(Class)
35
+ @klass = options
36
+ elsif options
37
+ type = options.fetch(:type) { DEFAULT_TYPE }
38
+ @builder = ModelBuilder[type].new(options)
39
+ end
40
+
41
+ build_class unless options
42
+ end
43
+
44
+ private
45
+
46
+ # Build a model class using a specialized builder
47
+ #
48
+ # @api private
49
+ def build_class
50
+ return klass if klass
51
+ return builder.call(attributes.map(&:first)) if builder
52
+ end
53
+ end
54
+ end
55
+ end
@@ -1,9 +1,5 @@
1
- # encoding: utf-8
2
-
3
1
  module ROM
4
2
  class Mapper
5
-
6
- VERSION = '0.1.1'.freeze
7
-
8
- end # Mapper
9
- end # ROM
3
+ VERSION = '0.2.0'.freeze
4
+ end
5
+ end
@@ -0,0 +1,99 @@
1
+ module ROM
2
+ # Model builders can be used to build model classes for mappers
3
+ #
4
+ # This is used when you define a mapper and setup a model using :name option.
5
+ #
6
+ # @example
7
+ # # this will define User model for you
8
+ # class UserMapper < ROM::Mapper
9
+ # model name: 'User'
10
+ # attribute :id
11
+ # attribute :name
12
+ # end
13
+ #
14
+ # @private
15
+ class ModelBuilder
16
+ attr_reader :name
17
+
18
+ attr_reader :const_name, :namespace, :klass
19
+
20
+ # Return model builder subclass based on type
21
+ #
22
+ # @param [Symbol] type
23
+ #
24
+ # @return [Class]
25
+ #
26
+ # @api private
27
+ def self.[](type)
28
+ case type
29
+ when :poro then PORO
30
+ else
31
+ raise ArgumentError, "#{type.inspect} is not a supported model type"
32
+ end
33
+ end
34
+
35
+ # Build a model class
36
+ #
37
+ # @return [Class]
38
+ #
39
+ # @api private
40
+ def self.call(*args)
41
+ new(*args).call
42
+ end
43
+
44
+ # @api private
45
+ def initialize(options = {})
46
+ @name = options[:name]
47
+
48
+ if name
49
+ parts = name.split('::')
50
+
51
+ @const_name = parts.pop
52
+
53
+ @namespace =
54
+ if parts.any?
55
+ Inflector.constantize(parts.join('::'))
56
+ else
57
+ Object
58
+ end
59
+ end
60
+ end
61
+
62
+ # Define a model class constant
63
+ #
64
+ # @api private
65
+ def define_const
66
+ namespace.const_set(const_name, klass)
67
+ end
68
+
69
+ # Build a model class supporting specific attributes
70
+ #
71
+ # @return [Class]
72
+ #
73
+ # @api private
74
+ def call(attrs)
75
+ define_class(attrs)
76
+ define_const if const_name
77
+ @klass
78
+ end
79
+
80
+ # PORO model class builder
81
+ #
82
+ # @private
83
+ class PORO < ModelBuilder
84
+ def define_class(attrs)
85
+ @klass = Class.new
86
+
87
+ @klass.send(:attr_reader, *attrs)
88
+
89
+ @klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
90
+ def initialize(params)
91
+ #{attrs.map { |name| "@#{name} = params[:#{name}]" }.join("\n")}
92
+ end
93
+ RUBY
94
+
95
+ self
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,28 @@
1
+ require 'rom/mapper'
2
+
3
+ module ROM
4
+ # Abstract processor class
5
+ #
6
+ # Every ROM processor should inherit from this class
7
+ #
8
+ # @api public
9
+ class Processor
10
+ # Hook used to auto-register a processor class
11
+ #
12
+ # @api private
13
+ def self.inherited(processor)
14
+ Mapper.register_processor(processor)
15
+ end
16
+
17
+ # Required interface to be implemented by descendants
18
+ #
19
+ # @return [Processor]
20
+ #
21
+ # @abstract
22
+ #
23
+ # @api private
24
+ def self.build
25
+ raise NotImplementedError, "+build+ must be implemented"
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,388 @@
1
+ require 'transproc/all'
2
+
3
+ require 'rom/processor'
4
+
5
+ module ROM
6
+ class Processor
7
+ # Data mapping transformer builder using Transproc
8
+ #
9
+ # This builds a transproc function that is used to map a whole relation
10
+ #
11
+ # @see https://github.com/solnic/transproc too
12
+ #
13
+ # @private
14
+ class Transproc < Processor
15
+ include ::Transproc::Composer
16
+
17
+ module Functions
18
+ extend ::Transproc::Registry
19
+
20
+ import ::Transproc::Coercions
21
+ import ::Transproc::ArrayTransformations
22
+ import ::Transproc::HashTransformations
23
+ import ::Transproc::ClassTransformations
24
+
25
+ def self.identity(tuple)
26
+ tuple
27
+ end
28
+
29
+ def self.filter_empty(arr)
30
+ arr.reject { |row| row.values.all?(&:nil?) }
31
+ end
32
+ end
33
+
34
+ # @return [Header] header from a mapper
35
+ #
36
+ # @api private
37
+ attr_reader :header
38
+
39
+ # @return [Class] model class from a mapper
40
+ #
41
+ # @api private
42
+ attr_reader :model
43
+
44
+ # @return [Hash] header's attribute mapping
45
+ #
46
+ # @api private
47
+ attr_reader :mapping
48
+
49
+ # @return [Proc] row-processing proc
50
+ #
51
+ # @api private
52
+ attr_reader :row_proc
53
+
54
+ # Build a transproc function from the header
55
+ #
56
+ # @param [ROM::Header] header
57
+ #
58
+ # @return [Transproc::Function]
59
+ #
60
+ # @api private
61
+ def self.build(header)
62
+ new(header).to_transproc
63
+ end
64
+
65
+ # @api private
66
+ def initialize(header)
67
+ @header = header
68
+ @model = header.model
69
+ @mapping = header.mapping
70
+ initialize_row_proc
71
+ end
72
+
73
+ # Coerce mapper header to a transproc data mapping function
74
+ #
75
+ # @return [Transproc::Function]
76
+ #
77
+ # @api private
78
+ def to_transproc
79
+ compose(t(:identity)) do |ops|
80
+ combined = header.combined
81
+ ops << t(:combine, combined.map(&method(:combined_args))) if combined.any?
82
+ ops << header.preprocessed.map { |attr| visit(attr, true) }
83
+ ops << t(:map_array, row_proc) if row_proc
84
+ ops << header.postprocessed.map { |attr| visit(attr, true) }
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ # Visit an attribute from the header
91
+ #
92
+ # This forwards to a specialized visitor based on the attribute type
93
+ #
94
+ # @param [Header::Attribute] attribute
95
+ # @param [Array] args Allows to send `preprocess: true`
96
+ #
97
+ # @api private
98
+ def visit(attribute, *args)
99
+ type = attribute.class.name.split('::').last.downcase
100
+ send("visit_#{type}", attribute, *args)
101
+ end
102
+
103
+ # Visit plain attribute
104
+ #
105
+ # It will call block transformation if it's used
106
+ #
107
+ # If it's a typed attribute a coercion transformation is added
108
+ #
109
+ # @param [Header::Attribute] attribute
110
+ #
111
+ # @api private
112
+ def visit_attribute(attribute)
113
+ coercer = attribute.meta[:coercer]
114
+ if coercer
115
+ t(:map_value, attribute.name, coercer)
116
+ elsif attribute.typed?
117
+ t(:map_value, attribute.name, t(:"to_#{attribute.type}"))
118
+ end
119
+ end
120
+
121
+ # Visit hash attribute
122
+ #
123
+ # @param [Header::Attribute::Hash] attribute
124
+ #
125
+ # @api private
126
+ def visit_hash(attribute)
127
+ with_row_proc(attribute) do |row_proc|
128
+ t(:map_value, attribute.name, row_proc)
129
+ end
130
+ end
131
+
132
+ # Visit combined attribute
133
+ #
134
+ # @api private
135
+ def visit_combined(attribute)
136
+ op = with_row_proc(attribute) do |row_proc|
137
+ array_proc =
138
+ if attribute.type == :hash
139
+ t(:map_array, row_proc) >> -> arr { arr.first }
140
+ else
141
+ t(:map_array, row_proc)
142
+ end
143
+
144
+ t(:map_value, attribute.name, array_proc)
145
+ end
146
+
147
+ if op
148
+ op
149
+ elsif attribute.type == :hash
150
+ t(:map_value, attribute.name, -> arr { arr.first })
151
+ end
152
+ end
153
+
154
+ # Visit array attribute
155
+ #
156
+ # @param [Header::Attribute::Array] attribute
157
+ #
158
+ # @api private
159
+ def visit_array(attribute)
160
+ with_row_proc(attribute) do |row_proc|
161
+ t(:map_value, attribute.name, t(:map_array, row_proc))
162
+ end
163
+ end
164
+
165
+ # Visit wrapped hash attribute
166
+ #
167
+ # :nest transformation is added to handle wrapping
168
+ #
169
+ # @param [Header::Attribute::Wrap] attribute
170
+ #
171
+ # @api private
172
+ def visit_wrap(attribute)
173
+ name = attribute.name
174
+ keys = attribute.tuple_keys
175
+
176
+ compose do |ops|
177
+ ops << t(:nest, name, keys)
178
+ ops << visit_hash(attribute)
179
+ end
180
+ end
181
+
182
+ # Visit unwrap attribute
183
+ #
184
+ # :unwrap transformation is added to handle unwrapping
185
+ #
186
+ # @param [Header::Attributes::Unwrap]
187
+ #
188
+ # @api private
189
+ def visit_unwrap(attribute)
190
+ name = attribute.name
191
+ keys = attribute.pop_keys
192
+
193
+ compose do |ops|
194
+ ops << visit_hash(attribute)
195
+ ops << t(:unwrap, name, keys)
196
+ end
197
+ end
198
+
199
+ # Visit group hash attribute
200
+ #
201
+ # :group transformation is added to handle grouping during preprocessing.
202
+ # Otherwise we simply use array visitor for the attribute.
203
+ #
204
+ # @param [Header::Attribute::Group] attribute
205
+ # @param [Boolean] preprocess true if we are building a relation preprocessing
206
+ # function that is applied to the whole relation
207
+ #
208
+ # @api private
209
+ def visit_group(attribute, preprocess = false)
210
+ if preprocess
211
+ name = attribute.name
212
+ header = attribute.header
213
+ keys = attribute.tuple_keys
214
+
215
+ others = header.preprocessed
216
+
217
+ compose do |ops|
218
+ ops << t(:group, name, keys)
219
+ ops << t(:map_array, t(:map_value, name, t(:filter_empty)))
220
+ ops << others.map { |attr|
221
+ t(:map_array, t(:map_value, name, visit(attr, true)))
222
+ }
223
+ end
224
+ else
225
+ visit_array(attribute)
226
+ end
227
+ end
228
+
229
+ # Visit ungroup attribute
230
+ #
231
+ # :ungroup transforation is added to handle ungrouping during preprocessing.
232
+ # Otherwise we simply use array visitor for the attribute.
233
+ #
234
+ # @param [Header::Attribute::Ungroup] attribute
235
+ # @param [Boolean] preprocess true if we are building a relation preprocessing
236
+ # function that is applied to the whole relation
237
+ #
238
+ # @api private
239
+ def visit_ungroup(attribute, preprocess = false)
240
+ if preprocess
241
+ name = attribute.name
242
+ header = attribute.header
243
+ keys = attribute.pop_keys
244
+
245
+ others = header.postprocessed
246
+
247
+ compose do |ops|
248
+ ops << others.map { |attr|
249
+ t(:map_array, t(:map_value, name, visit(attr, true)))
250
+ }
251
+ ops << t(:ungroup, name, keys)
252
+ end
253
+ else
254
+ visit_array(attribute)
255
+ end
256
+ end
257
+
258
+ # Visit fold hash attribute
259
+ #
260
+ # :fold transformation is added to handle folding during preprocessing.
261
+ #
262
+ # @param [Header::Attribute::Fold] 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_fold(attribute, preprocess = false)
268
+ if preprocess
269
+ name = attribute.name
270
+ keys = attribute.tuple_keys
271
+
272
+ compose do |ops|
273
+ ops << t(:group, name, keys)
274
+ ops << t(:map_array, t(:map_value, name, t(:filter_empty)))
275
+ ops << t(:map_array, t(:fold, name, keys.first))
276
+ end
277
+ end
278
+ end
279
+
280
+ # Visit unfold hash attribute
281
+ #
282
+ # :unfold transformation is added to handle unfolding during preprocessing.
283
+ #
284
+ # @param [Header::Attribute::Unfold] attribute
285
+ # @param [Boolean] preprocess true if we are building a relation preprocessing
286
+ # function that is applied to the whole relation
287
+ #
288
+ # @api private
289
+ def visit_unfold(attribute, preprocess = false)
290
+ if preprocess
291
+ name = attribute.name
292
+ header = attribute.header
293
+ keys = attribute.pop_keys
294
+ key = keys.first
295
+
296
+ others = header.postprocessed
297
+
298
+ compose do |ops|
299
+ ops << others.map { |attr|
300
+ t(:map_array, t(:map_value, name, visit(attr, true)))
301
+ }
302
+ ops << t(:map_array, t(:map_value, name, t(:insert_key, key)))
303
+ ops << t(:map_array, t(:reject_keys, [key] - [name]))
304
+ ops << t(:ungroup, name, [key])
305
+ end
306
+ end
307
+ end
308
+
309
+ # Visit excluded attribute
310
+ #
311
+ # @param [Header::Attribute::Exclude] attribute
312
+ #
313
+ # @api private
314
+ def visit_exclude(attribute)
315
+ t(:reject_keys, [attribute.name])
316
+ end
317
+
318
+ # @api private
319
+ def combined_args(attribute)
320
+ other = attribute.header.combined
321
+
322
+ if other.any?
323
+ children = other.map(&method(:combined_args))
324
+ [attribute.name, attribute.meta[:keys], children]
325
+ else
326
+ [attribute.name, attribute.meta[:keys]]
327
+ end
328
+ end
329
+
330
+ # Build row_proc
331
+ #
332
+ # This transproc function is applied to each row in a dataset
333
+ #
334
+ # @api private
335
+ def initialize_row_proc
336
+ @row_proc = compose { |ops|
337
+ process_header_keys(ops)
338
+
339
+ ops << t(:rename_keys, mapping) if header.aliased?
340
+ ops << header.map { |attr| visit(attr) }
341
+ ops << t(:constructor_inject, model) if model
342
+ }
343
+ end
344
+
345
+ # Process row_proc header keys
346
+ #
347
+ # @api private
348
+ def process_header_keys(ops)
349
+ if header.reject_keys
350
+ all_keys = header.tuple_keys + header.non_primitives.map(&:key)
351
+ ops << t(:accept_keys, all_keys)
352
+ end
353
+ ops
354
+ end
355
+
356
+ # Yield row proc for a given attribute if any
357
+ #
358
+ # @param [Header::Attribute] attribute
359
+ #
360
+ # @api private
361
+ def with_row_proc(attribute)
362
+ row_proc = row_proc_from(attribute)
363
+ yield(row_proc) if row_proc
364
+ end
365
+
366
+ # Build a row_proc from a given attribute
367
+ #
368
+ # This is used by embedded attribute visitors
369
+ #
370
+ # @api private
371
+ def row_proc_from(attribute)
372
+ new(attribute.header).row_proc
373
+ end
374
+
375
+ # Return a new instance of the processor
376
+ #
377
+ # @api private
378
+ def new(*args)
379
+ self.class.new(*args)
380
+ end
381
+
382
+ # @api private
383
+ def t(*args)
384
+ Functions[*args]
385
+ end
386
+ end
387
+ end
388
+ end