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
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'rom/relation/loaded'
|
2
|
+
require 'rom/relation/materializable'
|
3
|
+
require 'rom/pipeline'
|
4
|
+
|
5
|
+
module ROM
|
6
|
+
class Relation
|
7
|
+
# Load a relation with its associations
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# ROM.setup(:memory)
|
11
|
+
#
|
12
|
+
# class Users < ROM::Relation[:memory]
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# class Tasks < ROM::Relation[:memory]
|
16
|
+
# def for_users(users)
|
17
|
+
# restrict(user: users.map { |user| user[:name] })
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# rom = ROM.finalize.env
|
22
|
+
#
|
23
|
+
# rom.relations[:users] << { name: 'Jane' }
|
24
|
+
# rom.relations[:tasks] << { user: 'Jane', title: 'Do something' }
|
25
|
+
#
|
26
|
+
# rom.relation(:users).combine(rom.relation(:tasks).for_users)
|
27
|
+
#
|
28
|
+
# @api public
|
29
|
+
class Graph
|
30
|
+
include Materializable
|
31
|
+
include Pipeline
|
32
|
+
include Pipeline::Proxy
|
33
|
+
|
34
|
+
# Root aka parent relation
|
35
|
+
#
|
36
|
+
# @return [Relation::Lazy]
|
37
|
+
#
|
38
|
+
# @api private
|
39
|
+
attr_reader :root
|
40
|
+
|
41
|
+
# Child relation nodes
|
42
|
+
#
|
43
|
+
# @return [Array<Relation::Lazy>]
|
44
|
+
#
|
45
|
+
# @api private
|
46
|
+
attr_reader :nodes
|
47
|
+
|
48
|
+
alias_method :left, :root
|
49
|
+
alias_method :right, :nodes
|
50
|
+
|
51
|
+
# @api private
|
52
|
+
def initialize(root, nodes)
|
53
|
+
@root = root
|
54
|
+
@nodes = nodes
|
55
|
+
end
|
56
|
+
|
57
|
+
# Materialize this relation graph
|
58
|
+
#
|
59
|
+
# @return [Loaded]
|
60
|
+
#
|
61
|
+
# @api public
|
62
|
+
def call(*args)
|
63
|
+
left = root.call(*args)
|
64
|
+
|
65
|
+
right =
|
66
|
+
if left.count > 0
|
67
|
+
nodes.map { |node| node.call(left) }
|
68
|
+
else
|
69
|
+
nodes.map { |node| Loaded.new(node, []) }
|
70
|
+
end
|
71
|
+
|
72
|
+
Loaded.new(self, [left, right])
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/rom/relation/lazy.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
require 'rom/relation/loaded'
|
2
2
|
require 'rom/relation/composite'
|
3
|
+
require 'rom/relation/graph'
|
4
|
+
require 'rom/relation/materializable'
|
5
|
+
require 'rom/pipeline'
|
3
6
|
|
4
7
|
module ROM
|
5
8
|
class Relation
|
@@ -28,6 +31,8 @@ module ROM
|
|
28
31
|
class Lazy
|
29
32
|
include Equalizer.new(:relation, :options)
|
30
33
|
include Options
|
34
|
+
include Materializable
|
35
|
+
include Pipeline
|
31
36
|
|
32
37
|
option :mappers, reader: true, default: EMPTY_HASH
|
33
38
|
|
@@ -41,27 +46,24 @@ module ROM
|
|
41
46
|
# @return [Hash<Symbol=>TrueClass>]
|
42
47
|
#
|
43
48
|
# @api private
|
44
|
-
attr_reader :
|
49
|
+
attr_reader :exposed_relations
|
45
50
|
|
46
51
|
# @api private
|
47
52
|
def initialize(relation, options = {})
|
48
53
|
super
|
49
54
|
@relation = relation
|
50
|
-
@
|
55
|
+
@exposed_relations = @relation.exposed_relations
|
51
56
|
end
|
52
57
|
|
53
|
-
#
|
58
|
+
# Eager load other relation(s) for this relation
|
54
59
|
#
|
55
|
-
# @
|
56
|
-
# users.by_name('Jane') >> tasks.for_users
|
57
|
-
#
|
58
|
-
# @param [Relation] other The right relation
|
60
|
+
# @param [Array<Relation>] others The other relation(s) to eager load
|
59
61
|
#
|
60
|
-
# @return [Relation::
|
62
|
+
# @return [Relation::Graph]
|
61
63
|
#
|
62
64
|
# @api public
|
63
|
-
def
|
64
|
-
|
65
|
+
def combine(*others)
|
66
|
+
Graph.new(self, others)
|
65
67
|
end
|
66
68
|
|
67
69
|
# Build a relation pipeline using registered mappers
|
@@ -78,16 +80,6 @@ module ROM
|
|
78
80
|
end
|
79
81
|
alias_method :as, :map_with
|
80
82
|
|
81
|
-
# Coerce lazy relation to an array
|
82
|
-
#
|
83
|
-
# @return [Array]
|
84
|
-
#
|
85
|
-
# @api public
|
86
|
-
def to_a
|
87
|
-
call.to_a
|
88
|
-
end
|
89
|
-
alias_method :to_ary, :to_a
|
90
|
-
|
91
83
|
# Load relation
|
92
84
|
#
|
93
85
|
# @return [Relation::Loaded]
|
@@ -97,50 +89,9 @@ module ROM
|
|
97
89
|
Loaded.new(relation)
|
98
90
|
end
|
99
91
|
|
100
|
-
# Yield relation tuples
|
101
|
-
#
|
102
|
-
# @yield [Object]
|
103
|
-
#
|
104
|
-
# @api public
|
105
|
-
def each(&block)
|
106
|
-
return to_enum unless block
|
107
|
-
to_a.each { |tuple| yield(tuple) }
|
108
|
-
end
|
109
|
-
|
110
|
-
# Delegate to loaded relation and return one object
|
111
|
-
#
|
112
|
-
# @return [Object]
|
113
|
-
#
|
114
|
-
# @see Loaded#one
|
115
|
-
#
|
116
|
-
# @api public
|
117
|
-
def one
|
118
|
-
call.one
|
119
|
-
end
|
120
|
-
|
121
|
-
# Delegate to loaded relation and return one object
|
122
|
-
#
|
123
|
-
# @return [Object]
|
124
|
-
#
|
125
|
-
# @see Loaded#one
|
126
|
-
#
|
127
|
-
# @api public
|
128
|
-
def one!
|
129
|
-
call.one!
|
130
|
-
end
|
131
|
-
|
132
|
-
# Return first tuple from a relation coerced to an array
|
133
|
-
#
|
134
|
-
# @return [Object]
|
135
|
-
#
|
136
|
-
# @api public
|
137
|
-
def first
|
138
|
-
to_a.first
|
139
|
-
end
|
140
|
-
|
141
92
|
# @api private
|
142
93
|
def respond_to_missing?(name, include_private = false)
|
143
|
-
|
94
|
+
exposed_relations.include?(name) || super
|
144
95
|
end
|
145
96
|
|
146
97
|
# Return if this lazy relation is curried
|
@@ -162,13 +113,14 @@ module ROM
|
|
162
113
|
#
|
163
114
|
# @api private
|
164
115
|
def method_missing(meth, *args, &block)
|
165
|
-
if !
|
116
|
+
if !exposed_relations.include?(meth) || (curried? && name != meth)
|
166
117
|
super
|
167
118
|
else
|
168
119
|
arity = relation.method(meth).arity
|
169
120
|
|
170
121
|
if arity < 0 || arity == args.size
|
171
122
|
response = relation.__send__(meth, *args, &block)
|
123
|
+
|
172
124
|
if response.is_a?(Relation)
|
173
125
|
__new__(response)
|
174
126
|
else
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module ROM
|
2
|
+
class Relation
|
3
|
+
# Interface for objects that can be materialized into a loaded relation
|
4
|
+
#
|
5
|
+
# @api public
|
6
|
+
module Materializable
|
7
|
+
# @abstract
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
def call(*)
|
11
|
+
raise NotImplementedError, "#{self.class}#call must be implemented"
|
12
|
+
end
|
13
|
+
|
14
|
+
# Coerce the relation to an array
|
15
|
+
#
|
16
|
+
# @return [Array]
|
17
|
+
#
|
18
|
+
# @api public
|
19
|
+
def to_a
|
20
|
+
call.to_a
|
21
|
+
end
|
22
|
+
alias_method :to_ary, :to_a
|
23
|
+
|
24
|
+
# Yield relation tuples
|
25
|
+
#
|
26
|
+
# @yield [Hash,Object]
|
27
|
+
#
|
28
|
+
# @api public
|
29
|
+
def each(&block)
|
30
|
+
return to_enum unless block
|
31
|
+
to_a.each { |tuple| yield(tuple) }
|
32
|
+
end
|
33
|
+
|
34
|
+
# Delegate to loaded relation and return one object
|
35
|
+
#
|
36
|
+
# @return [Object]
|
37
|
+
#
|
38
|
+
# @see Loaded#one
|
39
|
+
#
|
40
|
+
# @api public
|
41
|
+
def one
|
42
|
+
call.one
|
43
|
+
end
|
44
|
+
|
45
|
+
# Delegate to loaded relation and return one object
|
46
|
+
#
|
47
|
+
# @return [Object]
|
48
|
+
#
|
49
|
+
# @see Loaded#one
|
50
|
+
#
|
51
|
+
# @api public
|
52
|
+
def one!
|
53
|
+
call.one!
|
54
|
+
end
|
55
|
+
|
56
|
+
# Return first tuple from a relation coerced to an array
|
57
|
+
#
|
58
|
+
# @return [Object]
|
59
|
+
#
|
60
|
+
# @api public
|
61
|
+
def first
|
62
|
+
to_a.first
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/rom/setup/finalize.rb
CHANGED
@@ -17,14 +17,15 @@ module ROM
|
|
17
17
|
# @private
|
18
18
|
class Finalize
|
19
19
|
attr_reader :repositories, :repo_adapter, :datasets,
|
20
|
-
:relation_classes, :mapper_classes, :command_classes
|
20
|
+
:relation_classes, :mapper_classes, :mappers, :command_classes
|
21
21
|
|
22
22
|
# @api private
|
23
|
-
def initialize(repositories, relation_classes,
|
23
|
+
def initialize(repositories, relation_classes, mappers, command_classes)
|
24
24
|
@repositories = repositories
|
25
25
|
@repo_adapter_map = ROM.repositories
|
26
26
|
@relation_classes = relation_classes
|
27
|
-
@mapper_classes =
|
27
|
+
@mapper_classes = mappers.select { |mapper| mapper.is_a?(Class) }
|
28
|
+
@mappers = (mappers - @mapper_classes).reduce(:merge) || {}
|
28
29
|
@command_classes = command_classes
|
29
30
|
initialize_datasets
|
30
31
|
end
|
@@ -83,9 +84,19 @@ module ROM
|
|
83
84
|
# @api private
|
84
85
|
def load_mappers
|
85
86
|
mapper_registry = Mapper.registry(mapper_classes).each_with_object({})
|
86
|
-
|
87
|
-
|
87
|
+
|
88
|
+
registry_hash = mapper_registry.each { |(relation, mappers), h|
|
89
|
+
h[relation] = MapperRegistry.new(mappers)
|
90
|
+
}
|
91
|
+
|
92
|
+
mappers.each do |relation, mappers|
|
93
|
+
if registry_hash.key?(relation)
|
94
|
+
mappers.each { |name, mapper| registry[name] = mapper }
|
95
|
+
else
|
96
|
+
registry_hash[relation] = MapperRegistry.new(mappers)
|
97
|
+
end
|
88
98
|
end
|
99
|
+
|
89
100
|
Registry.new(registry_hash)
|
90
101
|
end
|
91
102
|
|
@@ -6,10 +6,11 @@ module ROM
|
|
6
6
|
#
|
7
7
|
# @private
|
8
8
|
class MapperDSL
|
9
|
-
attr_reader :
|
9
|
+
attr_reader :registry
|
10
10
|
|
11
11
|
# @api private
|
12
|
-
def initialize(&block)
|
12
|
+
def initialize(registry, &block)
|
13
|
+
@registry = registry
|
13
14
|
instance_exec(&block)
|
14
15
|
end
|
15
16
|
|
@@ -25,6 +26,13 @@ module ROM
|
|
25
26
|
Mapper.build_class(name, options, &block)
|
26
27
|
self
|
27
28
|
end
|
29
|
+
|
30
|
+
# TODO
|
31
|
+
#
|
32
|
+
# @api public
|
33
|
+
def register(relation, mappers)
|
34
|
+
registry.register_mapper(relation => mappers)
|
35
|
+
end
|
28
36
|
end
|
29
37
|
end
|
30
38
|
end
|
data/lib/rom/setup_dsl/setup.rb
CHANGED
@@ -14,13 +14,16 @@ module ROM
|
|
14
14
|
#
|
15
15
|
# @api private
|
16
16
|
def self.included(klass)
|
17
|
-
klass.
|
17
|
+
klass.class_eval do
|
18
|
+
include Options
|
19
|
+
include DataProxy
|
20
|
+
end
|
18
21
|
end
|
19
22
|
|
20
23
|
forward(
|
21
24
|
:*, :+, :-, :compact, :compact!, :flatten, :flatten!, :length, :pop,
|
22
|
-
:reverse, :reverse!, :sample, :
|
23
|
-
:slice, :slice!, :sort!, :
|
25
|
+
:reverse, :reverse!, :sample, :size, :shift, :shuffle, :shuffle!,
|
26
|
+
:slice, :slice!, :sort!, :uniq, :uniq!, :unshift, :values_at
|
24
27
|
)
|
25
28
|
|
26
29
|
[
|
@@ -30,7 +33,7 @@ module ROM
|
|
30
33
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
31
34
|
def #{method}(*args, &block)
|
32
35
|
return to_enum unless block
|
33
|
-
self.class.new(data.send(:#{method}, *args, &block))
|
36
|
+
self.class.new(data.send(:#{method}, *args, &block), options)
|
34
37
|
end
|
35
38
|
RUBY
|
36
39
|
end
|
@@ -12,6 +12,8 @@ module ROM
|
|
12
12
|
:each, :to_a, :to_ary, :kind_of?, :instance_of?, :is_a?
|
13
13
|
].freeze
|
14
14
|
|
15
|
+
# Wrapped data array
|
16
|
+
#
|
15
17
|
# @return [Object] Data object for the iterator
|
16
18
|
#
|
17
19
|
# @api private
|
@@ -30,19 +32,17 @@ module ROM
|
|
30
32
|
def self.included(klass)
|
31
33
|
klass.class_eval do
|
32
34
|
extend ClassMethods
|
35
|
+
|
33
36
|
include Equalizer.new(:data)
|
37
|
+
|
38
|
+
option :row_proc, reader: true, default: proc { |obj| obj.class.row_proc }
|
34
39
|
end
|
35
40
|
end
|
36
41
|
|
37
|
-
# Constructor for dataset objects
|
38
|
-
#
|
39
|
-
# @param [Object] data
|
40
|
-
# @param [Proc] row_proc processing proc
|
41
|
-
#
|
42
42
|
# @api private
|
43
|
-
def initialize(data,
|
43
|
+
def initialize(data, options = {})
|
44
44
|
@data = data
|
45
|
-
|
45
|
+
super(data, options)
|
46
46
|
end
|
47
47
|
|
48
48
|
# Iterate over data using row_proc
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ROM
|
2
|
+
module Deprecations
|
3
|
+
# @api private
|
4
|
+
def deprecate(old_name, new_name, msg = nil)
|
5
|
+
class_eval do
|
6
|
+
define_method(old_name) do |*args, &block|
|
7
|
+
warn <<-MSG.gsub(/^\s+/, '')
|
8
|
+
#{self.class}##{old_name} is deprecated and will be removed in 1.0.0.
|
9
|
+
Please use #{self.class}##{new_name} instead."
|
10
|
+
#{msg}
|
11
|
+
MSG
|
12
|
+
__send__(new_name, *args, &block)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -23,6 +23,7 @@ module ROM
|
|
23
23
|
#
|
24
24
|
# @api public
|
25
25
|
module EnumerableDataset
|
26
|
+
extend DataProxy::ClassMethods
|
26
27
|
include Enumerable
|
27
28
|
|
28
29
|
# Coerce a dataset to an array
|
@@ -40,17 +41,23 @@ module ROM
|
|
40
41
|
# @api private
|
41
42
|
def self.included(klass)
|
42
43
|
return unless klass.is_a?(Class)
|
43
|
-
|
44
|
+
|
45
|
+
klass.class_eval do
|
46
|
+
include Options
|
47
|
+
include DataProxy
|
48
|
+
end
|
44
49
|
end
|
45
50
|
|
51
|
+
forward :take
|
52
|
+
|
46
53
|
[
|
47
54
|
:chunk, :collect, :collect_concat, :drop_while, :find_all, :flat_map,
|
48
|
-
:grep, :map, :reject, :select, :sort, :sort_by, :
|
55
|
+
:grep, :map, :reject, :select, :sort, :sort_by, :take_while
|
49
56
|
].each do |method|
|
50
57
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
51
58
|
def #{method}(*args, &block)
|
52
59
|
return to_enum unless block
|
53
|
-
self.class.new(super(*args, &block))
|
60
|
+
self.class.new(super(*args, &block), options)
|
54
61
|
end
|
55
62
|
RUBY
|
56
63
|
end
|