rom 0.8.1 → 0.9.0.beta1

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