rom 0.7.1 → 0.8.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.
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