rom 0.6.2 → 0.7.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.
- 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
|