rom 1.0.0 → 2.0.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 -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
|