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.
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