rom 0.8.1 → 0.9.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/README.md +5 -1
  4. data/lib/rom.rb +35 -16
  5. data/lib/rom/command.rb +1 -9
  6. data/lib/rom/commands/graph/class_interface.rb +2 -2
  7. data/lib/rom/constants.rb +0 -6
  8. data/lib/rom/{env.rb → container.rb} +3 -3
  9. data/lib/rom/environment.rb +238 -0
  10. data/lib/rom/environment_plugin.rb +17 -0
  11. data/lib/rom/environment_plugins/auto_registration.rb +17 -0
  12. data/lib/rom/global.rb +0 -203
  13. data/lib/rom/mapper_registry.rb +2 -0
  14. data/lib/rom/pipeline.rb +2 -0
  15. data/lib/rom/plugin.rb +4 -18
  16. data/lib/rom/plugin_base.rb +31 -0
  17. data/lib/rom/plugin_registry.rb +54 -17
  18. data/lib/rom/relation.rb +54 -11
  19. data/lib/rom/relation/class_interface.rb +14 -21
  20. data/lib/rom/relation/curried.rb +36 -2
  21. data/lib/rom/relation/graph.rb +7 -0
  22. data/lib/rom/relation_registry.rb +4 -0
  23. data/lib/rom/setup.rb +9 -8
  24. data/lib/rom/setup/finalize.rb +5 -5
  25. data/lib/rom/version.rb +1 -1
  26. data/rom.gemspec +2 -0
  27. data/spec/integration/commands/create_spec.rb +1 -1
  28. data/spec/integration/commands/update_spec.rb +1 -1
  29. data/spec/integration/mappers/unwrap_spec.rb +1 -1
  30. data/spec/spec_helper.rb +2 -0
  31. data/spec/unit/rom/{env_spec.rb → container_spec.rb} +5 -5
  32. data/spec/unit/rom/plugin_spec.rb +0 -8
  33. data/spec/unit/rom/relation/composite_spec.rb +2 -2
  34. data/spec/unit/rom/relation/curried_spec.rb +53 -0
  35. data/spec/unit/rom/relation/graph_spec.rb +4 -0
  36. data/spec/unit/rom/relation/lazy/combine_spec.rb +6 -6
  37. data/spec/unit/rom/relation/lazy_spec.rb +4 -8
  38. data/spec/unit/rom/relation_spec.rb +0 -14
  39. data/spec/unit/rom/setup_spec.rb +1 -1
  40. metadata +52 -35
  41. data/lib/rom/header.rb +0 -193
  42. data/lib/rom/header/attribute.rb +0 -184
  43. data/lib/rom/mapper.rb +0 -103
  44. data/lib/rom/mapper/attribute_dsl.rb +0 -477
  45. data/lib/rom/mapper/dsl.rb +0 -119
  46. data/lib/rom/mapper/model_dsl.rb +0 -55
  47. data/lib/rom/model_builder.rb +0 -101
  48. data/lib/rom/processor.rb +0 -28
  49. data/lib/rom/processor/transproc.rb +0 -388
  50. data/lib/rom/relation/lazy.rb +0 -145
  51. data/lib/rom/support/array_dataset.rb +0 -41
  52. data/lib/rom/support/class_builder.rb +0 -44
  53. data/lib/rom/support/class_macros.rb +0 -56
  54. data/lib/rom/support/data_proxy.rb +0 -102
  55. data/lib/rom/support/deprecations.rb +0 -36
  56. data/lib/rom/support/enumerable_dataset.rb +0 -65
  57. data/lib/rom/support/inflector.rb +0 -73
  58. data/lib/rom/support/options.rb +0 -195
  59. data/lib/rom/support/registry.rb +0 -43
  60. data/spec/unit/rom/header_spec.rb +0 -102
  61. data/spec/unit/rom/mapper/dsl_spec.rb +0 -467
  62. data/spec/unit/rom/mapper_spec.rb +0 -84
  63. data/spec/unit/rom/model_builder_spec.rb +0 -46
  64. data/spec/unit/rom/processor/transproc_spec.rb +0 -448
  65. data/spec/unit/rom/support/array_dataset_spec.rb +0 -61
  66. data/spec/unit/rom/support/class_builder_spec.rb +0 -42
  67. data/spec/unit/rom/support/enumerable_dataset_spec.rb +0 -17
  68. data/spec/unit/rom/support/inflector_spec.rb +0 -89
  69. data/spec/unit/rom/support/options_spec.rb +0 -119
@@ -1,119 +0,0 @@
1
- require 'rom/mapper/attribute_dsl'
2
-
3
- module ROM
4
- class Mapper
5
- # Mapper class-level DSL including Attribute DSL and Model DSL
6
- module DSL
7
- # Extend mapper class with macros and DSL methods
8
- #
9
- # @api private
10
- def self.included(klass)
11
- klass.extend(ClassMacros)
12
- klass.extend(ClassMethods)
13
- end
14
-
15
- # Class methods for all mappers
16
- #
17
- # @private
18
- module ClassMethods
19
- # Set base ivars for the mapper class
20
- #
21
- # @api private
22
- def inherited(klass)
23
- super
24
-
25
- klass.instance_variable_set('@attributes', nil)
26
- klass.instance_variable_set('@header', nil)
27
- klass.instance_variable_set('@dsl', nil)
28
- end
29
-
30
- # include a registered plugin in this mapper
31
- #
32
- # @param [Symbol] plugin
33
- # @param [Hash] options
34
- # @option options [Symbol] :adapter (:default) first adapter to check for plugin
35
- #
36
- # @api public
37
- def use(plugin, options = {})
38
- adapter = options.fetch(:adapter, :default)
39
-
40
- ROM.plugin_registry.mappers.fetch(plugin, adapter).apply_to(self)
41
- end
42
-
43
- # Return base_relation used for creating mapper registry
44
- #
45
- # This is used to "gather" mappers under same root name
46
- #
47
- # @api private
48
- def base_relation
49
- if superclass.relation
50
- superclass.relation
51
- else
52
- relation
53
- end
54
- end
55
-
56
- # Return header of the mapper
57
- #
58
- # This is memoized so mutating mapper class won't have an effect wrt
59
- # header after it was initialized for the first time.
60
- #
61
- # TODO: freezing mapper class here is probably a good idea
62
- #
63
- # @api private
64
- def header
65
- @header ||= dsl.header
66
- end
67
-
68
- # @api private
69
- def respond_to_missing?(name, _include_private = false)
70
- dsl.respond_to?(name) || super
71
- end
72
-
73
- private
74
-
75
- # Return default Attribute DSL options based on settings of the mapper
76
- # class
77
- #
78
- # @api private
79
- def options
80
- { prefix: prefix,
81
- prefix_separator: prefix_separator,
82
- symbolize_keys: symbolize_keys,
83
- reject_keys: reject_keys }
84
- end
85
-
86
- # Return default attributes that might have been inherited from the
87
- # superclass
88
- #
89
- # @api private
90
- def attributes
91
- @attributes ||=
92
- if superclass.respond_to?(:attributes, true) && inherit_header
93
- superclass.attributes.dup
94
- else
95
- []
96
- end
97
- end
98
-
99
- # Create the attribute DSL instance used by the mapper class
100
- #
101
- # @api private
102
- def dsl
103
- @dsl ||= AttributeDSL.new(attributes, options)
104
- end
105
-
106
- # Delegate Attribute DSL method to the dsl instance
107
- #
108
- # @api private
109
- def method_missing(name, *args, &block)
110
- if dsl.respond_to?(name)
111
- dsl.public_send(name, *args, &block)
112
- else
113
- super
114
- end
115
- end
116
- end
117
- end
118
- end
119
- end
@@ -1,55 +0,0 @@
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,101 +0,0 @@
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
- include Options
17
-
18
- option :name, reader: true
19
-
20
- attr_reader :const_name, :namespace, :klass
21
-
22
- # Return model builder subclass based on type
23
- #
24
- # @param [Symbol] type
25
- #
26
- # @return [Class]
27
- #
28
- # @api private
29
- def self.[](type)
30
- case type
31
- when :poro then PORO
32
- else
33
- raise ArgumentError, "#{type.inspect} is not a supported model type"
34
- end
35
- end
36
-
37
- # Build a model class
38
- #
39
- # @return [Class]
40
- #
41
- # @api private
42
- def self.call(*args)
43
- new(*args).call
44
- end
45
-
46
- # @api private
47
- def initialize(options = {})
48
- super
49
-
50
- if name
51
- parts = name.split('::')
52
-
53
- @const_name = parts.pop
54
-
55
- @namespace =
56
- if parts.any?
57
- Inflector.constantize(parts.join('::'))
58
- else
59
- Object
60
- end
61
- end
62
- end
63
-
64
- # Define a model class constant
65
- #
66
- # @api private
67
- def define_const
68
- namespace.const_set(const_name, klass)
69
- end
70
-
71
- # Build a model class supporting specific attributes
72
- #
73
- # @return [Class]
74
- #
75
- # @api private
76
- def call(attrs)
77
- define_class(attrs)
78
- define_const if const_name
79
- @klass
80
- end
81
-
82
- # PORO model class builder
83
- #
84
- # @private
85
- class PORO < ModelBuilder
86
- def define_class(attrs)
87
- @klass = Class.new
88
-
89
- @klass.send(:attr_reader, *attrs)
90
-
91
- @klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
92
- def initialize(params)
93
- #{attrs.map { |name| "@#{name} = params[:#{name}]" }.join("\n")}
94
- end
95
- RUBY
96
-
97
- self
98
- end
99
- end
100
- end
101
- end
data/lib/rom/processor.rb DELETED
@@ -1,28 +0,0 @@
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
@@ -1,388 +0,0 @@
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