rom 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/Gemfile +2 -0
- data/README.md +20 -5
- data/lib/rom.rb +12 -2
- data/lib/rom/adapter.rb +110 -4
- data/lib/rom/adapter/memory.rb +9 -52
- data/lib/rom/adapter/memory/commands.rb +41 -0
- data/lib/rom/adapter/memory/dataset.rb +52 -0
- data/lib/rom/adapter/memory/storage.rb +25 -0
- data/lib/rom/boot.rb +28 -1
- data/lib/rom/boot/command_dsl.rb +48 -0
- data/lib/rom/boot/dsl.rb +7 -0
- data/lib/rom/command_registry.rb +68 -0
- data/lib/rom/commands.rb +64 -0
- data/lib/rom/commands/create.rb +28 -0
- data/lib/rom/commands/delete.rb +44 -0
- data/lib/rom/commands/update.rb +42 -0
- data/lib/rom/commands/with_options.rb +21 -0
- data/lib/rom/env.rb +22 -6
- data/lib/rom/relation.rb +0 -18
- data/lib/rom/repository.rb +25 -3
- data/lib/rom/version.rb +1 -1
- data/spec/integration/adapters/setting_logger_spec.rb +35 -0
- data/spec/integration/commands/create_spec.rb +96 -0
- data/spec/integration/commands/delete_spec.rb +70 -0
- data/spec/integration/commands/error_handling_spec.rb +23 -0
- data/spec/integration/commands/try_spec.rb +27 -0
- data/spec/integration/commands/update_spec.rb +106 -0
- data/spec/integration/setup_spec.rb +35 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/support/mutant.rb +7 -0
- data/spec/unit/rom/env_spec.rb +19 -0
- data/spec/unit/rom/repository_spec.rb +21 -0
- metadata +22 -2
data/lib/rom/relation.rb
CHANGED
@@ -43,24 +43,6 @@ module ROM
|
|
43
43
|
dataset.each(&block)
|
44
44
|
end
|
45
45
|
|
46
|
-
# @api private
|
47
|
-
def insert(tuple)
|
48
|
-
dataset.insert(tuple)
|
49
|
-
self
|
50
|
-
end
|
51
|
-
|
52
|
-
# @api private
|
53
|
-
def update(tuple)
|
54
|
-
dataset.update(tuple)
|
55
|
-
self
|
56
|
-
end
|
57
|
-
|
58
|
-
# @api private
|
59
|
-
def delete
|
60
|
-
dataset.delete
|
61
|
-
self
|
62
|
-
end
|
63
|
-
|
64
46
|
end
|
65
47
|
|
66
48
|
end
|
data/lib/rom/repository.rb
CHANGED
@@ -16,6 +16,24 @@ module ROM
|
|
16
16
|
adapter[name]
|
17
17
|
end
|
18
18
|
|
19
|
+
# Set a logger for the adapter
|
20
|
+
#
|
21
|
+
# @param [Object] logger
|
22
|
+
#
|
23
|
+
# @api public
|
24
|
+
def use_logger(logger)
|
25
|
+
adapter.logger = logger
|
26
|
+
end
|
27
|
+
|
28
|
+
# Return logger used by the adapter
|
29
|
+
#
|
30
|
+
# @return [Object] logger
|
31
|
+
#
|
32
|
+
# @api public
|
33
|
+
def logger
|
34
|
+
adapter.logger
|
35
|
+
end
|
36
|
+
|
19
37
|
# Return the database connection provided by the adapter
|
20
38
|
#
|
21
39
|
# @api public
|
@@ -32,14 +50,18 @@ module ROM
|
|
32
50
|
|
33
51
|
# @api private
|
34
52
|
def respond_to_missing?(name, include_private = false)
|
35
|
-
adapter
|
53
|
+
adapter.dataset?(name) || super
|
36
54
|
end
|
37
55
|
|
38
56
|
private
|
39
57
|
|
40
58
|
# @api private
|
41
|
-
def method_missing(name)
|
42
|
-
adapter
|
59
|
+
def method_missing(name, *args, &block)
|
60
|
+
if adapter.dataset?(name)
|
61
|
+
adapter[name]
|
62
|
+
else
|
63
|
+
super
|
64
|
+
end
|
43
65
|
end
|
44
66
|
end
|
45
67
|
|
data/lib/rom/version.rb
CHANGED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
describe 'Adapters / Setting logger' do
|
6
|
+
let(:logger_class) do
|
7
|
+
Class.new do
|
8
|
+
attr_reader :messages
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@messages = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def info(msg)
|
15
|
+
@messages << msg
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:logger) do
|
21
|
+
logger_class.new
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'sets up a logger for a given adapter' do
|
25
|
+
setup = ROM.setup(memory: 'memory://localhost')
|
26
|
+
|
27
|
+
setup.memory.use_logger(logger)
|
28
|
+
|
29
|
+
rom = setup.finalize
|
30
|
+
|
31
|
+
rom.memory.logger.info("test")
|
32
|
+
|
33
|
+
expect(logger.messages).to eql(["test"])
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Commands / Create' do
|
4
|
+
include_context 'users and tasks'
|
5
|
+
|
6
|
+
let(:users) { rom.commands.users }
|
7
|
+
let(:tasks) { rom.commands.tasks }
|
8
|
+
|
9
|
+
before do
|
10
|
+
UserValidator = Class.new do
|
11
|
+
ValidationError = Class.new(ROM::CommandError)
|
12
|
+
|
13
|
+
def self.call(params)
|
14
|
+
unless params[:name] && params[:email]
|
15
|
+
raise ValidationError, ":name and :email are required"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
setup.relation(:users)
|
21
|
+
setup.relation(:tasks)
|
22
|
+
|
23
|
+
setup.commands(:users) do
|
24
|
+
define(:create) do
|
25
|
+
validator UserValidator
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
setup.commands(:tasks) do
|
30
|
+
define(:create)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'inserts user on successful validation' do
|
36
|
+
result = users.try { create(name: 'Piotr', email: 'piotr@solnic.eu') }
|
37
|
+
|
38
|
+
expect(result).to match_array([{ name: 'Piotr', email: 'piotr@solnic.eu' }])
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'inserts user and associated task when things go well' do
|
42
|
+
result = users.try {
|
43
|
+
create(name: 'Piotr', email: 'piotr@solnic.eu')
|
44
|
+
} >-> users {
|
45
|
+
tasks.try {
|
46
|
+
create(name: users.first[:name], title: 'Finish command-api')
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
expect(result).to match_array([{ name: 'Piotr', title: 'Finish command-api' }])
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'returns validation object with errors on failed validation' do
|
54
|
+
result = users.try { create(name: 'Piotr') }
|
55
|
+
|
56
|
+
expect(result.error).to be_instance_of(ValidationError)
|
57
|
+
expect(result.error.message).to eql(":name and :email are required")
|
58
|
+
expect(rom.relations.users.count).to be(2)
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '"result" option' do
|
62
|
+
|
63
|
+
it 'returns a single tuple when set to :one' do
|
64
|
+
setup.commands(:users) do
|
65
|
+
|
66
|
+
define(:create_one, type: :create) do
|
67
|
+
result :one
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
tuple = { name: 'Piotr', email: 'piotr@solnic.eu' }
|
73
|
+
|
74
|
+
result = users.try {
|
75
|
+
create_one(tuple)
|
76
|
+
}
|
77
|
+
|
78
|
+
expect(result.value).to eql(tuple)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'allows only valid result types' do
|
82
|
+
expect {
|
83
|
+
|
84
|
+
setup.commands(:users) do
|
85
|
+
define(:create_one, type: :create) do
|
86
|
+
result :invalid_type
|
87
|
+
end
|
88
|
+
end
|
89
|
+
setup.finalize
|
90
|
+
|
91
|
+
}.to raise_error(ROM::InvalidOptionError)
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Commands / Delete' do
|
4
|
+
include_context 'users and tasks'
|
5
|
+
|
6
|
+
subject(:users) { rom.commands.users }
|
7
|
+
|
8
|
+
before do
|
9
|
+
setup.relation(:users) do
|
10
|
+
def by_name(name)
|
11
|
+
restrict(name: name)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
setup.commands(:users) do
|
16
|
+
define(:delete)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'deletes all tuples when there is no restriction' do
|
21
|
+
result = users.try { delete }
|
22
|
+
|
23
|
+
expect(result).to match_array([
|
24
|
+
{ name: 'Jane', email: 'jane@doe.org' },
|
25
|
+
{ name: 'Joe', email: 'joe@doe.org' }
|
26
|
+
])
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'deletes tuples matching restriction' do
|
30
|
+
result = users.try { delete(:by_name, 'Joe').call }
|
31
|
+
|
32
|
+
expect(result).to match_array([{ name: 'Joe', email: 'joe@doe.org' }])
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'returns untouched relation if there are no tuples to delete' do
|
36
|
+
result = users.try { delete(:by_name, 'Not here').call }
|
37
|
+
|
38
|
+
expect(result).to match_array([])
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'returns deleted tuple when result is set to :one' do
|
42
|
+
setup.commands(:users) do
|
43
|
+
define(:delete_one, type: :delete) do
|
44
|
+
result :one
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
result = users.try { delete_one(:by_name, 'Jane').call }
|
49
|
+
|
50
|
+
expect(result.value).to eql(name: 'Jane', email: 'jane@doe.org')
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'raises error when result is set to :one and relation contains more tuples' do
|
54
|
+
setup.commands(:users) do
|
55
|
+
define(:delete) do
|
56
|
+
result :one
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
result = users.try { delete }
|
61
|
+
|
62
|
+
expect(result.error).to be_instance_of(ROM::TupleCountMismatchError)
|
63
|
+
|
64
|
+
expect(rom.relations.users.to_a).to match_array([
|
65
|
+
{ name: 'Jane', email: 'jane@doe.org' },
|
66
|
+
{ name: 'Joe', email: 'joe@doe.org' }
|
67
|
+
])
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Commands / Error handling' do
|
4
|
+
include_context 'users and tasks'
|
5
|
+
|
6
|
+
before do
|
7
|
+
setup.relation(:users)
|
8
|
+
setup.commands(:users) { define(:create) }
|
9
|
+
end
|
10
|
+
|
11
|
+
subject(:users) { rom.commands.users }
|
12
|
+
|
13
|
+
it 'rescues from ROM::CommandError' do
|
14
|
+
result = false
|
15
|
+
expect(users.try { raise ROM::CommandError } >-> test { result = true }).
|
16
|
+
to be_instance_of(ROM::Result::Failure)
|
17
|
+
expect(result).to be(false)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'raises other errors' do
|
21
|
+
expect { users.try { raise ArgumentError, 'test' } }.to raise_error(ArgumentError, 'test')
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Commands / Try api' do
|
4
|
+
include_context 'users and tasks'
|
5
|
+
|
6
|
+
before do
|
7
|
+
setup.relation(:users)
|
8
|
+
|
9
|
+
setup.commands(:users) do
|
10
|
+
define(:create)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:user_commands) { rom.command(:users) }
|
15
|
+
|
16
|
+
it 'exposes command functions inside the block' do
|
17
|
+
input = { name: 'Piotr', email: 'piotr@test.com' }
|
18
|
+
|
19
|
+
result = user_commands.try { create(input) }
|
20
|
+
|
21
|
+
expect(result.value).to eql([input])
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'raises on method missing' do
|
25
|
+
expect { users.try { not_here } }.to raise_error(NameError)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
describe 'Commands / Update' do
|
6
|
+
include_context 'users and tasks'
|
7
|
+
|
8
|
+
subject(:users) { rom.commands.users }
|
9
|
+
|
10
|
+
before do
|
11
|
+
UserValidator = Class.new do
|
12
|
+
ValidationError = Class.new(ROM::CommandError)
|
13
|
+
|
14
|
+
def self.call(params)
|
15
|
+
raise ValidationError, ":email is required" unless params[:email]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
setup.relation(:users) do
|
20
|
+
def all(criteria)
|
21
|
+
restrict(criteria)
|
22
|
+
end
|
23
|
+
|
24
|
+
def by_name(name)
|
25
|
+
restrict(name: name)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
setup.commands(:users) do
|
30
|
+
define(:update) do
|
31
|
+
validator UserValidator
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'update tuples on successful validation' do
|
38
|
+
result = users.try {
|
39
|
+
update(:all, name: 'Jane').set(email: 'jane.doe@test.com')
|
40
|
+
}
|
41
|
+
|
42
|
+
expect(result).to match_array([{ name: 'Jane', email: 'jane.doe@test.com' }])
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'returns validation object with errors on failed validation' do
|
46
|
+
result = users.try { update(:all, name: 'Jane').set(email: nil) }
|
47
|
+
|
48
|
+
expect(result.error).to be_instance_of(ValidationError)
|
49
|
+
expect(result.error.message).to eql(':email is required')
|
50
|
+
|
51
|
+
expect(rom.relations.users.restrict(name: 'Jane')).to match_array([
|
52
|
+
{ name: 'Jane', email: 'jane@doe.org' }
|
53
|
+
])
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '"result" option' do
|
57
|
+
|
58
|
+
it 'returns a single tuple when set to :one' do
|
59
|
+
setup.commands(:users) do
|
60
|
+
define(:update_one, type: :update) do
|
61
|
+
result :one
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
result = users.try {
|
66
|
+
update_one(:by_name, 'Jane').set(email: 'jane.doe@test.com')
|
67
|
+
}
|
68
|
+
|
69
|
+
expect(result.value).to eql(name: 'Jane', email: 'jane.doe@test.com')
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'raises error when there is more than one tuple and result is set to :one' do
|
73
|
+
setup.commands(:users) do
|
74
|
+
define(:update_one, type: :update) do
|
75
|
+
result :one
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
result = users.try {
|
80
|
+
update_one.set(email: 'jane.doe@test.com')
|
81
|
+
}
|
82
|
+
|
83
|
+
expect(result.error).to be_instance_of(ROM::TupleCountMismatchError)
|
84
|
+
|
85
|
+
expect(rom.relations.users).to match_array([
|
86
|
+
{ name: 'Jane', email: 'jane@doe.org' },
|
87
|
+
{ name: 'Joe', email: 'joe@doe.org' }
|
88
|
+
])
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'allows only valid result types' do
|
92
|
+
expect {
|
93
|
+
|
94
|
+
setup.commands(:users) do
|
95
|
+
define(:create_one, type: :create) do
|
96
|
+
result :invalid_type
|
97
|
+
end
|
98
|
+
end
|
99
|
+
setup.finalize
|
100
|
+
|
101
|
+
}.to raise_error(ROM::InvalidOptionError)
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
@@ -30,4 +30,39 @@ describe 'Setting up ROM' do
|
|
30
30
|
expect(rom.mappers).to eql(ROM::ReaderRegistry.new)
|
31
31
|
end
|
32
32
|
end
|
33
|
+
|
34
|
+
describe 'quick setup' do
|
35
|
+
it 'exposes boot DSL inside the setup block' do
|
36
|
+
User = Class.new { include Virtus.value_object; values { attribute :name, String } }
|
37
|
+
|
38
|
+
rom = ROM.setup(memory: 'memory://test') do
|
39
|
+
schema do
|
40
|
+
base_relation(:users) do
|
41
|
+
repository :memory
|
42
|
+
attribute :name
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
relation(:users) do
|
47
|
+
def by_name(name)
|
48
|
+
restrict(name: name)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
commands(:users) do
|
53
|
+
define(:create)
|
54
|
+
end
|
55
|
+
|
56
|
+
mappers do
|
57
|
+
define(:users) do
|
58
|
+
model User
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
rom.command(:users).create.call(name: 'Jane')
|
64
|
+
|
65
|
+
expect(rom.read(:users).by_name('Jane').to_a).to eql([User.new(name: 'Jane')])
|
66
|
+
end
|
67
|
+
end
|
33
68
|
end
|