rom 0.3.1 → 0.4.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/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
|