rom 1.0.0 → 2.0.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -1
  3. data/.travis.yml +5 -3
  4. data/CHANGELOG.md +38 -0
  5. data/Gemfile +2 -14
  6. data/README.md +11 -17
  7. data/lib/rom.rb +2 -0
  8. data/lib/rom/association_set.rb +26 -0
  9. data/lib/rom/command.rb +50 -45
  10. data/lib/rom/command_registry.rb +26 -3
  11. data/lib/rom/commands/class_interface.rb +52 -19
  12. data/lib/rom/commands/composite.rb +5 -0
  13. data/lib/rom/commands/delete.rb +1 -5
  14. data/lib/rom/commands/graph.rb +11 -0
  15. data/lib/rom/commands/lazy.rb +2 -0
  16. data/lib/rom/commands/update.rb +1 -5
  17. data/lib/rom/configuration.rb +2 -0
  18. data/lib/rom/container.rb +3 -3
  19. data/lib/rom/global.rb +1 -23
  20. data/lib/rom/memory/commands.rb +2 -0
  21. data/lib/rom/memory/relation.rb +3 -0
  22. data/lib/rom/memory/storage.rb +4 -7
  23. data/lib/rom/memory/types.rb +9 -0
  24. data/lib/rom/pipeline.rb +26 -12
  25. data/lib/rom/plugin_registry.rb +2 -2
  26. data/lib/rom/plugins/command/schema.rb +26 -0
  27. data/lib/rom/plugins/configuration/configuration_dsl.rb +2 -1
  28. data/lib/rom/plugins/relation/key_inference.rb +18 -3
  29. data/lib/rom/plugins/relation/registry_reader.rb +3 -1
  30. data/lib/rom/plugins/relation/view.rb +11 -6
  31. data/lib/rom/relation.rb +76 -16
  32. data/lib/rom/relation/class_interface.rb +44 -3
  33. data/lib/rom/relation/curried.rb +13 -4
  34. data/lib/rom/relation/graph.rb +15 -5
  35. data/lib/rom/relation/loaded.rb +42 -6
  36. data/lib/rom/relation/name.rb +102 -0
  37. data/lib/rom/relation_registry.rb +5 -0
  38. data/lib/rom/schema.rb +87 -0
  39. data/lib/rom/schema/dsl.rb +58 -0
  40. data/lib/rom/setup/auto_registration.rb +2 -2
  41. data/lib/rom/setup/finalize.rb +5 -5
  42. data/lib/rom/setup/finalize/{commands.rb → finalize_commands.rb} +2 -22
  43. data/lib/rom/setup/finalize/{mappers.rb → finalize_mappers.rb} +0 -0
  44. data/lib/rom/setup/finalize/finalize_relations.rb +60 -0
  45. data/lib/rom/types.rb +18 -0
  46. data/lib/rom/version.rb +1 -1
  47. data/log/.gitkeep +0 -0
  48. data/rom.gemspec +4 -2
  49. data/spec/integration/command_registry_spec.rb +13 -0
  50. data/spec/integration/commands/delete_spec.rb +0 -17
  51. data/spec/integration/commands/graph_builder_spec.rb +1 -1
  52. data/spec/integration/commands/graph_spec.rb +1 -1
  53. data/spec/integration/commands/update_spec.rb +0 -19
  54. data/spec/integration/commands_spec.rb +10 -3
  55. data/spec/integration/multi_repo_spec.rb +1 -1
  56. data/spec/integration/relations/default_dataset_spec.rb +27 -4
  57. data/spec/integration/setup_spec.rb +1 -4
  58. data/spec/shared/command_behavior.rb +17 -7
  59. data/spec/shared/container.rb +2 -2
  60. data/spec/shared/gateway_only.rb +1 -1
  61. data/spec/spec_helper.rb +5 -6
  62. data/spec/unit/rom/association_set_spec.rb +23 -0
  63. data/spec/unit/rom/auto_registration_spec.rb +1 -1
  64. data/spec/unit/rom/commands/lazy_spec.rb +8 -0
  65. data/spec/unit/rom/commands_spec.rb +45 -7
  66. data/spec/unit/rom/configurable_spec.rb +1 -1
  67. data/spec/unit/rom/container_spec.rb +6 -0
  68. data/spec/unit/rom/create_container_spec.rb +1 -1
  69. data/spec/unit/rom/environment_spec.rb +1 -1
  70. data/spec/unit/rom/memory/commands_spec.rb +43 -0
  71. data/spec/unit/rom/plugins/relation/key_inference_spec.rb +70 -12
  72. data/spec/unit/rom/plugins/relation/view_spec.rb +4 -0
  73. data/spec/unit/rom/relation/graph_spec.rb +10 -0
  74. data/spec/unit/rom/relation/lazy_spec.rb +3 -3
  75. data/spec/unit/rom/relation/loaded_spec.rb +15 -0
  76. data/spec/unit/rom/relation/name_spec.rb +51 -0
  77. data/spec/unit/rom/relation/schema_spec.rb +117 -0
  78. data/spec/unit/rom/relation_spec.rb +37 -7
  79. data/spec/unit/rom/schema_spec.rb +10 -0
  80. metadata +51 -12
  81. data/lib/rom/setup/finalize/relations.rb +0 -53
  82. data/spec/unit/rom/global_spec.rb +0 -18
  83. data/spec/unit/rom/registry_spec.rb +0 -38
@@ -15,6 +15,19 @@ describe 'ROM::CommandRegistry' do
15
15
  end)
16
16
  end
17
17
 
18
+ describe '#[]' do
19
+ it 'fetches a command from the registry' do
20
+ expect(users[:create]).to be_a(ROM::Commands::Create[:memory])
21
+ end
22
+
23
+ it 'throws an error when the command is not found' do
24
+ expect { users[:not_found] }.to raise_error(
25
+ ROM::CommandRegistry::CommandNotFoundError,
26
+ 'There is no :not_found command for :users relation'
27
+ )
28
+ end
29
+ end
30
+
18
31
  describe '#try' do
19
32
  it 'returns a success result object on successful execution' do
20
33
  result = users.try { users.create.call(name: 'Jane') }
@@ -64,21 +64,4 @@ describe 'Commands / Delete' do
64
64
 
65
65
  expect(result.value).to eql(name: 'Jane', email: 'jane@doe.org')
66
66
  end
67
-
68
- it 'raises when result is set to :one and relation contains more tuples' do
69
- configuration.commands(:users) do
70
- define(:delete) do
71
- result :one
72
- end
73
- end
74
-
75
- result = users.try { users.delete.call }
76
-
77
- expect(result.error).to be_instance_of(ROM::TupleCountMismatchError)
78
-
79
- expect(container.relations.users.to_a).to match_array([
80
- { name: 'Jane', email: 'jane@doe.org' },
81
- { name: 'Joe', email: 'joe@doe.org' }
82
- ])
83
- end
84
67
  end
@@ -208,6 +208,6 @@ RSpec.describe 'Command graph builder' do
208
208
  it 'raises when unknown command is accessed' do
209
209
  expect {
210
210
  container.command.not_here(:users)
211
- }.to raise_error(ROM::Registry::ElementNotFoundError, /not_here/)
211
+ }.to raise_error(ROM::CommandRegistry::CommandNotFoundError, /not_here/)
212
212
  end
213
213
  end
@@ -265,7 +265,7 @@ describe 'Building up a command graph for nested input' do
265
265
 
266
266
  command = container.command(options).as(:entity)
267
267
 
268
- result = command.call(input).one
268
+ result = command.call(input)
269
269
 
270
270
  expect(result).to be_instance_of(Test::User)
271
271
  expect(result.tasks.first).to be_instance_of(Test::Task)
@@ -73,25 +73,6 @@ describe 'Commands / Update' do
73
73
  expect(result.value).to eql(name: 'Jane', email: 'jane.doe@test.com')
74
74
  end
75
75
 
76
- it 'raises when there is more than one tuple and result is set to :one' do
77
- configuration.commands(:users) do
78
- define(:update_one, type: :update) do
79
- result :one
80
- end
81
- end
82
-
83
- result = users.try {
84
- users.update_one.call(email: 'jane.doe@test.com')
85
- }
86
-
87
- expect(result.error).to be_instance_of(ROM::TupleCountMismatchError)
88
-
89
- expect(container.relations.users).to match_array([
90
- { name: 'Jane', email: 'jane@doe.org' },
91
- { name: 'Joe', email: 'joe@doe.org' }
92
- ])
93
- end
94
-
95
76
  it 'allows only valid result types' do
96
77
  expect {
97
78
  configuration.commands(:users) do
@@ -10,20 +10,27 @@ describe 'Commands' do
10
10
  restrict(id: id)
11
11
  end
12
12
  end
13
+
13
14
  configuration.commands(:users) do
14
15
  define(:update)
16
+ define(:create)
15
17
  end
16
18
  end
17
19
 
18
- let(:command) { container.command(:users)[:update] }
20
+ let(:create) { container.command(:users)[:create] }
21
+ let(:update) { container.command(:users)[:update] }
19
22
 
20
23
  describe '#method_missing' do
21
24
  it 'forwards known relation view methods' do
22
- expect(command.by_id(1).relation).to eql(users_relation.by_id(1))
25
+ expect(update.by_id(1).relation).to eql(users_relation.by_id(1))
23
26
  end
24
27
 
25
28
  it 'raises no-method error when a non-view relation method was sent' do
26
- expect { command.as(:foo) }.to raise_error(NoMethodError, /as/)
29
+ expect { update.as(:foo) }.to raise_error(NoMethodError, /as/)
30
+ end
31
+
32
+ it 'does not forward relation view methods to non-restrictable commands' do
33
+ expect { create.by_id(1) }.to raise_error(NoMethodError, /by_id/)
27
34
  end
28
35
  end
29
36
 
@@ -3,7 +3,7 @@ require 'rom/memory'
3
3
 
4
4
  describe 'Using in-memory gateways for cross-repo access' do
5
5
  let(:configuration) do
6
- ROM::Configuration.new(left: :memory, right: :memory, main: :memory).use(:macros)
6
+ ROM::Configuration.new(left: :memory, right: :memory, main: :memory)
7
7
  end
8
8
 
9
9
  let(:container) { ROM.container(configuration) }
@@ -5,11 +5,34 @@ describe ROM::Relation, '.dataset' do
5
5
 
6
6
  it 'injects configured dataset when block was provided' do
7
7
  configuration.relation(:users) do
8
- dataset { restrict(name: 'Jane') }
8
+ dataset do
9
+ insert(id: 2, name: 'Joe')
10
+ insert(id: 1, name: 'Jane')
11
+
12
+ restrict(name: 'Jane')
13
+ end
14
+ end
15
+
16
+ expect(container.relation(:users).to_a).to eql([{ id: 1, name: 'Jane' }])
17
+ end
18
+
19
+ it 'yields relation class for setting custom dataset proc' do
20
+ configuration.relation(:users) do
21
+ schema do
22
+ attribute :id, ROM::Memory::Types::Int.meta(primary_key: true)
23
+ attribute :name, ROM::Memory::Types::String
24
+ end
25
+
26
+ dataset do |rel_klass|
27
+ insert(id: 2, name: 'Joe')
28
+ insert(id: 1, name: 'Jane')
29
+
30
+ order(*rel_klass.schema.primary_key.map { |t| t.meta[:name] })
31
+ end
9
32
  end
10
33
 
11
- expect(container.relation(:users).dataset).to eql(
12
- container.relation(:users).dataset.restrict(name: 'Jane')
13
- )
34
+ expect(container.relation(:users).to_a).to eql([
35
+ { id: 1, name: 'Jane' }, { id: 2, name: 'Joe' }
36
+ ])
14
37
  end
15
38
  end
@@ -43,7 +43,6 @@ describe 'Configuring ROM' do
43
43
  describe 'defining classes' do
44
44
  it 'sets up registries based on class definitions' do
45
45
  container = ROM.container(:memory) do |config|
46
- config.use(:macros)
47
46
 
48
47
  class Test::UserRelation < ROM::Relation[:memory]
49
48
  dataset :users
@@ -87,8 +86,6 @@ describe 'Configuring ROM' do
87
86
  end
88
87
 
89
88
  container = ROM.container(:memory) do |rom|
90
- rom.use(:macros)
91
-
92
89
  rom.relation(:users) do
93
90
  def by_name(name)
94
91
  restrict(name: name)
@@ -122,7 +119,7 @@ describe 'Configuring ROM' do
122
119
  end
123
120
  end
124
121
 
125
- configuration = ROM::Configuration.new(:memory).use(:macros)
122
+ configuration = ROM::Configuration.new(:memory)
126
123
 
127
124
  configuration.relation(:users) do
128
125
  def by_name(name)
@@ -1,14 +1,24 @@
1
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)
2
+ describe '#name' do
3
+ it 'returns relation name' do
4
+ expect(command.name).to eql(command.relation.name)
5
+ end
6
+ end
5
7
 
6
- expect(new_command).to be_instance_of(command.class)
7
- expect(new_command.relation).to eql(command.by_id(1).relation)
8
+ describe '#gateway' do
9
+ it 'returns relation gateway' do
10
+ expect(command.gateway).to eql(command.relation.gateway)
8
11
  end
12
+ end
9
13
 
10
- it 'returns original response if it was not a relation' do
11
- expect(command.name).to eql(command.relation.name)
14
+ describe '#method_missing' do
15
+ it 'forwards to relation and wraps response if it returned another relation' do
16
+ if command.class.restrictable
17
+ new_command = command.by_id(1)
18
+
19
+ expect(new_command).to be_instance_of(command.class)
20
+ expect(new_command.relation).to eql(command.by_id(1).relation)
21
+ end
12
22
  end
13
23
 
14
24
  it 'raises error when message is not known' do
@@ -1,9 +1,9 @@
1
1
  RSpec.shared_context 'container' do
2
2
  let(:container) { ROM.container(configuration) }
3
- let!(:configuration) { ROM::Configuration.new(:memory).use(:macros) }
3
+ let!(:configuration) { ROM::Configuration.new(:memory) }
4
4
  let(:gateway) { configuration.gateways[:default] }
5
5
  let(:users_relation) { container.relation(:users) }
6
6
  let(:tasks_relation) { container.relation(:tasks) }
7
7
  let(:users_dataset) { gateway.dataset(:users) }
8
8
  let(:tasks_dataset) { gateway.dataset(:tasks) }
9
- end
9
+ end
@@ -3,4 +3,4 @@ RSpec.shared_context 'gateway only' do
3
3
 
4
4
  let(:users_dataset) { gateway.dataset(:users) }
5
5
  let(:tasks_dataset) { gateway.dataset(:tasks) }
6
- end
6
+ end
@@ -9,6 +9,11 @@ if RUBY_ENGINE == "rbx"
9
9
  CodeClimate::TestReporter.start
10
10
  end
11
11
 
12
+ SPEC_ROOT = root = Pathname(__FILE__).dirname
13
+
14
+ require 'rom/support/deprecations'
15
+ ROM::Deprecations.set_logger!(SPEC_ROOT.join('../log/deprecations.log'))
16
+
12
17
  require 'rom'
13
18
  require 'anima'
14
19
 
@@ -17,8 +22,6 @@ begin
17
22
  rescue LoadError
18
23
  end
19
24
 
20
- SPEC_ROOT = root = Pathname(__FILE__).dirname
21
-
22
25
  Dir[root.join('support/*.rb').to_s].each do |f|
23
26
  require f
24
27
  end
@@ -38,10 +41,6 @@ def T(*args)
38
41
  end
39
42
 
40
43
  RSpec.configure do |config|
41
- config.before do
42
- ROM.env = nil
43
- end
44
-
45
44
  config.after do
46
45
  Test.remove_constants
47
46
  end
@@ -0,0 +1,23 @@
1
+ RSpec.describe ROM::AssociationSet do
2
+ describe '#try' do
3
+ it 'returns association when it exists' do
4
+ assoc = spy(:assoc)
5
+ assoc_set = ROM::AssociationSet.new(users: assoc)
6
+
7
+ assoc_set.try(:users, &:done)
8
+
9
+ expect(assoc).to have_received(:done)
10
+ end
11
+
12
+ it 'returns false when assoc is not found' do
13
+ assoc = spy(:assoc)
14
+ fallback = spy(:fallback)
15
+ assoc_set = ROM::AssociationSet.new({})
16
+
17
+ assoc_set.try(:users, &:done) or fallback.done
18
+
19
+ expect(assoc).to_not have_received(:done)
20
+ expect(fallback).to have_received(:done)
21
+ end
22
+ end
23
+ end
@@ -6,7 +6,7 @@ RSpec.describe ROM::Setup, '#auto_registration' do
6
6
 
7
7
  context 'with namespace turned on' do
8
8
  before do
9
- setup.auto_registration(SPEC_ROOT.join('fixtures/lib/persistence'))
9
+ setup.auto_registration(SPEC_ROOT.join('fixtures/lib/persistence').to_s)
10
10
  end
11
11
 
12
12
  describe '#relations' do
@@ -284,6 +284,14 @@ describe ROM::Commands::Lazy do
284
284
  end
285
285
  end
286
286
 
287
+ describe '#unwrap' do
288
+ subject(:command) { ROM::Commands::Lazy[create_user].new(create_user, evaluator) }
289
+
290
+ it 'returns wrapped command' do
291
+ expect(command.unwrap).to be(create_user)
292
+ end
293
+ end
294
+
287
295
  describe '#method_missing' do
288
296
  subject(:command) { ROM::Commands::Lazy[update_user].new(update_user, evaluator) }
289
297
 
@@ -30,6 +30,24 @@ describe 'Commands' do
30
30
  end
31
31
  end
32
32
 
33
+ describe '.create_class' do
34
+ it 'builds a class' do
35
+ klass = ROM::Command.create_class(:create, ROM::Memory::Commands::Create)
36
+
37
+ expect(klass.name).to eql('ROM::Memory::Commands::Create[:create]')
38
+ end
39
+
40
+ it 'builds a class and yields it' do
41
+ klass = ROM::Command.create_class(:create, ROM::Memory::Commands::Create) do |k|
42
+ k.result :one
43
+ k
44
+ end
45
+
46
+ expect(klass.name).to eql('ROM::Memory::Commands::Create[:create]')
47
+ expect(klass.result).to be(:one)
48
+ end
49
+ end
50
+
33
51
  describe '.build' do
34
52
  it 'returns create command when type is set to :create' do
35
53
  klass = Class.new(ROM::Commands::Create[:memory]) do
@@ -63,9 +81,9 @@ describe 'Commands' do
63
81
  end
64
82
 
65
83
  describe '#>>' do
66
- let(:users) { double('users') }
67
- let(:tasks) { double('tasks') }
68
- let(:logs) { [] }
84
+ let(:users) { double('users', schema: nil) }
85
+ let(:tasks) { double('tasks', schema: nil) }
86
+ let(:logs) { double('logs', schema: nil) }
69
87
 
70
88
  it 'composes two commands' do
71
89
  user_input = { name: 'Jane' }
@@ -100,11 +118,9 @@ describe 'Commands' do
100
118
 
101
119
  expect(users).to receive(:insert).with(user_input).and_return(user_tuple)
102
120
  expect(tasks).to receive(:insert).with(task_tuple).and_return(task_tuple)
121
+ expect(logs).to receive(:<<).with(task_tuple).and_return([task_tuple])
103
122
 
104
- result = command.call
105
-
106
- expect(result).to eql(task_tuple)
107
- expect(logs).to include(task_tuple)
123
+ expect(command.call).to eql(task_tuple)
108
124
  end
109
125
 
110
126
  it 'forwards methods to the left' do
@@ -123,5 +139,27 @@ describe 'Commands' do
123
139
 
124
140
  command.with(user_input).call
125
141
  end
142
+
143
+ it 'short-circuits pipeline when left-side result is empty' do
144
+ command = Class.new(ROM::Commands::Update) do
145
+ result :one
146
+
147
+ def execute(*)
148
+ []
149
+ end
150
+ end.build(users) >> -> result { result.map(&:to_a) }
151
+
152
+ expect(command.call('foo')).to be(nil)
153
+
154
+ command = Class.new(ROM::Commands::Update) do
155
+ result :many
156
+
157
+ def execute(*)
158
+ []
159
+ end
160
+ end.build(users) >> -> result { result.map(&:to_a) }
161
+
162
+ expect(command.call('foo')).to be(ROM::EMPTY_ARRAY)
163
+ end
126
164
  end
127
165
  end
@@ -46,4 +46,4 @@ RSpec.describe ROM::Configurable do
46
46
  expect(config.key?(:sql)).to be(true)
47
47
  end
48
48
  end
49
- end
49
+ end
@@ -90,4 +90,10 @@ describe ROM::Container do
90
90
  expect(container.mappers.users[:name_list]).to_not be(nil)
91
91
  end
92
92
  end
93
+
94
+ describe '#disconnect' do
95
+ it 'does not break' do
96
+ container.disconnect
97
+ end
98
+ end
93
99
  end
@@ -148,4 +148,4 @@ describe ROM::CreateContainer do
148
148
  end
149
149
  end
150
150
  end
151
- end
151
+ end
@@ -120,4 +120,4 @@ RSpec.describe ROM::Environment do
120
120
  expect(gateways_map).to be_empty
121
121
  end
122
122
  end
123
- end
123
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+ require 'rom/memory'
3
+
4
+ describe ROM::Memory::Commands do
5
+ let(:relation) do
6
+ Class.new(ROM::Relation[:memory]) do
7
+ schema do
8
+ attribute :id, ROM::Memory::Types::Int
9
+ attribute :name, ROM::Memory::Types::String
10
+ end
11
+ end.new(ROM::Memory::Dataset.new([]))
12
+ end
13
+
14
+ describe 'Create' do
15
+ subject(:command) { ROM::Commands::Create[:memory].build(relation) }
16
+
17
+ describe '#call' do
18
+ it 'uses default input handler' do
19
+ result = command.call([id: 1, name: 'Jane', haha: 'oops'])
20
+
21
+ expect(result).to eql([{ id: 1, name: 'Jane' }])
22
+ end
23
+ end
24
+ end
25
+
26
+ describe 'Update' do
27
+ subject(:command) { ROM::Commands::Update[:memory].build(relation) }
28
+
29
+ before do
30
+ relation.insert(id: 1, name: 'Jane')
31
+ end
32
+
33
+ describe '#call' do
34
+ it 'uses default input handler' do
35
+ result = command
36
+ .new(relation.restrict(id: 1))
37
+ .call(name: 'Jane Doe', haha: 'oops')
38
+
39
+ expect(result).to eql([{ id: 1, name: 'Jane Doe' }])
40
+ end
41
+ end
42
+ end
43
+ end