rom 0.7.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +5 -8
  4. data/CHANGELOG.md +28 -1
  5. data/CODE_OF_CONDUCT.md +13 -0
  6. data/Gemfile +2 -2
  7. data/lib/rom.rb +1 -1
  8. data/lib/rom/command.rb +7 -5
  9. data/lib/rom/command_registry.rb +1 -1
  10. data/lib/rom/commands.rb +0 -2
  11. data/lib/rom/commands/abstract.rb +55 -25
  12. data/lib/rom/commands/composite.rb +13 -1
  13. data/lib/rom/commands/delete.rb +0 -8
  14. data/lib/rom/commands/graph.rb +102 -0
  15. data/lib/rom/commands/graph/class_interface.rb +69 -0
  16. data/lib/rom/commands/lazy.rb +87 -0
  17. data/lib/rom/constants.rb +22 -0
  18. data/lib/rom/env.rb +48 -18
  19. data/lib/rom/gateway.rb +132 -0
  20. data/lib/rom/global.rb +19 -19
  21. data/lib/rom/header.rb +42 -16
  22. data/lib/rom/header/attribute.rb +37 -15
  23. data/lib/rom/lint/gateway.rb +94 -0
  24. data/lib/rom/lint/spec.rb +15 -3
  25. data/lib/rom/lint/test.rb +45 -14
  26. data/lib/rom/mapper.rb +23 -10
  27. data/lib/rom/mapper/attribute_dsl.rb +157 -18
  28. data/lib/rom/memory.rb +1 -1
  29. data/lib/rom/memory/commands.rb +10 -8
  30. data/lib/rom/memory/dataset.rb +22 -2
  31. data/lib/rom/memory/{repository.rb → gateway.rb} +10 -10
  32. data/lib/rom/pipeline.rb +2 -1
  33. data/lib/rom/processor/transproc.rb +105 -14
  34. data/lib/rom/relation.rb +4 -4
  35. data/lib/rom/relation/class_interface.rb +19 -13
  36. data/lib/rom/relation/graph.rb +22 -0
  37. data/lib/rom/relation/lazy.rb +5 -3
  38. data/lib/rom/repository.rb +9 -118
  39. data/lib/rom/setup.rb +21 -14
  40. data/lib/rom/setup/finalize.rb +19 -19
  41. data/lib/rom/setup_dsl/relation.rb +10 -1
  42. data/lib/rom/support/deprecations.rb +21 -3
  43. data/lib/rom/support/enumerable_dataset.rb +1 -1
  44. data/lib/rom/version.rb +1 -1
  45. data/rom.gemspec +2 -4
  46. data/spec/integration/commands/delete_spec.rb +6 -0
  47. data/spec/integration/commands/graph_spec.rb +235 -0
  48. data/spec/integration/mappers/combine_spec.rb +14 -5
  49. data/spec/integration/mappers/definition_dsl_spec.rb +6 -1
  50. data/spec/integration/mappers/exclude_spec.rb +28 -0
  51. data/spec/integration/mappers/fold_spec.rb +16 -0
  52. data/spec/integration/mappers/group_spec.rb +0 -22
  53. data/spec/integration/mappers/prefix_separator_spec.rb +54 -0
  54. data/spec/integration/mappers/prefix_spec.rb +50 -0
  55. data/spec/integration/mappers/reusing_mappers_spec.rb +21 -0
  56. data/spec/integration/mappers/step_spec.rb +120 -0
  57. data/spec/integration/mappers/unfold_spec.rb +93 -0
  58. data/spec/integration/mappers/ungroup_spec.rb +127 -0
  59. data/spec/integration/mappers/unwrap_spec.rb +2 -2
  60. data/spec/integration/multi_repo_spec.rb +11 -11
  61. data/spec/integration/repositories/setting_logger_spec.rb +2 -2
  62. data/spec/integration/setup_spec.rb +11 -1
  63. data/spec/shared/command_behavior.rb +18 -0
  64. data/spec/shared/materializable.rb +4 -2
  65. data/spec/shared/users_and_tasks.rb +3 -3
  66. data/spec/test/memory_repository_lint_test.rb +4 -4
  67. data/spec/unit/rom/commands/graph_spec.rb +198 -0
  68. data/spec/unit/rom/commands/lazy_spec.rb +88 -0
  69. data/spec/unit/rom/commands_spec.rb +2 -2
  70. data/spec/unit/rom/env_spec.rb +26 -0
  71. data/spec/unit/rom/gateway_spec.rb +90 -0
  72. data/spec/unit/rom/global_spec.rb +4 -3
  73. data/spec/unit/rom/mapper/dsl_spec.rb +42 -1
  74. data/spec/unit/rom/mapper_spec.rb +4 -1
  75. data/spec/unit/rom/memory/commands/create_spec.rb +21 -0
  76. data/spec/unit/rom/memory/commands/delete_spec.rb +21 -0
  77. data/spec/unit/rom/memory/commands/update_spec.rb +21 -0
  78. data/spec/unit/rom/memory/relation_spec.rb +42 -10
  79. data/spec/unit/rom/memory/repository_spec.rb +3 -3
  80. data/spec/unit/rom/processor/transproc_spec.rb +75 -0
  81. data/spec/unit/rom/relation/lazy/combine_spec.rb +33 -4
  82. data/spec/unit/rom/relation/lazy_spec.rb +9 -1
  83. data/spec/unit/rom/repository_spec.rb +4 -63
  84. data/spec/unit/rom/setup_spec.rb +19 -5
  85. metadata +28 -38
  86. data/.ruby-version +0 -1
  87. data/lib/rom/lint/repository.rb +0 -94
@@ -0,0 +1,127 @@
1
+ require 'spec_helper'
2
+ require 'rom/memory'
3
+
4
+ describe 'Mapper definition DSL' do
5
+ let(:setup) { ROM.setup(:memory) }
6
+ let(:rom) { ROM.finalize.env }
7
+
8
+ before do
9
+ setup.relation(:users)
10
+
11
+ users = setup.default.dataset(:users)
12
+ users.insert(name: 'Joe', emails: [
13
+ { address: 'joe@home.org', type: 'home' },
14
+ { address: 'joe@job.com', type: 'job' },
15
+ { address: 'joe@doe.com', type: 'job' },
16
+ { address: 'joe@thor.org' },
17
+ { type: 'home' },
18
+ {}
19
+ ])
20
+ users.insert(name: 'Jane')
21
+ end
22
+
23
+ describe 'ungroup' do
24
+ subject(:mapped_users) { rom.relation(:users).as(:users).to_a }
25
+
26
+ it 'partially ungroups attributes' do
27
+ setup.mappers do
28
+ define(:users) { ungroup emails: [:type] }
29
+ end
30
+
31
+ expect(mapped_users).to eql [
32
+ {
33
+ name: 'Joe', type: 'home',
34
+ emails: [{ address: 'joe@home.org' }, { address: nil }]
35
+ },
36
+ {
37
+ name: 'Joe', type: 'job',
38
+ emails: [{ address: 'joe@job.com' }, { address: 'joe@doe.com' }]
39
+ },
40
+ {
41
+ name: 'Joe', type: nil,
42
+ emails: [{ address: 'joe@thor.org' }, { address: nil }]
43
+ },
44
+ { name: 'Jane' }
45
+ ]
46
+ end
47
+
48
+ it 'removes group when all attributes extracted' do
49
+ setup.mappers do
50
+ define(:users) { ungroup emails: [:address, :type, :foo] }
51
+ end
52
+
53
+ expect(mapped_users).to eql [
54
+ { name: 'Joe', address: 'joe@home.org', type: 'home' },
55
+ { name: 'Joe', address: 'joe@job.com', type: 'job' },
56
+ { name: 'Joe', address: 'joe@doe.com', type: 'job' },
57
+ { name: 'Joe', address: 'joe@thor.org', type: nil },
58
+ { name: 'Joe', address: nil, type: 'home' },
59
+ { name: 'Joe', address: nil, type: nil },
60
+ { name: 'Jane' }
61
+ ]
62
+ end
63
+
64
+ it 'accepts block syntax' do
65
+ setup.mappers do
66
+ define(:users) do
67
+ ungroup :emails do
68
+ attribute :address
69
+ attribute :type
70
+ end
71
+ end
72
+ end
73
+
74
+ expect(mapped_users).to eql [
75
+ { name: 'Joe', address: 'joe@home.org', type: 'home' },
76
+ { name: 'Joe', address: 'joe@job.com', type: 'job' },
77
+ { name: 'Joe', address: 'joe@doe.com', type: 'job' },
78
+ { name: 'Joe', address: 'joe@thor.org', type: nil },
79
+ { name: 'Joe', address: nil, type: 'home' },
80
+ { name: 'Joe', address: nil, type: nil },
81
+ { name: 'Jane' }
82
+ ]
83
+ end
84
+
85
+ it 'renames ungrouped attributes' do
86
+ setup.mappers do
87
+ define(:users) do
88
+ ungroup :emails do
89
+ attribute :email, from: :address
90
+ attribute :type
91
+ end
92
+ end
93
+ end
94
+
95
+ expect(mapped_users).to eql [
96
+ { name: 'Joe', email: 'joe@home.org', type: 'home' },
97
+ { name: 'Joe', email: 'joe@job.com', type: 'job' },
98
+ { name: 'Joe', email: 'joe@doe.com', type: 'job' },
99
+ { name: 'Joe', email: 'joe@thor.org', type: nil },
100
+ { name: 'Joe', email: nil, type: 'home' },
101
+ { name: 'Joe', email: nil, type: nil },
102
+ { name: 'Jane' }
103
+ ]
104
+ end
105
+
106
+ it 'skips existing attributes' do
107
+ setup.mappers do
108
+ define(:users) do
109
+ ungroup :emails do
110
+ attribute :name, from: :address
111
+ attribute :type
112
+ end
113
+ end
114
+ end
115
+
116
+ expect(mapped_users).to eql [
117
+ { name: 'Joe', type: 'home' },
118
+ { name: 'Joe', type: 'job' },
119
+ { name: 'Joe', type: 'job' },
120
+ { name: 'Joe', type: nil },
121
+ { name: 'Joe', type: 'home' },
122
+ { name: 'Joe', type: nil },
123
+ { name: 'Jane' }
124
+ ]
125
+ end
126
+ end
127
+ end
@@ -78,7 +78,7 @@ describe 'Mapper definition DSL' do
78
78
  attribute :title
79
79
  attribute :priority
80
80
 
81
- unwrap :user do
81
+ unwrap :contact, from: :user do
82
82
  attribute :task_user_name, from: :name
83
83
  end
84
84
  end
@@ -92,7 +92,7 @@ describe 'Mapper definition DSL' do
92
92
  priority: 2,
93
93
  name: 'Jane',
94
94
  task_user_name: 'Jane',
95
- user: { email: 'jane@doe.org' })
95
+ contact: { email: 'jane@doe.org' })
96
96
  end
97
97
  end
98
98
  end
@@ -1,24 +1,24 @@
1
1
  require 'spec_helper'
2
2
  require 'rom/memory'
3
3
 
4
- describe 'Using in-memory repositories for cross-repo access' do
4
+ describe 'Using in-memory gateways for cross-repo access' do
5
5
  let(:setup) do
6
6
  ROM.setup(left: :memory, right: :memory, main: :memory)
7
7
  end
8
8
 
9
- let(:repositories) { rom.repositories }
9
+ let(:gateways) { rom.gateways }
10
10
  let(:rom) { setup.finalize }
11
11
 
12
12
  it 'works' do
13
- setup.relation(:users, repository: :left) do
13
+ setup.relation(:users, gateway: :left) do
14
14
  def by_name(name)
15
15
  restrict(name: name)
16
16
  end
17
17
  end
18
18
 
19
- setup.relation(:tasks, repository: :right)
19
+ setup.relation(:tasks, gateway: :right)
20
20
 
21
- setup.relation(:users_and_tasks, repository: :main) do
21
+ setup.relation(:users_and_tasks, gateway: :main) do
22
22
  def by_user(name)
23
23
  join(users.by_name(name), tasks)
24
24
  end
@@ -30,14 +30,14 @@ describe 'Using in-memory repositories for cross-repo access' do
30
30
  end
31
31
  end
32
32
 
33
- repositories[:left][:users] << { user_id: 1, name: 'Joe' }
34
- repositories[:left][:users] << { user_id: 2, name: 'Jane' }
35
- repositories[:right][:tasks] << { user_id: 1, title: 'Have fun' }
36
- repositories[:right][:tasks] << { user_id: 2, title: 'Have fun' }
33
+ gateways[:left][:users] << { user_id: 1, name: 'Joe' }
34
+ gateways[:left][:users] << { user_id: 2, name: 'Jane' }
35
+ gateways[:right][:tasks] << { user_id: 1, title: 'Have fun' }
36
+ gateways[:right][:tasks] << { user_id: 2, title: 'Have fun' }
37
37
 
38
38
  user_and_tasks = rom.relation(:users_and_tasks)
39
- .by_user('Jane')
40
- .as(:users_and_tasks)
39
+ .by_user('Jane')
40
+ .as(:users_and_tasks)
41
41
 
42
42
  expect(user_and_tasks).to match_array([
43
43
  { user_id: 2, name: 'Jane', tasks: [{ title: 'Have fun' }] }
@@ -22,14 +22,14 @@ describe 'Repositories / Setting logger' do
22
22
  logger_class.new
23
23
  end
24
24
 
25
- it 'sets up a logger for a given repository' do
25
+ it 'sets up a logger for a given gateway' do
26
26
  setup = ROM.setup(:memory)
27
27
 
28
28
  setup.default.use_logger(logger)
29
29
 
30
30
  rom = setup.finalize
31
31
 
32
- rom.repositories[:default].logger.info("test")
32
+ rom.gateways[:default].logger.info("test")
33
33
 
34
34
  expect(logger.messages).to eql(["test"])
35
35
  end
@@ -14,7 +14,7 @@ describe 'Setting up ROM' do
14
14
  end
15
15
 
16
16
  it 'configures schema relations' do
17
- expect(rom.repositories[:default][:users]).to match_array([joe, jane])
17
+ expect(rom.gateways[:default][:users]).to match_array([joe, jane])
18
18
  end
19
19
 
20
20
  it 'configures rom relations' do
@@ -48,6 +48,16 @@ describe 'Setting up ROM' do
48
48
  end
49
49
  end
50
50
 
51
+ context 'with old syntax' do
52
+ it 'warns when old repository key is used' do
53
+ setup = ROM.setup(data: :memory)
54
+
55
+ expect {
56
+ setup.relation(:users, repository: :data)
57
+ }.to output(/use `gateway: :data`/).to_stderr
58
+ end
59
+ end
60
+
51
61
  describe 'defining classes' do
52
62
  it 'sets up registries based on class definitions' do
53
63
  ROM.setup(:memory)
@@ -0,0 +1,18 @@
1
+ shared_examples_for 'a command' do
2
+ describe '#method_missing' do
3
+ it 'forwards to relation and wraps response if it returned another relation' do
4
+ new_command = command.by_id(1)
5
+
6
+ expect(new_command).to be_instance_of(command.class)
7
+ expect(new_command.relation).to eql(command.by_id(1).relation)
8
+ end
9
+
10
+ it 'returns original response if it was not a relation' do
11
+ expect(command.name).to eql(command.relation.name)
12
+ end
13
+
14
+ it 'raises error when message is not known' do
15
+ expect { command.not_here }.to raise_error(NoMethodError, /not_here/)
16
+ end
17
+ end
18
+ end
@@ -1,10 +1,12 @@
1
1
  shared_examples_for 'materializable relation' do
2
2
  describe '#each' do
3
3
  it 'yields objects' do
4
- count = relation.to_a.count
4
+ count = relation.to_a.size
5
5
  result = []
6
6
 
7
- relation.each { |object| result << object }
7
+ relation.each do |object|
8
+ result << object
9
+ end
8
10
 
9
11
  expect(result.count).to eql(count)
10
12
  end
@@ -6,10 +6,10 @@ RSpec.shared_context 'users and tasks' do
6
6
  let(:setup) { ROM.setup(:memory) }
7
7
 
8
8
  before do
9
- repository = setup.default
9
+ gateway = setup.default
10
10
 
11
- users = repository.dataset(:users)
12
- tasks = repository.dataset(:tasks)
11
+ users = gateway.dataset(:users)
12
+ tasks = gateway.dataset(:tasks)
13
13
 
14
14
  users.insert(name: "Joe", email: "joe@doe.org")
15
15
  users.insert(name: "Jane", email: "jane@doe.org")
@@ -5,15 +5,15 @@ require 'rom/lint/test'
5
5
  require 'minitest/autorun'
6
6
 
7
7
  class MemoryRepositoryLintTest < Minitest::Test
8
- include ROM::Lint::TestRepository
8
+ include ROM::Lint::TestGateway
9
9
 
10
10
  def setup
11
- @repository = ROM::Memory::Repository
11
+ @gateway = ROM::Memory::Gateway
12
12
  @identifier = :memory
13
13
  end
14
14
 
15
- def repository_instance
16
- ROM::Memory::Repository.new
15
+ def gateway_instance
16
+ ROM::Memory::Gateway.new
17
17
  end
18
18
  end
19
19
 
@@ -0,0 +1,198 @@
1
+ require 'spec_helper'
2
+
3
+ describe ROM::Commands::Graph do
4
+ shared_examples_for 'a persisted graph' do
5
+ it 'returns nested results' do
6
+ expect(command.call).to match_array([
7
+ # parent users
8
+ [
9
+ { name: 'Jane' },
10
+ ],
11
+ [
12
+ [
13
+ # user tasks
14
+ [
15
+ { title: 'One', user: 'Jane' }
16
+ ],
17
+ [
18
+ # task tags
19
+ [
20
+ { name: 'red', task: 'One' },
21
+ { name: 'green', task: 'One' },
22
+ { name: 'blue', task: 'One' }
23
+ ]
24
+ ]
25
+ ]
26
+ ]
27
+ ])
28
+ end
29
+
30
+ context 'persisted relations' do
31
+ before { command.call }
32
+
33
+ it 'inserts root' do
34
+ expect(rom.relation(:users)).to match_array([{ name: 'Jane' }])
35
+ end
36
+
37
+ it 'inserts root nodes' do
38
+ expect(rom.relation(:tasks)).to match_array([{ user: 'Jane', title: 'One' }])
39
+ end
40
+
41
+ it 'inserts nested graph nodes' do
42
+ expect(rom.relation(:tags)).to match_array([
43
+ { name: 'red', task: 'One' },
44
+ { name: 'green', task: 'One' },
45
+ { name: 'blue', task: 'One' }
46
+ ])
47
+ end
48
+ end
49
+ end
50
+
51
+ let(:rom) { setup.finalize }
52
+ let(:setup) { ROM.setup(:memory) }
53
+
54
+ let(:create_user) { rom.command(:users).create }
55
+ let(:create_task) { rom.command(:tasks).create }
56
+
57
+ let(:create_many_tasks) { rom.command(:tasks).create_many }
58
+ let(:create_many_tags) { rom.command(:tags).create_many }
59
+
60
+ let(:user) { { name: 'Jane' } }
61
+ let(:task) { { title: 'One' } }
62
+ let(:tags) { [{ name: 'red' }, { name: 'green' }, { name: 'blue' }] }
63
+
64
+ before do
65
+ setup.relation(:users)
66
+ setup.relation(:tasks)
67
+ setup.relation(:tags)
68
+
69
+ setup.commands(:users) do
70
+ define(:create) do
71
+ result :one
72
+ end
73
+ end
74
+
75
+ setup.commands(:tasks) do
76
+ define(:create) do
77
+ result :one
78
+
79
+ def execute(task, user)
80
+ super(task.merge(user: user[:name]))
81
+ end
82
+ end
83
+
84
+ define(:create) do
85
+ register_as :create_many
86
+
87
+ def execute(tasks, user)
88
+ super(tasks.map { |t| t.merge(user: user[:name]) })
89
+ end
90
+ end
91
+ end
92
+
93
+ setup.commands(:tags) do
94
+ define(:create) do
95
+ register_as :create_many
96
+
97
+ def execute(tags, tasks)
98
+ super(
99
+ Array([tasks]).flatten.map { |task|
100
+ tags.map { |tag| tag.merge(task: task[:title]) }
101
+ }.flatten
102
+ )
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ describe '#call' do
109
+ context 'when result is :one in root and its direct children' do
110
+ it_behaves_like 'a persisted graph' do
111
+ subject(:command) do
112
+ create_user.with(user)
113
+ .combine(create_task.with(task)
114
+ .combine(create_many_tags.with(tags)))
115
+ end
116
+ end
117
+ end
118
+
119
+ context 'when result is :many for root direct children' do
120
+ it_behaves_like 'a persisted graph' do
121
+ subject(:command) do
122
+ create_user.with(user)
123
+ .combine(create_many_tasks.with([task])
124
+ .combine(create_many_tags.with(tags)))
125
+ end
126
+ end
127
+ end
128
+
129
+ context 'when error is raised' do
130
+ subject(:command) do
131
+ create_user.with(user).combine(create_many_tasks.with([task]))
132
+ end
133
+
134
+ it 're-raises the error providing proper context when root fails' do
135
+ allow(command.root).to receive(:call).and_raise(StandardError, 'ooops')
136
+
137
+ expect { command.call }.to raise_error(ROM::CommandFailure, /oops/)
138
+ end
139
+
140
+ it 're-raises the error providing proper context when a node fails' do
141
+ allow(command.nodes[0]).to receive(:call).and_raise(StandardError, 'ooops')
142
+
143
+ expect { command.call }.to raise_error(ROM::CommandFailure, /oops/)
144
+ end
145
+ end
146
+ end
147
+
148
+ describe 'pipeline' do
149
+ subject(:command) do
150
+ rom.command(:users).as(:entity).create.with(user)
151
+ .combine(create_task.with(task)
152
+ .combine(create_many_tags.with(tags)))
153
+ end
154
+
155
+ before do
156
+ Test::Tag = Class.new { include Anima.new(:name) }
157
+ Test::Task = Class.new { include Anima.new(:title, :tags) }
158
+ Test::User = Class.new { include Anima.new(:name, :task) }
159
+
160
+ class Test::UserMapper < ROM::Mapper
161
+ relation :users
162
+ register_as :entity
163
+ reject_keys :true
164
+
165
+ model Test::User
166
+
167
+ attribute :name
168
+
169
+ combine :task, on: { name: :user }, type: :hash do
170
+ model Test::Task
171
+
172
+ attribute :title
173
+
174
+ combine :tags, on: { title: :task } do
175
+ model Test::Tag
176
+ attribute :name
177
+ end
178
+ end
179
+ end
180
+ end
181
+
182
+ it 'sends data through the pipeline' do
183
+ expect(command.call).to eql(
184
+ Test::User.new(
185
+ name: 'Jane',
186
+ task: Test::Task.new(
187
+ title: 'One',
188
+ tags: [
189
+ Test::Tag.new(name: 'red'),
190
+ Test::Tag.new(name: 'green'),
191
+ Test::Tag.new(name: 'blue'),
192
+ ]
193
+ )
194
+ )
195
+ )
196
+ end
197
+ end
198
+ end