rom 0.6.2 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.rubocop.yml +9 -0
- data/CHANGELOG.md +34 -0
- data/CONTRIBUTING.md +1 -0
- data/Gemfile +1 -1
- data/README.md +12 -7
- data/lib/rom.rb +8 -0
- data/lib/rom/command.rb +19 -0
- data/lib/rom/commands/abstract.rb +6 -1
- data/lib/rom/commands/composite.rb +1 -52
- data/lib/rom/commands/update.rb +4 -1
- data/lib/rom/constants.rb +1 -0
- data/lib/rom/env.rb +3 -25
- data/lib/rom/global.rb +23 -0
- data/lib/rom/global/plugin_dsl.rb +47 -0
- data/lib/rom/header.rb +19 -8
- data/lib/rom/header/attribute.rb +14 -2
- data/lib/rom/lint/enumerable_dataset.rb +3 -1
- data/lib/rom/lint/repository.rb +5 -5
- data/lib/rom/mapper.rb +2 -1
- data/lib/rom/mapper/attribute_dsl.rb +86 -13
- data/lib/rom/mapper/dsl.rb +20 -1
- data/lib/rom/memory/commands.rb +3 -1
- data/lib/rom/memory/dataset.rb +1 -1
- data/lib/rom/memory/relation.rb +1 -1
- data/lib/rom/pipeline.rb +91 -0
- data/lib/rom/plugin.rb +31 -0
- data/lib/rom/plugin_registry.rb +134 -0
- data/lib/rom/plugins/relation/registry_reader.rb +30 -0
- data/lib/rom/processor/transproc.rb +78 -3
- data/lib/rom/relation/class_interface.rb +14 -2
- data/lib/rom/relation/composite.rb +9 -97
- data/lib/rom/relation/graph.rb +76 -0
- data/lib/rom/relation/lazy.rb +15 -63
- data/lib/rom/relation/materializable.rb +66 -0
- data/lib/rom/setup/finalize.rb +16 -5
- data/lib/rom/setup_dsl/mapper_dsl.rb +10 -2
- data/lib/rom/setup_dsl/setup.rb +1 -1
- data/lib/rom/support/array_dataset.rb +7 -4
- data/lib/rom/support/data_proxy.rb +7 -7
- data/lib/rom/support/deprecations.rb +17 -0
- data/lib/rom/support/enumerable_dataset.rb +10 -3
- data/lib/rom/support/inflector.rb +1 -1
- data/lib/rom/version.rb +1 -1
- data/rom.gemspec +1 -1
- data/spec/integration/commands/create_spec.rb +3 -3
- data/spec/integration/commands/update_spec.rb +24 -4
- data/spec/integration/mappers/combine_spec.rb +107 -0
- data/spec/integration/mappers/registering_custom_mappers_spec.rb +29 -0
- data/spec/integration/mappers/reusing_mappers_spec.rb +22 -0
- data/spec/integration/mappers/unwrap_spec.rb +98 -0
- data/spec/integration/multi_repo_spec.rb +2 -2
- data/spec/integration/repositories/extending_relations_spec.rb +9 -0
- data/spec/integration/setup_spec.rb +2 -2
- data/spec/shared/enumerable_dataset.rb +4 -1
- data/spec/shared/materializable.rb +34 -0
- data/spec/shared/proxy.rb +0 -0
- data/spec/spec_helper.rb +6 -2
- data/spec/support/mutant.rb +9 -6
- data/spec/unit/rom/commands_spec.rb +3 -3
- data/spec/unit/rom/header_spec.rb +2 -2
- data/spec/unit/rom/mapper/dsl_spec.rb +102 -1
- data/spec/unit/rom/memory/dataset_spec.rb +10 -33
- data/spec/unit/rom/memory/relation_spec.rb +63 -0
- data/spec/unit/rom/memory/storage_spec.rb +2 -2
- data/spec/unit/rom/plugin_spec.rb +121 -0
- data/spec/unit/rom/processor/transproc_spec.rb +47 -6
- data/spec/unit/rom/relation/composite_spec.rb +3 -1
- data/spec/unit/rom/relation/graph_spec.rb +78 -0
- data/spec/unit/rom/relation/lazy/combine_spec.rb +130 -0
- data/spec/unit/rom/relation/lazy_spec.rb +3 -1
- data/spec/unit/rom/relation/loaded_spec.rb +3 -1
- data/spec/unit/rom/setup_spec.rb +8 -8
- data/spec/unit/rom/support/array_dataset_spec.rb +3 -1
- data/spec/unit/rom/support/class_builder_spec.rb +2 -2
- metadata +24 -7
- data/lib/rom/relation/registry_reader.rb +0 -23
data/lib/rom/header.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'equalizer'
|
2
|
+
|
3
|
+
require 'rom/support/options'
|
1
4
|
require 'rom/header/attribute'
|
2
5
|
|
3
6
|
module ROM
|
@@ -9,15 +12,18 @@ module ROM
|
|
9
12
|
# @private
|
10
13
|
class Header
|
11
14
|
include Enumerable
|
15
|
+
include Options
|
12
16
|
include Equalizer.new(:attributes, :model)
|
13
17
|
|
14
|
-
# @api private
|
15
|
-
attr_reader :attributes
|
16
|
-
|
17
18
|
# @return [Class] optional model associated with a header
|
18
19
|
#
|
19
20
|
# @api private
|
20
|
-
|
21
|
+
option :model, reader: true
|
22
|
+
|
23
|
+
option :reject_keys, reader: true, default: false
|
24
|
+
|
25
|
+
# @api private
|
26
|
+
attr_reader :attributes
|
21
27
|
|
22
28
|
# @return [Hash] attribute key/name mapping for all primitive attributes
|
23
29
|
#
|
@@ -38,7 +44,7 @@ module ROM
|
|
38
44
|
# @return [Header]
|
39
45
|
#
|
40
46
|
# @api private
|
41
|
-
def self.coerce(input,
|
47
|
+
def self.coerce(input, options = {})
|
42
48
|
if input.instance_of?(self)
|
43
49
|
input
|
44
50
|
else
|
@@ -46,14 +52,14 @@ module ROM
|
|
46
52
|
h[pair.first] = Attribute.coerce(pair)
|
47
53
|
}
|
48
54
|
|
49
|
-
new(attributes,
|
55
|
+
new(attributes, options)
|
50
56
|
end
|
51
57
|
end
|
52
58
|
|
53
59
|
# @api private
|
54
|
-
def initialize(attributes,
|
60
|
+
def initialize(attributes, options = {})
|
61
|
+
super
|
55
62
|
@attributes = attributes
|
56
|
-
@model = model
|
57
63
|
initialize_mapping
|
58
64
|
initialize_tuple_keys
|
59
65
|
end
|
@@ -110,6 +116,11 @@ module ROM
|
|
110
116
|
by_type(Wrap)
|
111
117
|
end
|
112
118
|
|
119
|
+
# @api private
|
120
|
+
def combined
|
121
|
+
by_type(Combined)
|
122
|
+
end
|
123
|
+
|
113
124
|
# Return all primitive attributes (no Group and Wrap)
|
114
125
|
#
|
115
126
|
# @return [Array<Attribute>]
|
data/lib/rom/header/attribute.rb
CHANGED
@@ -41,7 +41,11 @@ module ROM
|
|
41
41
|
def self.[](meta)
|
42
42
|
type = meta[:type]
|
43
43
|
|
44
|
-
if
|
44
|
+
if meta[:combine]
|
45
|
+
Combined
|
46
|
+
elsif meta[:unwrap]
|
47
|
+
Unwrap
|
48
|
+
elsif type.equal?(:hash)
|
45
49
|
meta[:wrap] ? Wrap : Hash
|
46
50
|
elsif type.equal?(:array)
|
47
51
|
meta[:group] ? Group : Array
|
@@ -64,7 +68,7 @@ module ROM
|
|
64
68
|
meta[:type] ||= :object
|
65
69
|
|
66
70
|
if meta.key?(:header)
|
67
|
-
meta[:header] = Header.coerce(meta[:header], meta[:model])
|
71
|
+
meta[:header] = Header.coerce(meta[:header], model: meta[:model])
|
68
72
|
end
|
69
73
|
|
70
74
|
self[meta].new(name, meta)
|
@@ -137,10 +141,18 @@ module ROM
|
|
137
141
|
# Hash is an embedded attribute type
|
138
142
|
Hash = Class.new(Embedded)
|
139
143
|
|
144
|
+
# Combined is an embedded attribute type describing combination of multiple
|
145
|
+
# relations
|
146
|
+
Combined = Class.new(Embedded)
|
147
|
+
|
140
148
|
# Wrap is a special type of Hash attribute that requires wrapping
|
141
149
|
# transformation
|
142
150
|
Wrap = Class.new(Hash)
|
143
151
|
|
152
|
+
# Unwrap is a special type of Hash attribute that requires unwrapping
|
153
|
+
# transformation
|
154
|
+
Unwrap = Class.new(Hash)
|
155
|
+
|
144
156
|
# Group is a special type of Array attribute that requires grouping
|
145
157
|
# transformation
|
146
158
|
Group = Class.new(Array)
|
data/lib/rom/lint/repository.rb
CHANGED
@@ -22,6 +22,11 @@ module ROM
|
|
22
22
|
# @api public
|
23
23
|
attr_reader :uri
|
24
24
|
|
25
|
+
# Repository instance used in lint tests
|
26
|
+
#
|
27
|
+
# @api private
|
28
|
+
attr_reader :repository_instance
|
29
|
+
|
25
30
|
# Create a repository linter
|
26
31
|
#
|
27
32
|
# @param [Symbol] identifier
|
@@ -66,11 +71,6 @@ module ROM
|
|
66
71
|
|
67
72
|
private
|
68
73
|
|
69
|
-
# Repository instance
|
70
|
-
#
|
71
|
-
# @api private
|
72
|
-
attr_reader :repository_instance
|
73
|
-
|
74
74
|
# Setup repository instance
|
75
75
|
#
|
76
76
|
# @api private
|
data/lib/rom/mapper.rb
CHANGED
@@ -9,9 +9,10 @@ module ROM
|
|
9
9
|
include Equalizer.new(:transformer, :header)
|
10
10
|
|
11
11
|
defines :relation, :register_as, :symbolize_keys,
|
12
|
-
:prefix, :prefix_separator, :inherit_header
|
12
|
+
:prefix, :prefix_separator, :inherit_header, :reject_keys
|
13
13
|
|
14
14
|
inherit_header true
|
15
|
+
reject_keys false
|
15
16
|
prefix_separator '_'.freeze
|
16
17
|
|
17
18
|
# @return [Object] transformer object built by a processor
|
@@ -8,11 +8,14 @@ module ROM
|
|
8
8
|
# This class is private even though its methods are exposed by mappers.
|
9
9
|
# Typically it's not meant to be used directly.
|
10
10
|
#
|
11
|
-
#
|
11
|
+
# TODO: break this madness down into smaller pieces
|
12
|
+
#
|
13
|
+
# @api private
|
12
14
|
class AttributeDSL
|
13
15
|
include ModelDSL
|
14
16
|
|
15
|
-
attr_reader :attributes, :options, :symbolize_keys, :prefix,
|
17
|
+
attr_reader :attributes, :options, :symbolize_keys, :prefix,
|
18
|
+
:prefix_separator, :reject_keys
|
16
19
|
|
17
20
|
# @param [Array] attributes accumulator array
|
18
21
|
# @param [Hash] options
|
@@ -24,6 +27,7 @@ module ROM
|
|
24
27
|
@symbolize_keys = options.fetch(:symbolize_keys)
|
25
28
|
@prefix = options.fetch(:prefix)
|
26
29
|
@prefix_separator = options.fetch(:prefix_separator)
|
30
|
+
@reject_keys = options.fetch(:reject_keys)
|
27
31
|
end
|
28
32
|
|
29
33
|
# Define a mapping attribute with its options
|
@@ -80,13 +84,19 @@ module ROM
|
|
80
84
|
# @api public
|
81
85
|
def embedded(name, options, &block)
|
82
86
|
with_attr_options(name) do |attr_options|
|
83
|
-
|
84
|
-
|
85
|
-
attr_options.update(options)
|
87
|
+
mapper = options[:mapper]
|
86
88
|
|
87
|
-
|
88
|
-
|
89
|
-
|
89
|
+
if mapper
|
90
|
+
attributes_from_mapper(
|
91
|
+
mapper, name, { type: :array }.update(attr_options)
|
92
|
+
)
|
93
|
+
else
|
94
|
+
dsl = new(options, &block)
|
95
|
+
attr_options.update(options)
|
96
|
+
add_attribute(
|
97
|
+
name, { header: dsl.header, type: :array }.update(attr_options)
|
98
|
+
)
|
99
|
+
end
|
90
100
|
end
|
91
101
|
end
|
92
102
|
|
@@ -110,8 +120,20 @@ module ROM
|
|
110
120
|
#
|
111
121
|
# @api public
|
112
122
|
def wrap(*args, &block)
|
123
|
+
with_name_or_options(*args) do |name, options, mapper|
|
124
|
+
wrap_options = { type: :hash, wrap: true }.update(options)
|
125
|
+
|
126
|
+
if mapper
|
127
|
+
attributes_from_mapper(mapper, name, wrap_options)
|
128
|
+
else
|
129
|
+
dsl(name, wrap_options, &block)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def unwrap(*args, &block)
|
113
135
|
with_name_or_options(*args) do |name, options|
|
114
|
-
dsl(name, { type: :hash,
|
136
|
+
dsl(name, { type: :hash, unwrap: true }.update(options), &block)
|
115
137
|
end
|
116
138
|
end
|
117
139
|
|
@@ -133,18 +155,56 @@ module ROM
|
|
133
155
|
#
|
134
156
|
# @api public
|
135
157
|
def group(*args, &block)
|
136
|
-
with_name_or_options(*args) do |name, options|
|
137
|
-
|
158
|
+
with_name_or_options(*args) do |name, options, mapper|
|
159
|
+
group_options = { type: :array, group: true }.update(options)
|
160
|
+
|
161
|
+
if mapper
|
162
|
+
attributes_from_mapper(mapper, name, group_options)
|
163
|
+
else
|
164
|
+
dsl(name, group_options, &block)
|
165
|
+
end
|
138
166
|
end
|
139
167
|
end
|
140
168
|
|
169
|
+
# Define an embedded combined attribute that requires "combine" transformation
|
170
|
+
#
|
171
|
+
# Typically this can be used to process results of eager-loading
|
172
|
+
#
|
173
|
+
# @example
|
174
|
+
# dsl = AttributeDSL.new([])
|
175
|
+
#
|
176
|
+
# dsl.combine(:tags, user_id: :id) do
|
177
|
+
# model Tag
|
178
|
+
#
|
179
|
+
# attribute :name
|
180
|
+
# end
|
181
|
+
#
|
182
|
+
# @param [Symbol] name
|
183
|
+
# @param [Hash] options
|
184
|
+
# @option options [Hash] :on The "join keys"
|
185
|
+
# @option options [Symbol] :type The type, either :array (default) or :hash
|
186
|
+
#
|
187
|
+
# @api public
|
188
|
+
def combine(name, options, &block)
|
189
|
+
dsl = new(options, &block)
|
190
|
+
|
191
|
+
attr_opts = {
|
192
|
+
type: options.fetch(:type, :array),
|
193
|
+
keys: options.fetch(:on),
|
194
|
+
combine: true,
|
195
|
+
header: dsl.header,
|
196
|
+
}
|
197
|
+
|
198
|
+
add_attribute(name, attr_opts)
|
199
|
+
end
|
200
|
+
|
141
201
|
# Generate a header from attribute definitions
|
142
202
|
#
|
143
203
|
# @return [Header]
|
144
204
|
#
|
145
205
|
# @api private
|
146
206
|
def header
|
147
|
-
Header.coerce(attributes, model)
|
207
|
+
Header.coerce(attributes, model: model, reject_keys: reject_keys)
|
148
208
|
end
|
149
209
|
|
150
210
|
private
|
@@ -175,7 +235,7 @@ module ROM
|
|
175
235
|
[args.first, {}]
|
176
236
|
end
|
177
237
|
|
178
|
-
yield(name, options)
|
238
|
+
yield(name, options, options[:mapper])
|
179
239
|
end
|
180
240
|
|
181
241
|
# Create another instance of the dsl for nested definitions
|
@@ -217,6 +277,19 @@ module ROM
|
|
217
277
|
end
|
218
278
|
end
|
219
279
|
|
280
|
+
# Infer mapper header for an embedded attribute
|
281
|
+
#
|
282
|
+
# @api private
|
283
|
+
def attributes_from_mapper(mapper, name, options)
|
284
|
+
if mapper.is_a?(Class)
|
285
|
+
add_attribute(name, { header: mapper.header }.update(options))
|
286
|
+
else
|
287
|
+
raise(
|
288
|
+
ArgumentError, ":mapper must be a class #{mapper.inspect}"
|
289
|
+
)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
220
293
|
# Add a new attribute and make sure it overrides previous definition
|
221
294
|
#
|
222
295
|
# @api private
|
data/lib/rom/mapper/dsl.rb
CHANGED
@@ -27,6 +27,19 @@ module ROM
|
|
27
27
|
klass.instance_variable_set('@dsl', nil)
|
28
28
|
end
|
29
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
|
+
|
30
43
|
# Return base_relation used for creating mapper registry
|
31
44
|
#
|
32
45
|
# This is used to "gather" mappers under same root name
|
@@ -52,6 +65,11 @@ module ROM
|
|
52
65
|
@header ||= dsl.header
|
53
66
|
end
|
54
67
|
|
68
|
+
# @api private
|
69
|
+
def respond_to_missing?(name, _include_private = false)
|
70
|
+
dsl.respond_to?(name) || super
|
71
|
+
end
|
72
|
+
|
55
73
|
private
|
56
74
|
|
57
75
|
# Return default Attribute DSL options based on settings of the mapper
|
@@ -61,7 +79,8 @@ module ROM
|
|
61
79
|
def options
|
62
80
|
{ prefix: prefix,
|
63
81
|
prefix_separator: prefix_separator,
|
64
|
-
symbolize_keys: symbolize_keys
|
82
|
+
symbolize_keys: symbolize_keys,
|
83
|
+
reject_keys: reject_keys }
|
65
84
|
end
|
66
85
|
|
67
86
|
# Return default attributes that might have been inherited from the
|
data/lib/rom/memory/commands.rb
CHANGED
data/lib/rom/memory/dataset.rb
CHANGED
data/lib/rom/memory/relation.rb
CHANGED
data/lib/rom/pipeline.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
module ROM
|
2
|
+
# Data pipeline common interface
|
3
|
+
#
|
4
|
+
# @api private
|
5
|
+
module Pipeline
|
6
|
+
# Compose two relation with a left-to-right composition
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# users.by_name('Jane') >> tasks.for_users
|
10
|
+
#
|
11
|
+
# @param [Relation] other The right relation
|
12
|
+
#
|
13
|
+
# @return [Relation::Composite]
|
14
|
+
#
|
15
|
+
# @api public
|
16
|
+
def >>(other)
|
17
|
+
Relation::Composite.new(self, other)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Send data through specified mappers
|
21
|
+
#
|
22
|
+
# @return [Relation::Composite]
|
23
|
+
#
|
24
|
+
# @api public
|
25
|
+
def map_with(*names)
|
26
|
+
[self, *names.map { |name| mappers[name] }]
|
27
|
+
.reduce { |a, e| Relation::Composite.new(a, e) }
|
28
|
+
end
|
29
|
+
alias_method :as, :map_with
|
30
|
+
|
31
|
+
# Forwards messages to the left side of a pipeline
|
32
|
+
#
|
33
|
+
# @api private
|
34
|
+
module Proxy
|
35
|
+
# @api private
|
36
|
+
def respond_to_missing?(name, include_private = false)
|
37
|
+
left.respond_to?(name) || super
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# Check if response from method missing should be decorated
|
43
|
+
#
|
44
|
+
# @api private
|
45
|
+
def decorate?(response)
|
46
|
+
response.is_a?(left.class)
|
47
|
+
end
|
48
|
+
|
49
|
+
# @api private
|
50
|
+
def method_missing(name, *args, &block)
|
51
|
+
if left.respond_to?(name)
|
52
|
+
response = left.__send__(name, *args, &block)
|
53
|
+
|
54
|
+
if decorate?(response)
|
55
|
+
self.class.new(response, right)
|
56
|
+
else
|
57
|
+
response
|
58
|
+
end
|
59
|
+
else
|
60
|
+
super
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Base composite class with left-to-right pipeline behavior
|
66
|
+
#
|
67
|
+
# @api private
|
68
|
+
class Composite
|
69
|
+
include Equalizer.new(:left, :right)
|
70
|
+
include Proxy
|
71
|
+
|
72
|
+
# @api private
|
73
|
+
attr_reader :left
|
74
|
+
|
75
|
+
# @api private
|
76
|
+
attr_reader :right
|
77
|
+
|
78
|
+
# @api private
|
79
|
+
def initialize(left, right)
|
80
|
+
@left, @right = left, right
|
81
|
+
end
|
82
|
+
|
83
|
+
# Compose this composite with another object
|
84
|
+
#
|
85
|
+
# @api public
|
86
|
+
def >>(other)
|
87
|
+
self.class.new(self, other)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|