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
@@ -15,6 +15,19 @@ describe 'ROM::CommandRegistry' do
|
|
15
15
|
end)
|
16
16
|
end
|
17
17
|
|
18
|
+
describe '#[]' do
|
19
|
+
it 'fetches a command from the registry' do
|
20
|
+
expect(users[:create]).to be_a(ROM::Commands::Create[:memory])
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'throws an error when the command is not found' do
|
24
|
+
expect { users[:not_found] }.to raise_error(
|
25
|
+
ROM::CommandRegistry::CommandNotFoundError,
|
26
|
+
'There is no :not_found command for :users relation'
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
18
31
|
describe '#try' do
|
19
32
|
it 'returns a success result object on successful execution' do
|
20
33
|
result = users.try { users.create.call(name: 'Jane') }
|
@@ -64,21 +64,4 @@ describe 'Commands / Delete' do
|
|
64
64
|
|
65
65
|
expect(result.value).to eql(name: 'Jane', email: 'jane@doe.org')
|
66
66
|
end
|
67
|
-
|
68
|
-
it 'raises when result is set to :one and relation contains more tuples' do
|
69
|
-
configuration.commands(:users) do
|
70
|
-
define(:delete) do
|
71
|
-
result :one
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
result = users.try { users.delete.call }
|
76
|
-
|
77
|
-
expect(result.error).to be_instance_of(ROM::TupleCountMismatchError)
|
78
|
-
|
79
|
-
expect(container.relations.users.to_a).to match_array([
|
80
|
-
{ name: 'Jane', email: 'jane@doe.org' },
|
81
|
-
{ name: 'Joe', email: 'joe@doe.org' }
|
82
|
-
])
|
83
|
-
end
|
84
67
|
end
|
@@ -208,6 +208,6 @@ RSpec.describe 'Command graph builder' do
|
|
208
208
|
it 'raises when unknown command is accessed' do
|
209
209
|
expect {
|
210
210
|
container.command.not_here(:users)
|
211
|
-
}.to raise_error(ROM::
|
211
|
+
}.to raise_error(ROM::CommandRegistry::CommandNotFoundError, /not_here/)
|
212
212
|
end
|
213
213
|
end
|
@@ -265,7 +265,7 @@ describe 'Building up a command graph for nested input' do
|
|
265
265
|
|
266
266
|
command = container.command(options).as(:entity)
|
267
267
|
|
268
|
-
result = command.call(input)
|
268
|
+
result = command.call(input)
|
269
269
|
|
270
270
|
expect(result).to be_instance_of(Test::User)
|
271
271
|
expect(result.tasks.first).to be_instance_of(Test::Task)
|
@@ -73,25 +73,6 @@ describe 'Commands / Update' do
|
|
73
73
|
expect(result.value).to eql(name: 'Jane', email: 'jane.doe@test.com')
|
74
74
|
end
|
75
75
|
|
76
|
-
it 'raises when there is more than one tuple and result is set to :one' do
|
77
|
-
configuration.commands(:users) do
|
78
|
-
define(:update_one, type: :update) do
|
79
|
-
result :one
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
result = users.try {
|
84
|
-
users.update_one.call(email: 'jane.doe@test.com')
|
85
|
-
}
|
86
|
-
|
87
|
-
expect(result.error).to be_instance_of(ROM::TupleCountMismatchError)
|
88
|
-
|
89
|
-
expect(container.relations.users).to match_array([
|
90
|
-
{ name: 'Jane', email: 'jane@doe.org' },
|
91
|
-
{ name: 'Joe', email: 'joe@doe.org' }
|
92
|
-
])
|
93
|
-
end
|
94
|
-
|
95
76
|
it 'allows only valid result types' do
|
96
77
|
expect {
|
97
78
|
configuration.commands(:users) do
|
@@ -10,20 +10,27 @@ describe 'Commands' do
|
|
10
10
|
restrict(id: id)
|
11
11
|
end
|
12
12
|
end
|
13
|
+
|
13
14
|
configuration.commands(:users) do
|
14
15
|
define(:update)
|
16
|
+
define(:create)
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
18
|
-
let(:
|
20
|
+
let(:create) { container.command(:users)[:create] }
|
21
|
+
let(:update) { container.command(:users)[:update] }
|
19
22
|
|
20
23
|
describe '#method_missing' do
|
21
24
|
it 'forwards known relation view methods' do
|
22
|
-
expect(
|
25
|
+
expect(update.by_id(1).relation).to eql(users_relation.by_id(1))
|
23
26
|
end
|
24
27
|
|
25
28
|
it 'raises no-method error when a non-view relation method was sent' do
|
26
|
-
expect {
|
29
|
+
expect { update.as(:foo) }.to raise_error(NoMethodError, /as/)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'does not forward relation view methods to non-restrictable commands' do
|
33
|
+
expect { create.by_id(1) }.to raise_error(NoMethodError, /by_id/)
|
27
34
|
end
|
28
35
|
end
|
29
36
|
|
@@ -3,7 +3,7 @@ require 'rom/memory'
|
|
3
3
|
|
4
4
|
describe 'Using in-memory gateways for cross-repo access' do
|
5
5
|
let(:configuration) do
|
6
|
-
ROM::Configuration.new(left: :memory, right: :memory, main: :memory)
|
6
|
+
ROM::Configuration.new(left: :memory, right: :memory, main: :memory)
|
7
7
|
end
|
8
8
|
|
9
9
|
let(:container) { ROM.container(configuration) }
|
@@ -5,11 +5,34 @@ describe ROM::Relation, '.dataset' do
|
|
5
5
|
|
6
6
|
it 'injects configured dataset when block was provided' do
|
7
7
|
configuration.relation(:users) do
|
8
|
-
dataset
|
8
|
+
dataset do
|
9
|
+
insert(id: 2, name: 'Joe')
|
10
|
+
insert(id: 1, name: 'Jane')
|
11
|
+
|
12
|
+
restrict(name: 'Jane')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
expect(container.relation(:users).to_a).to eql([{ id: 1, name: 'Jane' }])
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'yields relation class for setting custom dataset proc' do
|
20
|
+
configuration.relation(:users) do
|
21
|
+
schema do
|
22
|
+
attribute :id, ROM::Memory::Types::Int.meta(primary_key: true)
|
23
|
+
attribute :name, ROM::Memory::Types::String
|
24
|
+
end
|
25
|
+
|
26
|
+
dataset do |rel_klass|
|
27
|
+
insert(id: 2, name: 'Joe')
|
28
|
+
insert(id: 1, name: 'Jane')
|
29
|
+
|
30
|
+
order(*rel_klass.schema.primary_key.map { |t| t.meta[:name] })
|
31
|
+
end
|
9
32
|
end
|
10
33
|
|
11
|
-
expect(container.relation(:users).
|
12
|
-
|
13
|
-
)
|
34
|
+
expect(container.relation(:users).to_a).to eql([
|
35
|
+
{ id: 1, name: 'Jane' }, { id: 2, name: 'Joe' }
|
36
|
+
])
|
14
37
|
end
|
15
38
|
end
|
@@ -43,7 +43,6 @@ describe 'Configuring ROM' do
|
|
43
43
|
describe 'defining classes' do
|
44
44
|
it 'sets up registries based on class definitions' do
|
45
45
|
container = ROM.container(:memory) do |config|
|
46
|
-
config.use(:macros)
|
47
46
|
|
48
47
|
class Test::UserRelation < ROM::Relation[:memory]
|
49
48
|
dataset :users
|
@@ -87,8 +86,6 @@ describe 'Configuring ROM' do
|
|
87
86
|
end
|
88
87
|
|
89
88
|
container = ROM.container(:memory) do |rom|
|
90
|
-
rom.use(:macros)
|
91
|
-
|
92
89
|
rom.relation(:users) do
|
93
90
|
def by_name(name)
|
94
91
|
restrict(name: name)
|
@@ -122,7 +119,7 @@ describe 'Configuring ROM' do
|
|
122
119
|
end
|
123
120
|
end
|
124
121
|
|
125
|
-
configuration = ROM::Configuration.new(:memory)
|
122
|
+
configuration = ROM::Configuration.new(:memory)
|
126
123
|
|
127
124
|
configuration.relation(:users) do
|
128
125
|
def by_name(name)
|
@@ -1,14 +1,24 @@
|
|
1
1
|
shared_examples_for 'a command' do
|
2
|
-
describe '#
|
3
|
-
it '
|
4
|
-
|
2
|
+
describe '#name' do
|
3
|
+
it 'returns relation name' do
|
4
|
+
expect(command.name).to eql(command.relation.name)
|
5
|
+
end
|
6
|
+
end
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
+
describe '#gateway' do
|
9
|
+
it 'returns relation gateway' do
|
10
|
+
expect(command.gateway).to eql(command.relation.gateway)
|
8
11
|
end
|
12
|
+
end
|
9
13
|
|
10
|
-
|
11
|
-
|
14
|
+
describe '#method_missing' do
|
15
|
+
it 'forwards to relation and wraps response if it returned another relation' do
|
16
|
+
if command.class.restrictable
|
17
|
+
new_command = command.by_id(1)
|
18
|
+
|
19
|
+
expect(new_command).to be_instance_of(command.class)
|
20
|
+
expect(new_command.relation).to eql(command.by_id(1).relation)
|
21
|
+
end
|
12
22
|
end
|
13
23
|
|
14
24
|
it 'raises error when message is not known' do
|
data/spec/shared/container.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
RSpec.shared_context 'container' do
|
2
2
|
let(:container) { ROM.container(configuration) }
|
3
|
-
let!(:configuration) { ROM::Configuration.new(:memory)
|
3
|
+
let!(:configuration) { ROM::Configuration.new(:memory) }
|
4
4
|
let(:gateway) { configuration.gateways[:default] }
|
5
5
|
let(:users_relation) { container.relation(:users) }
|
6
6
|
let(:tasks_relation) { container.relation(:tasks) }
|
7
7
|
let(:users_dataset) { gateway.dataset(:users) }
|
8
8
|
let(:tasks_dataset) { gateway.dataset(:tasks) }
|
9
|
-
end
|
9
|
+
end
|
data/spec/shared/gateway_only.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -9,6 +9,11 @@ if RUBY_ENGINE == "rbx"
|
|
9
9
|
CodeClimate::TestReporter.start
|
10
10
|
end
|
11
11
|
|
12
|
+
SPEC_ROOT = root = Pathname(__FILE__).dirname
|
13
|
+
|
14
|
+
require 'rom/support/deprecations'
|
15
|
+
ROM::Deprecations.set_logger!(SPEC_ROOT.join('../log/deprecations.log'))
|
16
|
+
|
12
17
|
require 'rom'
|
13
18
|
require 'anima'
|
14
19
|
|
@@ -17,8 +22,6 @@ begin
|
|
17
22
|
rescue LoadError
|
18
23
|
end
|
19
24
|
|
20
|
-
SPEC_ROOT = root = Pathname(__FILE__).dirname
|
21
|
-
|
22
25
|
Dir[root.join('support/*.rb').to_s].each do |f|
|
23
26
|
require f
|
24
27
|
end
|
@@ -38,10 +41,6 @@ def T(*args)
|
|
38
41
|
end
|
39
42
|
|
40
43
|
RSpec.configure do |config|
|
41
|
-
config.before do
|
42
|
-
ROM.env = nil
|
43
|
-
end
|
44
|
-
|
45
44
|
config.after do
|
46
45
|
Test.remove_constants
|
47
46
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
RSpec.describe ROM::AssociationSet do
|
2
|
+
describe '#try' do
|
3
|
+
it 'returns association when it exists' do
|
4
|
+
assoc = spy(:assoc)
|
5
|
+
assoc_set = ROM::AssociationSet.new(users: assoc)
|
6
|
+
|
7
|
+
assoc_set.try(:users, &:done)
|
8
|
+
|
9
|
+
expect(assoc).to have_received(:done)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'returns false when assoc is not found' do
|
13
|
+
assoc = spy(:assoc)
|
14
|
+
fallback = spy(:fallback)
|
15
|
+
assoc_set = ROM::AssociationSet.new({})
|
16
|
+
|
17
|
+
assoc_set.try(:users, &:done) or fallback.done
|
18
|
+
|
19
|
+
expect(assoc).to_not have_received(:done)
|
20
|
+
expect(fallback).to have_received(:done)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -6,7 +6,7 @@ RSpec.describe ROM::Setup, '#auto_registration' do
|
|
6
6
|
|
7
7
|
context 'with namespace turned on' do
|
8
8
|
before do
|
9
|
-
setup.auto_registration(SPEC_ROOT.join('fixtures/lib/persistence'))
|
9
|
+
setup.auto_registration(SPEC_ROOT.join('fixtures/lib/persistence').to_s)
|
10
10
|
end
|
11
11
|
|
12
12
|
describe '#relations' do
|
@@ -284,6 +284,14 @@ describe ROM::Commands::Lazy do
|
|
284
284
|
end
|
285
285
|
end
|
286
286
|
|
287
|
+
describe '#unwrap' do
|
288
|
+
subject(:command) { ROM::Commands::Lazy[create_user].new(create_user, evaluator) }
|
289
|
+
|
290
|
+
it 'returns wrapped command' do
|
291
|
+
expect(command.unwrap).to be(create_user)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
287
295
|
describe '#method_missing' do
|
288
296
|
subject(:command) { ROM::Commands::Lazy[update_user].new(update_user, evaluator) }
|
289
297
|
|
@@ -30,6 +30,24 @@ describe 'Commands' do
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
+
describe '.create_class' do
|
34
|
+
it 'builds a class' do
|
35
|
+
klass = ROM::Command.create_class(:create, ROM::Memory::Commands::Create)
|
36
|
+
|
37
|
+
expect(klass.name).to eql('ROM::Memory::Commands::Create[:create]')
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'builds a class and yields it' do
|
41
|
+
klass = ROM::Command.create_class(:create, ROM::Memory::Commands::Create) do |k|
|
42
|
+
k.result :one
|
43
|
+
k
|
44
|
+
end
|
45
|
+
|
46
|
+
expect(klass.name).to eql('ROM::Memory::Commands::Create[:create]')
|
47
|
+
expect(klass.result).to be(:one)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
33
51
|
describe '.build' do
|
34
52
|
it 'returns create command when type is set to :create' do
|
35
53
|
klass = Class.new(ROM::Commands::Create[:memory]) do
|
@@ -63,9 +81,9 @@ describe 'Commands' do
|
|
63
81
|
end
|
64
82
|
|
65
83
|
describe '#>>' do
|
66
|
-
let(:users) { double('users') }
|
67
|
-
let(:tasks) { double('tasks') }
|
68
|
-
let(:logs) {
|
84
|
+
let(:users) { double('users', schema: nil) }
|
85
|
+
let(:tasks) { double('tasks', schema: nil) }
|
86
|
+
let(:logs) { double('logs', schema: nil) }
|
69
87
|
|
70
88
|
it 'composes two commands' do
|
71
89
|
user_input = { name: 'Jane' }
|
@@ -100,11 +118,9 @@ describe 'Commands' do
|
|
100
118
|
|
101
119
|
expect(users).to receive(:insert).with(user_input).and_return(user_tuple)
|
102
120
|
expect(tasks).to receive(:insert).with(task_tuple).and_return(task_tuple)
|
121
|
+
expect(logs).to receive(:<<).with(task_tuple).and_return([task_tuple])
|
103
122
|
|
104
|
-
|
105
|
-
|
106
|
-
expect(result).to eql(task_tuple)
|
107
|
-
expect(logs).to include(task_tuple)
|
123
|
+
expect(command.call).to eql(task_tuple)
|
108
124
|
end
|
109
125
|
|
110
126
|
it 'forwards methods to the left' do
|
@@ -123,5 +139,27 @@ describe 'Commands' do
|
|
123
139
|
|
124
140
|
command.with(user_input).call
|
125
141
|
end
|
142
|
+
|
143
|
+
it 'short-circuits pipeline when left-side result is empty' do
|
144
|
+
command = Class.new(ROM::Commands::Update) do
|
145
|
+
result :one
|
146
|
+
|
147
|
+
def execute(*)
|
148
|
+
[]
|
149
|
+
end
|
150
|
+
end.build(users) >> -> result { result.map(&:to_a) }
|
151
|
+
|
152
|
+
expect(command.call('foo')).to be(nil)
|
153
|
+
|
154
|
+
command = Class.new(ROM::Commands::Update) do
|
155
|
+
result :many
|
156
|
+
|
157
|
+
def execute(*)
|
158
|
+
[]
|
159
|
+
end
|
160
|
+
end.build(users) >> -> result { result.map(&:to_a) }
|
161
|
+
|
162
|
+
expect(command.call('foo')).to be(ROM::EMPTY_ARRAY)
|
163
|
+
end
|
126
164
|
end
|
127
165
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rom/memory'
|
3
|
+
|
4
|
+
describe ROM::Memory::Commands do
|
5
|
+
let(:relation) do
|
6
|
+
Class.new(ROM::Relation[:memory]) do
|
7
|
+
schema do
|
8
|
+
attribute :id, ROM::Memory::Types::Int
|
9
|
+
attribute :name, ROM::Memory::Types::String
|
10
|
+
end
|
11
|
+
end.new(ROM::Memory::Dataset.new([]))
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'Create' do
|
15
|
+
subject(:command) { ROM::Commands::Create[:memory].build(relation) }
|
16
|
+
|
17
|
+
describe '#call' do
|
18
|
+
it 'uses default input handler' do
|
19
|
+
result = command.call([id: 1, name: 'Jane', haha: 'oops'])
|
20
|
+
|
21
|
+
expect(result).to eql([{ id: 1, name: 'Jane' }])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'Update' do
|
27
|
+
subject(:command) { ROM::Commands::Update[:memory].build(relation) }
|
28
|
+
|
29
|
+
before do
|
30
|
+
relation.insert(id: 1, name: 'Jane')
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#call' do
|
34
|
+
it 'uses default input handler' do
|
35
|
+
result = command
|
36
|
+
.new(relation.restrict(id: 1))
|
37
|
+
.call(name: 'Jane Doe', haha: 'oops')
|
38
|
+
|
39
|
+
expect(result).to eql([{ id: 1, name: 'Jane Doe' }])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|