rom-repository 0.3.1 → 1.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +11 -13
- data/CHANGELOG.md +25 -0
- data/Gemfile +13 -3
- data/lib/rom/repository.rb +57 -19
- data/lib/rom/repository/changeset.rb +89 -26
- data/lib/rom/repository/changeset/create.rb +34 -0
- data/lib/rom/repository/changeset/delete.rb +15 -0
- data/lib/rom/repository/changeset/pipe.rb +11 -4
- data/lib/rom/repository/changeset/update.rb +11 -1
- data/lib/rom/repository/command_compiler.rb +51 -30
- data/lib/rom/repository/command_proxy.rb +3 -1
- data/lib/rom/repository/header_builder.rb +3 -3
- data/lib/rom/repository/mapper_builder.rb +2 -2
- data/lib/rom/repository/relation_proxy.rb +26 -35
- data/lib/rom/repository/relation_proxy/combine.rb +59 -27
- data/lib/rom/repository/root.rb +4 -6
- data/lib/rom/repository/session.rb +55 -0
- data/lib/rom/repository/struct_builder.rb +29 -17
- data/lib/rom/repository/version.rb +1 -1
- data/lib/rom/struct.rb +11 -20
- data/rom-repository.gemspec +4 -3
- data/spec/integration/command_macros_spec.rb +5 -2
- data/spec/integration/command_spec.rb +0 -6
- data/spec/integration/multi_adapter_spec.rb +8 -5
- data/spec/integration/repository_spec.rb +58 -2
- data/spec/integration/root_repository_spec.rb +9 -2
- data/spec/integration/typed_structs_spec.rb +31 -0
- data/spec/shared/database.rb +5 -1
- data/spec/shared/relations.rb +3 -1
- data/spec/shared/repo.rb +13 -1
- data/spec/shared/structs.rb +39 -0
- data/spec/spec_helper.rb +7 -5
- data/spec/support/mutant.rb +10 -0
- data/spec/unit/changeset/map_spec.rb +42 -0
- data/spec/unit/changeset_spec.rb +32 -6
- data/spec/unit/relation_proxy_spec.rb +27 -9
- data/spec/unit/repository/changeset_spec.rb +125 -0
- data/spec/unit/repository/inspect_spec.rb +18 -0
- data/spec/unit/repository/session_spec.rb +251 -0
- data/spec/unit/session_spec.rb +54 -0
- data/spec/unit/struct_builder_spec.rb +45 -1
- metadata +41 -17
- data/lib/rom/repository/struct_attributes.rb +0 -46
- data/spec/unit/header_builder_spec.rb +0 -73
- data/spec/unit/plugins/view_spec.rb +0 -29
- data/spec/unit/sql/relation_spec.rb +0 -54
- data/spec/unit/struct_spec.rb +0 -22
data/spec/shared/relations.rb
CHANGED
data/spec/shared/repo.rb
CHANGED
@@ -6,7 +6,7 @@ RSpec.shared_context('repo') do
|
|
6
6
|
|
7
7
|
let(:repo_class) do
|
8
8
|
Class.new(ROM::Repository[:users]) do
|
9
|
-
relations :tasks, :tags, :posts, :labels
|
9
|
+
relations :tasks, :tags, :posts, :labels, :posts_labels
|
10
10
|
|
11
11
|
def find_users(criteria)
|
12
12
|
users.find(criteria)
|
@@ -36,6 +36,18 @@ RSpec.shared_context('repo') do
|
|
36
36
|
aggregate(one: tasks.find(title: title))
|
37
37
|
end
|
38
38
|
|
39
|
+
def users_with_posts_and_their_labels
|
40
|
+
users.combine(posts: [:labels])
|
41
|
+
end
|
42
|
+
|
43
|
+
def posts_with_labels
|
44
|
+
posts.combine_children(many: labels)
|
45
|
+
end
|
46
|
+
|
47
|
+
def label_with_posts
|
48
|
+
labels.combine_children(one: posts)
|
49
|
+
end
|
50
|
+
|
39
51
|
def tasks_for_users(users)
|
40
52
|
tasks.for_users(users)
|
41
53
|
end
|
data/spec/shared/structs.rb
CHANGED
@@ -13,6 +13,22 @@ RSpec.shared_context 'structs' do
|
|
13
13
|
repo.tags.mapper.model
|
14
14
|
end
|
15
15
|
|
16
|
+
let(:post_struct) do
|
17
|
+
repo.posts.mapper.model
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:label_struct) do
|
21
|
+
repo.labels.mapper.model
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:post_with_labels_struct) do
|
25
|
+
mapper_for(repo.posts_with_labels).model
|
26
|
+
end
|
27
|
+
|
28
|
+
let(:label_with_posts_struct) do
|
29
|
+
mapper_for(repo.label_with_posts).model
|
30
|
+
end
|
31
|
+
|
16
32
|
let(:tag_with_task_struct) do
|
17
33
|
mapper_for(repo.tag_with_wrapped_task).model
|
18
34
|
end
|
@@ -25,6 +41,10 @@ RSpec.shared_context 'structs' do
|
|
25
41
|
mapper_for(repo.users_with_task).model
|
26
42
|
end
|
27
43
|
|
44
|
+
let(:user_with_posts_struct) do
|
45
|
+
mapper_for(repo.users_with_posts_and_their_labels).model
|
46
|
+
end
|
47
|
+
|
28
48
|
let(:task_with_tags_struct) do
|
29
49
|
mapper_for(repo.tasks_with_tags).model
|
30
50
|
end
|
@@ -100,4 +120,23 @@ RSpec.shared_context 'structs' do
|
|
100
120
|
let(:joe_task) do
|
101
121
|
task_struct.new(id: 1, user_id: 2, title: 'Joe Task')
|
102
122
|
end
|
123
|
+
|
124
|
+
let(:jane_with_posts) do
|
125
|
+
user_with_posts_struct.new(id: 1, name: 'Jane', posts: [post_with_label])
|
126
|
+
end
|
127
|
+
|
128
|
+
let(:label_red) do
|
129
|
+
label_with_posts_struct.new(id: 1, name: 'red', post: 1)
|
130
|
+
end
|
131
|
+
|
132
|
+
let(:label_blue) do
|
133
|
+
label_with_posts_struct.new(id: 3, name: 'blue', post: 1)
|
134
|
+
end
|
135
|
+
|
136
|
+
let(:post_with_label) do
|
137
|
+
post_with_labels_struct.new(id: 2, title: 'Hello From Jane',
|
138
|
+
body: 'Jane Post',
|
139
|
+
author_id: 1,
|
140
|
+
labels: [label_red, label_blue])
|
141
|
+
end
|
103
142
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -4,9 +4,11 @@
|
|
4
4
|
require "bundler"
|
5
5
|
Bundler.setup
|
6
6
|
|
7
|
-
if RUBY_ENGINE ==
|
8
|
-
require
|
9
|
-
|
7
|
+
if ENV['COVERAGE'] == 'true' && RUBY_ENGINE == 'ruby' && RUBY_VERSION >= '2.4.0' && ENV['CI'] == 'true'
|
8
|
+
require 'simplecov'
|
9
|
+
SimpleCov.start do
|
10
|
+
add_filter '/spec/'
|
11
|
+
end
|
10
12
|
end
|
11
13
|
|
12
14
|
require 'rom-sql'
|
@@ -28,8 +30,8 @@ Dir[root.join('shared/*.rb').to_s].each do |f|
|
|
28
30
|
require f
|
29
31
|
end
|
30
32
|
|
31
|
-
require '
|
32
|
-
|
33
|
+
require 'dry/core/deprecations'
|
34
|
+
Dry::Core::Deprecations.set_logger!(root.join('../log/deprecations.log'))
|
33
35
|
|
34
36
|
# Namespace holding all objects created during specs
|
35
37
|
module Test
|
@@ -0,0 +1,42 @@
|
|
1
|
+
RSpec.describe ROM::Changeset, '.map' do
|
2
|
+
subject(:changeset) do
|
3
|
+
Class.new(ROM::Changeset) do
|
4
|
+
map do
|
5
|
+
unwrap :address
|
6
|
+
rename_keys street: :address_street, city: :address_city, country: :address_country
|
7
|
+
end
|
8
|
+
|
9
|
+
def default_command_type
|
10
|
+
:test
|
11
|
+
end
|
12
|
+
end.new(relation, __data__: user_data)
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:relation) { double(:relation) }
|
16
|
+
|
17
|
+
context 'with a hash' do
|
18
|
+
let(:user_data) do
|
19
|
+
{ name: 'Jane', address: { street: 'Street 1', city: 'NYC', country: 'US' } }
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'sets up custom data pipe' do
|
23
|
+
expect(changeset.to_h)
|
24
|
+
.to eql(name: 'Jane', address_street: 'Street 1', address_city: 'NYC', address_country: 'US' )
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'with an array' do
|
29
|
+
let(:user_data) do
|
30
|
+
[{ name: 'Jane', address: { street: 'Street 1', city: 'NYC', country: 'US' } },
|
31
|
+
{ name: 'Joe', address: { street: 'Street 2', city: 'KRK', country: 'PL' } }]
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'sets up custom data pipe' do
|
35
|
+
expect(changeset.to_a)
|
36
|
+
.to eql([
|
37
|
+
{ name: 'Jane', address_street: 'Street 1', address_city: 'NYC', address_country: 'US' },
|
38
|
+
{ name: 'Joe', address_street: 'Street 2', address_city: 'KRK', address_country: 'PL' }
|
39
|
+
])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/spec/unit/changeset_spec.rb
CHANGED
@@ -6,7 +6,7 @@ RSpec.describe ROM::Changeset do
|
|
6
6
|
it 'returns a hash with changes' do
|
7
7
|
expect(relation).to receive(:fetch).with(2).and_return(jane)
|
8
8
|
|
9
|
-
changeset = ROM::Changeset::Update.new(relation, { name: "Jane Doe" }, primary_key: 2)
|
9
|
+
changeset = ROM::Changeset::Update.new(relation, __data__: { name: "Jane Doe" }, primary_key: 2)
|
10
10
|
|
11
11
|
expect(changeset.diff).to eql(name: "Jane Doe")
|
12
12
|
end
|
@@ -16,7 +16,7 @@ RSpec.describe ROM::Changeset do
|
|
16
16
|
it 'returns true when data differs from the original tuple' do
|
17
17
|
expect(relation).to receive(:fetch).with(2).and_return(jane)
|
18
18
|
|
19
|
-
changeset = ROM::Changeset::Update.new(relation, { name: "Jane Doe" }, primary_key: 2)
|
19
|
+
changeset = ROM::Changeset::Update.new(relation, __data__: { name: "Jane Doe" }, primary_key: 2)
|
20
20
|
|
21
21
|
expect(changeset).to be_diff
|
22
22
|
end
|
@@ -24,16 +24,16 @@ RSpec.describe ROM::Changeset do
|
|
24
24
|
it 'returns false when data are equal to the original tuple' do
|
25
25
|
expect(relation).to receive(:fetch).with(2).and_return(jane)
|
26
26
|
|
27
|
-
changeset = ROM::Changeset::Update.new(relation, { name: "Jane" }, primary_key: 2)
|
27
|
+
changeset = ROM::Changeset::Update.new(relation, __data__: { name: "Jane" }, primary_key: 2)
|
28
28
|
|
29
29
|
expect(changeset).to_not be_diff
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
33
|
describe 'quacks like a hash' do
|
34
|
-
subject(:changeset) { ROM::Changeset::Create.new(relation, data) }
|
34
|
+
subject(:changeset) { ROM::Changeset::Create.new(relation, __data__: data) }
|
35
35
|
|
36
|
-
let(:data) { instance_double(Hash) }
|
36
|
+
let(:data) { instance_double(Hash, class: Hash) }
|
37
37
|
|
38
38
|
it 'delegates to its data hash' do
|
39
39
|
expect(data).to receive(:[]).with(:name).and_return('Jane')
|
@@ -47,7 +47,7 @@ RSpec.describe ROM::Changeset do
|
|
47
47
|
new_changeset = changeset.merge(foo: 'bar')
|
48
48
|
|
49
49
|
expect(new_changeset).to be_instance_of(ROM::Changeset::Create)
|
50
|
-
expect(new_changeset.options).to eql(changeset.options)
|
50
|
+
expect(new_changeset.options).to eql(changeset.options.merge(__data__: { foo: 'bar' }))
|
51
51
|
expect(new_changeset.to_h).to eql(foo: 'bar')
|
52
52
|
end
|
53
53
|
|
@@ -55,4 +55,30 @@ RSpec.describe ROM::Changeset do
|
|
55
55
|
expect { changeset.not_here }.to raise_error(NoMethodError, /not_here/)
|
56
56
|
end
|
57
57
|
end
|
58
|
+
|
59
|
+
describe 'quacks like an array' do
|
60
|
+
subject(:changeset) { ROM::Changeset::Create.new(relation, __data__: data) }
|
61
|
+
|
62
|
+
let(:data) { instance_double(Array, class: Array) }
|
63
|
+
|
64
|
+
it 'delegates to its data hash' do
|
65
|
+
expect(data).to receive(:[]).with(1).and_return('Jane')
|
66
|
+
|
67
|
+
expect(changeset[1]).to eql('Jane')
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'maintains its own type' do
|
71
|
+
expect(data).to receive(:+).with([1, 2]).and_return([1, 2])
|
72
|
+
|
73
|
+
new_changeset = changeset + [1, 2]
|
74
|
+
|
75
|
+
expect(new_changeset).to be_instance_of(ROM::Changeset::Create)
|
76
|
+
expect(new_changeset.options).to eql(changeset.options.merge(__data__: [1, 2]))
|
77
|
+
expect(new_changeset.to_a).to eql([1, 2])
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'raises NoMethodError when an unknown message was sent' do
|
81
|
+
expect { changeset.not_here }.to raise_error(NoMethodError, /not_here/)
|
82
|
+
end
|
83
|
+
end
|
58
84
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'dry-struct'
|
2
|
+
|
1
3
|
RSpec.describe 'loading proxy' do
|
2
4
|
include_context 'database'
|
3
5
|
include_context 'relations'
|
@@ -17,6 +19,13 @@ RSpec.describe 'loading proxy' do
|
|
17
19
|
ROM::Repository::RelationProxy.new(rom.relation(:tags), name: :tags)
|
18
20
|
end
|
19
21
|
|
22
|
+
describe '#inspect' do
|
23
|
+
specify do
|
24
|
+
expect(users.inspect).
|
25
|
+
to eql("#<ROM::Relation[Users] name=users dataset=#{users.dataset.inspect}>")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
20
29
|
describe '#each' do
|
21
30
|
it 'yields loaded structs' do
|
22
31
|
result = []
|
@@ -56,7 +65,7 @@ RSpec.describe 'loading proxy' do
|
|
56
65
|
|
57
66
|
context 'setting custom model type' do
|
58
67
|
let(:user_type) do
|
59
|
-
Class.new(Dry::
|
68
|
+
Class.new(Dry::Struct) do
|
60
69
|
attribute :id, Dry::Types['strict.int']
|
61
70
|
attribute :name, Dry::Types['strict.string']
|
62
71
|
end
|
@@ -103,7 +112,7 @@ RSpec.describe 'loading proxy' do
|
|
103
112
|
[:relation, [
|
104
113
|
:users,
|
105
114
|
{ dataset: :users },
|
106
|
-
[:header, [[:attribute, :id], [:attribute, :name]]]]
|
115
|
+
[:header, [[:attribute, users.schema[:id]], [:attribute, users.schema[:name]]]]]
|
107
116
|
]
|
108
117
|
)
|
109
118
|
end
|
@@ -116,12 +125,15 @@ RSpec.describe 'loading proxy' do
|
|
116
125
|
:users,
|
117
126
|
{ dataset: :users },
|
118
127
|
[:header, [
|
119
|
-
[:attribute, :id],
|
120
|
-
[:attribute, :name],
|
128
|
+
[:attribute, users.schema[:id]],
|
129
|
+
[:attribute, users.schema[:name]],
|
121
130
|
[:relation, [
|
122
131
|
:tasks,
|
123
132
|
{ dataset: :tasks, keys: { id: :user_id }, combine_type: :many, combine_name: :user_tasks },
|
124
|
-
[:header, [
|
133
|
+
[:header, [
|
134
|
+
[:attribute, tasks.schema[:id]],
|
135
|
+
[:attribute, tasks.schema[:user_id]],
|
136
|
+
[:attribute, tasks.schema[:title]]]]
|
125
137
|
]]
|
126
138
|
]
|
127
139
|
]]]
|
@@ -131,18 +143,24 @@ RSpec.describe 'loading proxy' do
|
|
131
143
|
it 'returns valid ast for a wrapped relation' do
|
132
144
|
relation = tags.wrap_parent(task: tasks)
|
133
145
|
|
146
|
+
tags_schema = tags.schema.qualified
|
147
|
+
tasks_schema = tasks.schema.wrap.qualified
|
148
|
+
|
134
149
|
expect(relation.to_ast).to eql(
|
135
150
|
[:relation, [
|
136
151
|
:tags,
|
137
152
|
{ dataset: :tags },
|
138
153
|
[:header, [
|
139
|
-
[:attribute, :id],
|
140
|
-
[:attribute, :task_id],
|
141
|
-
[:attribute, :name],
|
154
|
+
[:attribute, tags_schema[:id]],
|
155
|
+
[:attribute, tags_schema[:task_id]],
|
156
|
+
[:attribute, tags_schema[:name]],
|
142
157
|
[:relation, [
|
143
158
|
:tasks,
|
144
159
|
{ dataset: :tasks, keys: { id: :task_id }, wrap: true, combine_name: :task },
|
145
|
-
[:header, [
|
160
|
+
[:header, [
|
161
|
+
[:attribute, tasks_schema[:id]],
|
162
|
+
[:attribute, tasks_schema[:user_id]],
|
163
|
+
[:attribute, tasks_schema[:title]]]]
|
146
164
|
]]
|
147
165
|
]]
|
148
166
|
]]
|
@@ -0,0 +1,125 @@
|
|
1
|
+
RSpec.describe ROM::Repository, '#changeset' do
|
2
|
+
subject(:repo) do
|
3
|
+
Class.new(ROM::Repository) { relations :users }.new(rom)
|
4
|
+
end
|
5
|
+
|
6
|
+
include_context 'database'
|
7
|
+
include_context 'relations'
|
8
|
+
|
9
|
+
describe ROM::Changeset::Create do
|
10
|
+
context 'with a hash' do
|
11
|
+
let(:changeset) do
|
12
|
+
repo.changeset(:users, name: 'Jane')
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'has data' do
|
16
|
+
expect(changeset.to_h).to eql(name: 'Jane')
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'has relation' do
|
20
|
+
expect(changeset.relation).to be(repo.users)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'can return a dedicated command' do
|
24
|
+
expect(changeset.command.call).to eql(id: 1, name: 'Jane')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'with an array' do
|
29
|
+
let(:changeset) do
|
30
|
+
repo.changeset(:users, data)
|
31
|
+
end
|
32
|
+
|
33
|
+
let(:data) do
|
34
|
+
[{ name: 'Jane' }, { name: 'Joe' }]
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'has data' do
|
38
|
+
expect(changeset.to_a).to eql(data)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'has relation' do
|
42
|
+
expect(changeset.relation).to be(repo.users)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'can return a dedicated command' do
|
46
|
+
expect(changeset.command.call).to eql([{ id: 1, name: 'Jane' }, { id: 2, name: 'Joe' }])
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe ROM::Changeset::Update do
|
52
|
+
let(:changeset) do
|
53
|
+
repo.changeset(:users, user[:id], name: 'Jane Doe')
|
54
|
+
end
|
55
|
+
|
56
|
+
let(:user) do
|
57
|
+
repo.command(:create, repo.users).call(name: 'Jane')
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'has data' do
|
61
|
+
expect(changeset.to_h).to eql(name: 'Jane Doe')
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'has diff' do
|
65
|
+
expect(changeset.diff).to eql(name: 'Jane Doe')
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'has relation' do
|
69
|
+
expect(changeset.relation).to be(repo.users)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'can return a dedicated command' do
|
73
|
+
expect(changeset.command.call).to eql(id: 1, name: 'Jane Doe')
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe ROM::Changeset::Delete do
|
78
|
+
let(:changeset) do
|
79
|
+
repo.changeset(delete: relation)
|
80
|
+
end
|
81
|
+
|
82
|
+
let(:relation) do
|
83
|
+
repo.users.by_pk(user[:id])
|
84
|
+
end
|
85
|
+
|
86
|
+
let(:user) do
|
87
|
+
repo.command(:create, repo.users).call(name: 'Jane')
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'has relation' do
|
91
|
+
expect(changeset.relation).to eql(relation)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'can return a dedicated command' do
|
95
|
+
expect(changeset.command.call.to_h).to eql(id: 1, name: 'Jane')
|
96
|
+
expect(relation.one).to be(nil)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe 'custom changeset class' do
|
101
|
+
let(:changeset) do
|
102
|
+
repo.changeset(changeset_class[:users]).data({})
|
103
|
+
end
|
104
|
+
|
105
|
+
let(:changeset_class) do
|
106
|
+
Class.new(ROM::Changeset::Create) do
|
107
|
+
def to_h
|
108
|
+
__data__.merge(name: 'Jane')
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'has data' do
|
114
|
+
expect(changeset.to_h).to eql(name: 'Jane')
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'has relation' do
|
118
|
+
expect(changeset.relation).to be(repo.users)
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'can return a dedicated command' do
|
122
|
+
expect(changeset.command.call).to eql(id: 1, name: 'Jane')
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|