rom-repository 0.3.1 → 1.0.0.beta1

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +11 -13
  3. data/CHANGELOG.md +25 -0
  4. data/Gemfile +13 -3
  5. data/lib/rom/repository.rb +57 -19
  6. data/lib/rom/repository/changeset.rb +89 -26
  7. data/lib/rom/repository/changeset/create.rb +34 -0
  8. data/lib/rom/repository/changeset/delete.rb +15 -0
  9. data/lib/rom/repository/changeset/pipe.rb +11 -4
  10. data/lib/rom/repository/changeset/update.rb +11 -1
  11. data/lib/rom/repository/command_compiler.rb +51 -30
  12. data/lib/rom/repository/command_proxy.rb +3 -1
  13. data/lib/rom/repository/header_builder.rb +3 -3
  14. data/lib/rom/repository/mapper_builder.rb +2 -2
  15. data/lib/rom/repository/relation_proxy.rb +26 -35
  16. data/lib/rom/repository/relation_proxy/combine.rb +59 -27
  17. data/lib/rom/repository/root.rb +4 -6
  18. data/lib/rom/repository/session.rb +55 -0
  19. data/lib/rom/repository/struct_builder.rb +29 -17
  20. data/lib/rom/repository/version.rb +1 -1
  21. data/lib/rom/struct.rb +11 -20
  22. data/rom-repository.gemspec +4 -3
  23. data/spec/integration/command_macros_spec.rb +5 -2
  24. data/spec/integration/command_spec.rb +0 -6
  25. data/spec/integration/multi_adapter_spec.rb +8 -5
  26. data/spec/integration/repository_spec.rb +58 -2
  27. data/spec/integration/root_repository_spec.rb +9 -2
  28. data/spec/integration/typed_structs_spec.rb +31 -0
  29. data/spec/shared/database.rb +5 -1
  30. data/spec/shared/relations.rb +3 -1
  31. data/spec/shared/repo.rb +13 -1
  32. data/spec/shared/structs.rb +39 -0
  33. data/spec/spec_helper.rb +7 -5
  34. data/spec/support/mutant.rb +10 -0
  35. data/spec/unit/changeset/map_spec.rb +42 -0
  36. data/spec/unit/changeset_spec.rb +32 -6
  37. data/spec/unit/relation_proxy_spec.rb +27 -9
  38. data/spec/unit/repository/changeset_spec.rb +125 -0
  39. data/spec/unit/repository/inspect_spec.rb +18 -0
  40. data/spec/unit/repository/session_spec.rb +251 -0
  41. data/spec/unit/session_spec.rb +54 -0
  42. data/spec/unit/struct_builder_spec.rb +45 -1
  43. metadata +41 -17
  44. data/lib/rom/repository/struct_attributes.rb +0 -46
  45. data/spec/unit/header_builder_spec.rb +0 -73
  46. data/spec/unit/plugins/view_spec.rb +0 -29
  47. data/spec/unit/sql/relation_spec.rb +0 -54
  48. data/spec/unit/struct_spec.rb +0 -22
@@ -52,7 +52,9 @@ RSpec.shared_context 'relations' do
52
52
  end
53
53
  end
54
54
 
55
- configuration.relation(:tags)
55
+ configuration.relation(:tags) do
56
+ schema(infer: true)
57
+ end
56
58
 
57
59
  configuration.relation(:labels) do
58
60
  schema(infer: true) do
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
@@ -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 == "ruby" && RUBY_VERSION == '2.3.1'
8
- require "codeclimate-test-reporter"
9
- CodeClimate::TestReporter.start
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 'rom/support/deprecations'
32
- ROM::Deprecations.set_logger!(root.join('../log/deprecations.log'))
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,10 @@
1
+ module Mutant
2
+ class Selector
3
+ # Expression based test selector
4
+ class Expression < self
5
+ def call(_subject)
6
+ integration.all_tests
7
+ end
8
+ end # Expression
9
+ end # Selector
10
+ end # Mutant
@@ -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
@@ -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::Types::Struct) do
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, [[:attribute, :id], [:attribute, :user_id], [:attribute, :title]]]
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, [ [:attribute, :id], [:attribute, :user_id], [:attribute, :title]]]
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