rom 1.0.0 → 2.0.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 -1
- data/.travis.yml +5 -3
- data/CHANGELOG.md +38 -0
- data/Gemfile +2 -14
- data/README.md +11 -17
- data/lib/rom.rb +2 -0
- data/lib/rom/association_set.rb +26 -0
- data/lib/rom/command.rb +50 -45
- data/lib/rom/command_registry.rb +26 -3
- data/lib/rom/commands/class_interface.rb +52 -19
- data/lib/rom/commands/composite.rb +5 -0
- data/lib/rom/commands/delete.rb +1 -5
- data/lib/rom/commands/graph.rb +11 -0
- data/lib/rom/commands/lazy.rb +2 -0
- data/lib/rom/commands/update.rb +1 -5
- data/lib/rom/configuration.rb +2 -0
- data/lib/rom/container.rb +3 -3
- data/lib/rom/global.rb +1 -23
- data/lib/rom/memory/commands.rb +2 -0
- data/lib/rom/memory/relation.rb +3 -0
- data/lib/rom/memory/storage.rb +4 -7
- data/lib/rom/memory/types.rb +9 -0
- data/lib/rom/pipeline.rb +26 -12
- data/lib/rom/plugin_registry.rb +2 -2
- data/lib/rom/plugins/command/schema.rb +26 -0
- data/lib/rom/plugins/configuration/configuration_dsl.rb +2 -1
- data/lib/rom/plugins/relation/key_inference.rb +18 -3
- data/lib/rom/plugins/relation/registry_reader.rb +3 -1
- data/lib/rom/plugins/relation/view.rb +11 -6
- data/lib/rom/relation.rb +76 -16
- data/lib/rom/relation/class_interface.rb +44 -3
- data/lib/rom/relation/curried.rb +13 -4
- data/lib/rom/relation/graph.rb +15 -5
- data/lib/rom/relation/loaded.rb +42 -6
- data/lib/rom/relation/name.rb +102 -0
- data/lib/rom/relation_registry.rb +5 -0
- data/lib/rom/schema.rb +87 -0
- data/lib/rom/schema/dsl.rb +58 -0
- data/lib/rom/setup/auto_registration.rb +2 -2
- data/lib/rom/setup/finalize.rb +5 -5
- data/lib/rom/setup/finalize/{commands.rb → finalize_commands.rb} +2 -22
- data/lib/rom/setup/finalize/{mappers.rb → finalize_mappers.rb} +0 -0
- data/lib/rom/setup/finalize/finalize_relations.rb +60 -0
- data/lib/rom/types.rb +18 -0
- data/lib/rom/version.rb +1 -1
- data/log/.gitkeep +0 -0
- data/rom.gemspec +4 -2
- data/spec/integration/command_registry_spec.rb +13 -0
- data/spec/integration/commands/delete_spec.rb +0 -17
- data/spec/integration/commands/graph_builder_spec.rb +1 -1
- data/spec/integration/commands/graph_spec.rb +1 -1
- data/spec/integration/commands/update_spec.rb +0 -19
- data/spec/integration/commands_spec.rb +10 -3
- data/spec/integration/multi_repo_spec.rb +1 -1
- data/spec/integration/relations/default_dataset_spec.rb +27 -4
- data/spec/integration/setup_spec.rb +1 -4
- data/spec/shared/command_behavior.rb +17 -7
- data/spec/shared/container.rb +2 -2
- data/spec/shared/gateway_only.rb +1 -1
- data/spec/spec_helper.rb +5 -6
- data/spec/unit/rom/association_set_spec.rb +23 -0
- data/spec/unit/rom/auto_registration_spec.rb +1 -1
- data/spec/unit/rom/commands/lazy_spec.rb +8 -0
- data/spec/unit/rom/commands_spec.rb +45 -7
- data/spec/unit/rom/configurable_spec.rb +1 -1
- data/spec/unit/rom/container_spec.rb +6 -0
- data/spec/unit/rom/create_container_spec.rb +1 -1
- data/spec/unit/rom/environment_spec.rb +1 -1
- data/spec/unit/rom/memory/commands_spec.rb +43 -0
- data/spec/unit/rom/plugins/relation/key_inference_spec.rb +70 -12
- data/spec/unit/rom/plugins/relation/view_spec.rb +4 -0
- data/spec/unit/rom/relation/graph_spec.rb +10 -0
- data/spec/unit/rom/relation/lazy_spec.rb +3 -3
- data/spec/unit/rom/relation/loaded_spec.rb +15 -0
- data/spec/unit/rom/relation/name_spec.rb +51 -0
- data/spec/unit/rom/relation/schema_spec.rb +117 -0
- data/spec/unit/rom/relation_spec.rb +37 -7
- data/spec/unit/rom/schema_spec.rb +10 -0
- metadata +51 -12
- data/lib/rom/setup/finalize/relations.rb +0 -53
- data/spec/unit/rom/global_spec.rb +0 -18
- data/spec/unit/rom/registry_spec.rb +0 -38
@@ -1,6 +1,8 @@
|
|
1
1
|
module ROM
|
2
2
|
module Plugins
|
3
3
|
module Relation
|
4
|
+
EMPTY_REGISTRY = RelationRegistry.new.freeze
|
5
|
+
|
4
6
|
# Allows relations to access all other relations through registry
|
5
7
|
#
|
6
8
|
# For now this plugin is always enabled
|
@@ -10,7 +12,7 @@ module ROM
|
|
10
12
|
# @api private
|
11
13
|
def self.included(klass)
|
12
14
|
super
|
13
|
-
klass.option :__registry__, type:
|
15
|
+
klass.option :__registry__, type: RelationRegistry, default: EMPTY_REGISTRY, reader: true
|
14
16
|
end
|
15
17
|
|
16
18
|
# @api private
|
@@ -11,6 +11,7 @@ module ROM
|
|
11
11
|
extend ClassInterface
|
12
12
|
|
13
13
|
option :view, reader: true
|
14
|
+
option :attributes
|
14
15
|
|
15
16
|
def self.attributes
|
16
17
|
@__attributes__ ||= {}
|
@@ -27,13 +28,17 @@ module ROM
|
|
27
28
|
#
|
28
29
|
# @api private
|
29
30
|
def attributes(view_name = view)
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
if header.is_a?(Proc)
|
34
|
-
instance_exec(&header)
|
31
|
+
if options.key?(:attributes)
|
32
|
+
options[:attributes]
|
35
33
|
else
|
36
|
-
header
|
34
|
+
header = self.class.attributes
|
35
|
+
.fetch(view_name, self.class.attributes.fetch(:base))
|
36
|
+
|
37
|
+
if header.is_a?(Proc)
|
38
|
+
instance_exec(&header)
|
39
|
+
else
|
40
|
+
header
|
41
|
+
end
|
37
42
|
end
|
38
43
|
end
|
39
44
|
|
data/lib/rom/relation.rb
CHANGED
@@ -8,6 +8,10 @@ require 'rom/relation/curried'
|
|
8
8
|
require 'rom/relation/composite'
|
9
9
|
require 'rom/relation/graph'
|
10
10
|
require 'rom/relation/materializable'
|
11
|
+
require 'rom/association_set'
|
12
|
+
|
13
|
+
require 'rom/types'
|
14
|
+
require 'rom/schema'
|
11
15
|
|
12
16
|
module ROM
|
13
17
|
# Base relation class
|
@@ -32,36 +36,63 @@ module ROM
|
|
32
36
|
include Materializable
|
33
37
|
include Pipeline
|
34
38
|
|
39
|
+
# @!attribute [r] mappers
|
40
|
+
# @return [MapperRegistry] an optional mapper registry (empty by default)
|
35
41
|
option :mappers, reader: true, default: proc { MapperRegistry.new }
|
36
42
|
|
37
|
-
#
|
43
|
+
# @!attribute [r] schema_hash
|
44
|
+
# @return [Object#[]] tuple processing function, uses schema or defaults to Hash[]
|
45
|
+
# @api private
|
46
|
+
option :schema_hash, reader: true, default: -> relation {
|
47
|
+
relation.schema? ? Types::Coercible::Hash.schema(relation.schema.to_h) : Hash
|
48
|
+
}
|
49
|
+
|
50
|
+
# @!attribute [r] associations
|
51
|
+
# @return [AssociationSet] Schema's association set (empty by default)
|
52
|
+
option :associations, reader: true, default: -> rel {
|
53
|
+
rel.schema? ? rel.schema.associations : Schema::EMPTY_ASSOCIATION_SET
|
54
|
+
}
|
55
|
+
|
56
|
+
# @!attribute [r] dataset
|
57
|
+
# @return [Object] dataset used by the relation provided by relation's gateway
|
58
|
+
# @api public
|
59
|
+
attr_reader :dataset
|
60
|
+
|
61
|
+
# @!attribute [r] schema
|
62
|
+
# @return [Schema] returns relation schema object (if defined)
|
63
|
+
# @api public
|
64
|
+
attr_reader :schema
|
65
|
+
|
66
|
+
# Initializes a relation object
|
38
67
|
#
|
39
|
-
#
|
68
|
+
# @param dataset [Object]
|
40
69
|
#
|
41
|
-
# @
|
70
|
+
# @param options [Hash]
|
71
|
+
# @option :mappers [MapperRegistry]
|
72
|
+
# @option :schema_hash [#[]]
|
73
|
+
# @option :associations [AssociationSet]
|
42
74
|
#
|
43
|
-
# @api
|
44
|
-
attr_reader :dataset
|
45
|
-
|
46
|
-
# @api private
|
75
|
+
# @api public
|
47
76
|
def initialize(dataset, options = EMPTY_HASH)
|
48
77
|
@dataset = dataset
|
78
|
+
@schema = self.class.schema
|
49
79
|
super
|
50
80
|
end
|
51
81
|
|
52
|
-
#
|
82
|
+
# Yields relation tuples
|
53
83
|
#
|
54
84
|
# @yield [Hash]
|
85
|
+
# @return [Enumerator] if block is not provided
|
55
86
|
#
|
56
|
-
# @api
|
87
|
+
# @api public
|
57
88
|
def each(&block)
|
58
89
|
return to_enum unless block
|
59
90
|
dataset.each { |tuple| yield(tuple) }
|
60
91
|
end
|
61
92
|
|
62
|
-
#
|
93
|
+
# Composes with other relations
|
63
94
|
#
|
64
|
-
# @param [Array<Relation>]
|
95
|
+
# @param *others [Array<Relation>] The other relation(s) to compose with
|
65
96
|
#
|
66
97
|
# @return [Relation::Graph]
|
67
98
|
#
|
@@ -70,7 +101,7 @@ module ROM
|
|
70
101
|
Graph.build(self, others)
|
71
102
|
end
|
72
103
|
|
73
|
-
#
|
104
|
+
# Loads relation
|
74
105
|
#
|
75
106
|
# @return [Relation::Loaded]
|
76
107
|
#
|
@@ -79,7 +110,7 @@ module ROM
|
|
79
110
|
Loaded.new(self)
|
80
111
|
end
|
81
112
|
|
82
|
-
#
|
113
|
+
# Materializes a relation into an array
|
83
114
|
#
|
84
115
|
# @return [Array<Hash>]
|
85
116
|
#
|
@@ -88,7 +119,7 @@ module ROM
|
|
88
119
|
to_enum.to_a
|
89
120
|
end
|
90
121
|
|
91
|
-
#
|
122
|
+
# Returns if this relation is curried
|
92
123
|
#
|
93
124
|
# @return [false]
|
94
125
|
#
|
@@ -97,9 +128,33 @@ module ROM
|
|
97
128
|
false
|
98
129
|
end
|
99
130
|
|
131
|
+
# Returns if this relation is a graph
|
132
|
+
#
|
133
|
+
# @return [false]
|
134
|
+
#
|
100
135
|
# @api private
|
101
|
-
def
|
102
|
-
|
136
|
+
def graph?
|
137
|
+
false
|
138
|
+
end
|
139
|
+
|
140
|
+
# Returns true if a relation has schema defined
|
141
|
+
#
|
142
|
+
# @return [TrueClass, FalseClass]
|
143
|
+
#
|
144
|
+
# @api private
|
145
|
+
def schema?
|
146
|
+
! schema.nil?
|
147
|
+
end
|
148
|
+
|
149
|
+
# Returns a new instance with the same dataset but new options
|
150
|
+
#
|
151
|
+
# @param new_options [Hash]
|
152
|
+
#
|
153
|
+
# @return [Relation]
|
154
|
+
#
|
155
|
+
# @api private
|
156
|
+
def with(new_options)
|
157
|
+
__new__(dataset, options.merge(new_options))
|
103
158
|
end
|
104
159
|
|
105
160
|
private
|
@@ -108,5 +163,10 @@ module ROM
|
|
108
163
|
def __new__(dataset, new_opts = EMPTY_HASH)
|
109
164
|
self.class.new(dataset, options.merge(new_opts))
|
110
165
|
end
|
166
|
+
|
167
|
+
# @api private
|
168
|
+
def composite_class
|
169
|
+
Relation::Composite
|
170
|
+
end
|
111
171
|
end
|
112
172
|
end
|
@@ -2,6 +2,8 @@ require 'set'
|
|
2
2
|
|
3
3
|
require 'rom/support/auto_curry'
|
4
4
|
require 'rom/relation/curried'
|
5
|
+
require 'rom/relation/name'
|
6
|
+
require 'rom/schema'
|
5
7
|
|
6
8
|
module ROM
|
7
9
|
class Relation
|
@@ -30,9 +32,11 @@ module ROM
|
|
30
32
|
klass.class_eval do
|
31
33
|
use :registry_reader
|
32
34
|
|
33
|
-
defines :gateway, :dataset, :dataset_proc, :register_as
|
35
|
+
defines :gateway, :dataset, :dataset_proc, :register_as, :schema_dsl, :schema_inferrer
|
34
36
|
|
35
37
|
gateway :default
|
38
|
+
schema_dsl Schema::DSL
|
39
|
+
schema_inferrer nil
|
36
40
|
|
37
41
|
dataset default_name
|
38
42
|
|
@@ -93,7 +97,7 @@ module ROM
|
|
93
97
|
|
94
98
|
# @api private
|
95
99
|
def initialize(dataset, options = EMPTY_HASH)
|
96
|
-
@name = self.class.dataset
|
100
|
+
@name = Name.new(self.class.register_as, self.class.dataset)
|
97
101
|
super
|
98
102
|
end
|
99
103
|
|
@@ -125,6 +129,43 @@ module ROM
|
|
125
129
|
raise AdapterNotPresentError.new(adapter, :relation)
|
126
130
|
end
|
127
131
|
|
132
|
+
# Specify canonical schema for a relation
|
133
|
+
#
|
134
|
+
# With a schema defined commands will set up a type-safe input handler
|
135
|
+
# automatically
|
136
|
+
#
|
137
|
+
# @example
|
138
|
+
# class Users < ROM::Relation[:sql]
|
139
|
+
# schema do
|
140
|
+
# attribute :id, Types::Serial
|
141
|
+
# attribute :name, Types::String
|
142
|
+
# end
|
143
|
+
# end
|
144
|
+
#
|
145
|
+
# # access schema
|
146
|
+
# Users.schema
|
147
|
+
#
|
148
|
+
# @return [Schema]
|
149
|
+
#
|
150
|
+
# @param [Symbol] dataset An optional dataset name
|
151
|
+
# @param [Boolean] infer Whether to do an automatic schema inferring
|
152
|
+
#
|
153
|
+
# @api public
|
154
|
+
def schema(dataset = nil, infer: false, &block)
|
155
|
+
if defined?(@schema)
|
156
|
+
@schema
|
157
|
+
elsif block || infer
|
158
|
+
self.dataset(dataset) if dataset
|
159
|
+
self.register_as(self.dataset) unless register_as
|
160
|
+
|
161
|
+
name = Name[register_as, self.dataset]
|
162
|
+
inferrer = infer ? schema_inferrer : nil
|
163
|
+
dsl = schema_dsl.new(name, inferrer, &block)
|
164
|
+
|
165
|
+
@schema = dsl.call
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
128
169
|
# Dynamically define a method that will forward to the dataset and wrap
|
129
170
|
# response in the relation itself
|
130
171
|
#
|
@@ -175,7 +216,7 @@ module ROM
|
|
175
216
|
ancestor_methods = ancestors.reject { |klass| klass == self }
|
176
217
|
.map(&:instance_methods).flatten
|
177
218
|
|
178
|
-
instance_methods - ancestor_methods
|
219
|
+
instance_methods - ancestor_methods + auto_curried_methods
|
179
220
|
end
|
180
221
|
|
181
222
|
# Hook to finalize a relation after its instance was created
|
data/lib/rom/relation/curried.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'rom/support/options'
|
2
2
|
|
3
3
|
require 'rom/pipeline'
|
4
|
+
require 'rom/relation/name'
|
4
5
|
require 'rom/relation/materializable'
|
5
6
|
|
6
7
|
module ROM
|
@@ -10,15 +11,18 @@ module ROM
|
|
10
11
|
include Materializable
|
11
12
|
include Pipeline
|
12
13
|
|
13
|
-
option :name, type: Symbol
|
14
|
+
option :name, type: Symbol
|
14
15
|
option :arity, type: Integer, reader: true, default: -1
|
15
16
|
option :curry_args, type: Array, reader: true, default: EMPTY_ARRAY
|
16
17
|
|
17
18
|
attr_reader :relation
|
18
19
|
|
20
|
+
attr_reader :name
|
21
|
+
|
19
22
|
# @api private
|
20
23
|
def initialize(relation, options = EMPTY_HASH)
|
21
24
|
@relation = relation
|
25
|
+
@name = relation.name.with(options[:name])
|
22
26
|
super
|
23
27
|
end
|
24
28
|
|
@@ -33,7 +37,7 @@ module ROM
|
|
33
37
|
all_args = curry_args + args
|
34
38
|
|
35
39
|
if arity == all_args.size
|
36
|
-
Loaded.new(relation.__send__(name, *all_args))
|
40
|
+
Loaded.new(relation.__send__(name.relation, *all_args))
|
37
41
|
else
|
38
42
|
__new__(relation, curry_args: all_args)
|
39
43
|
end
|
@@ -47,7 +51,7 @@ module ROM
|
|
47
51
|
def to_a
|
48
52
|
raise(
|
49
53
|
ArgumentError,
|
50
|
-
"#{relation.class}##{name} arity is #{arity} " \
|
54
|
+
"#{relation.class}##{name.relation} arity is #{arity} " \
|
51
55
|
"(#{curry_args.size} args given)"
|
52
56
|
)
|
53
57
|
end
|
@@ -64,7 +68,7 @@ module ROM
|
|
64
68
|
|
65
69
|
# @api private
|
66
70
|
def respond_to_missing?(name, include_private = false)
|
67
|
-
super || relation.respond_to?(name)
|
71
|
+
super || relation.respond_to?(name, include_private)
|
68
72
|
end
|
69
73
|
|
70
74
|
private
|
@@ -74,6 +78,11 @@ module ROM
|
|
74
78
|
Curried.new(relation, options.merge(new_opts))
|
75
79
|
end
|
76
80
|
|
81
|
+
# @api private
|
82
|
+
def composite_class
|
83
|
+
Relation::Composite
|
84
|
+
end
|
85
|
+
|
77
86
|
# @api private
|
78
87
|
def method_missing(meth, *args, &block)
|
79
88
|
if relation.respond_to?(meth)
|
data/lib/rom/relation/graph.rb
CHANGED
@@ -5,11 +5,9 @@ require 'rom/pipeline'
|
|
5
5
|
|
6
6
|
module ROM
|
7
7
|
class Relation
|
8
|
-
#
|
8
|
+
# Compose relations using join-keys
|
9
9
|
#
|
10
10
|
# @example
|
11
|
-
# ROM.setup(:memory)
|
12
|
-
#
|
13
11
|
# class Users < ROM::Relation[:memory]
|
14
12
|
# end
|
15
13
|
#
|
@@ -19,8 +17,6 @@ module ROM
|
|
19
17
|
# end
|
20
18
|
# end
|
21
19
|
#
|
22
|
-
# rom = ROM.finalize.env
|
23
|
-
#
|
24
20
|
# rom.relations[:users] << { name: 'Jane' }
|
25
21
|
# rom.relations[:tasks] << { user: 'Jane', title: 'Do something' }
|
26
22
|
#
|
@@ -65,6 +61,15 @@ module ROM
|
|
65
61
|
@nodes = nodes
|
66
62
|
end
|
67
63
|
|
64
|
+
# Return if this is a graph relation
|
65
|
+
#
|
66
|
+
# @return [true]
|
67
|
+
#
|
68
|
+
# @api private
|
69
|
+
def graph?
|
70
|
+
true
|
71
|
+
end
|
72
|
+
|
68
73
|
# Combine this graph with more nodes
|
69
74
|
#
|
70
75
|
# @param [Array<Relation::Lazy>]
|
@@ -100,6 +105,11 @@ module ROM
|
|
100
105
|
def decorate?(other)
|
101
106
|
super || other.is_a?(Curried)
|
102
107
|
end
|
108
|
+
|
109
|
+
# @api private
|
110
|
+
def composite_class
|
111
|
+
Relation::Composite
|
112
|
+
end
|
103
113
|
end
|
104
114
|
end
|
105
115
|
end
|
data/lib/rom/relation/loaded.rb
CHANGED
@@ -40,12 +40,7 @@ module ROM
|
|
40
40
|
# @api public
|
41
41
|
def each(&block)
|
42
42
|
return to_enum unless block
|
43
|
-
collection.each { |
|
44
|
-
end
|
45
|
-
|
46
|
-
# @api public
|
47
|
-
def new(collection)
|
48
|
-
self.class.new(source, collection)
|
43
|
+
collection.each { |tuple| yield(tuple) }
|
49
44
|
end
|
50
45
|
|
51
46
|
# Returns a single tuple from the relation if there is one.
|
@@ -77,6 +72,47 @@ module ROM
|
|
77
72
|
'The relation does not contain any tuples'
|
78
73
|
)
|
79
74
|
end
|
75
|
+
|
76
|
+
# Return a list of values under provided key
|
77
|
+
#
|
78
|
+
# @example
|
79
|
+
# all_users = rom.relations[:users].call
|
80
|
+
# all_users.pluck(:name)
|
81
|
+
# # ["Jane", "Joe"]
|
82
|
+
#
|
83
|
+
# @param [Symbol] key The key name
|
84
|
+
#
|
85
|
+
# @return [Array]
|
86
|
+
# @raises KeyError when provided key doesn't exist in any of the tuples
|
87
|
+
#
|
88
|
+
# @api public
|
89
|
+
def pluck(key)
|
90
|
+
map { |tuple| tuple.fetch(key) }
|
91
|
+
end
|
92
|
+
|
93
|
+
# Pluck primary key values
|
94
|
+
#
|
95
|
+
# This method *may not work* with adapters that don't provide relations
|
96
|
+
# that have primary key configured
|
97
|
+
#
|
98
|
+
# @example
|
99
|
+
# users = rom.relations[:users].call
|
100
|
+
# users.primary_keys
|
101
|
+
# # [1, 2, 3]
|
102
|
+
#
|
103
|
+
# @return [Array]
|
104
|
+
#
|
105
|
+
# @api public
|
106
|
+
def primary_keys
|
107
|
+
pluck(source.primary_key)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Return a loaded relation with a new collection
|
111
|
+
#
|
112
|
+
# @api public
|
113
|
+
def new(collection)
|
114
|
+
self.class.new(source, collection)
|
115
|
+
end
|
80
116
|
end
|
81
117
|
end
|
82
118
|
end
|