rom 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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