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,88 @@
1
+ require 'spec_helper'
2
+
3
+ describe ROM::Commands::Lazy do
4
+ let(:rom) { setup.finalize }
5
+ let(:setup) { ROM.setup(:memory) }
6
+
7
+ let(:create_user) { rom.command(:users).create }
8
+ let(:update_user) { rom.command(:users).update }
9
+ let(:create_task) { rom.command(:tasks).create }
10
+
11
+ let(:user) { { user: { name: 'Jane' } } }
12
+ let(:evaluator) { -> input { input[:user] } }
13
+
14
+ before do
15
+ setup.relation(:tasks)
16
+
17
+ setup.relation(:users) do
18
+ def by_name(name)
19
+ restrict(name: name)
20
+ end
21
+ end
22
+
23
+ setup.commands(:users) do
24
+ define(:create) do
25
+ result :one
26
+ end
27
+
28
+ define(:update)
29
+ end
30
+
31
+ setup.commands(:tasks) do
32
+ define(:create)
33
+ end
34
+ end
35
+
36
+ describe '#call' do
37
+ subject(:command) { ROM::Commands::Lazy.new(create_user, evaluator) }
38
+
39
+ it 'evaluates the input and calls command' do
40
+ command.call(user)
41
+
42
+ expect(rom.relation(:users)).to match_array([
43
+ { name: 'Jane' }
44
+ ])
45
+ end
46
+ end
47
+
48
+ describe '#>>' do
49
+ subject(:command) { ROM::Commands::Lazy.new(create_user, evaluator) }
50
+
51
+ it 'composes with another command' do
52
+ expect(command >> create_task).to be_instance_of(ROM::Commands::Composite)
53
+ end
54
+ end
55
+
56
+ describe '#combine' do
57
+ subject(:command) { ROM::Commands::Lazy.new(create_user, evaluator) }
58
+
59
+ it 'combines with another command' do
60
+ expect(command.combine(create_task)).to be_instance_of(ROM::Commands::Graph)
61
+ end
62
+ end
63
+
64
+ describe '#method_missing' do
65
+ subject(:command) { ROM::Commands::Lazy.new(update_user, evaluator) }
66
+
67
+ it 'forwards to command' do
68
+ rom.relations[:users] << { name: 'Jane' } << { name: 'Joe' }
69
+
70
+ new_command = command.by_name('Jane')
71
+ new_command.call(user: { name: 'Jane Doe' })
72
+
73
+ expect(rom.relation(:users)).to match_array([
74
+ { name: 'Jane Doe' },
75
+ { name: 'Joe' }
76
+ ])
77
+ end
78
+
79
+ it 'return original response if it was not a command' do
80
+ response = command.result
81
+ expect(response).to be(:many)
82
+ end
83
+
84
+ it 'raises error when message is unknown' do
85
+ expect { command.not_here }.to raise_error(NoMethodError, /not_here/)
86
+ end
87
+ end
88
+ end
@@ -64,7 +64,7 @@ describe 'Commands' do
64
64
 
65
65
  describe 'extending command with a db-specific behavior' do
66
66
  before do
67
- setup.repositories[:default].instance_exec do
67
+ setup.gateways[:default].instance_exec do
68
68
  def extend_command_class(klass, _)
69
69
  klass.class_eval do
70
70
  def super_command?
@@ -103,7 +103,7 @@ describe 'Commands' do
103
103
  end
104
104
 
105
105
  registry = ROM::Command.registry(
106
- rom.relations, setup.repositories, commands.values
106
+ rom.relations, setup.gateways, commands.values
107
107
  )
108
108
 
109
109
  expect(registry).to eql(
@@ -12,6 +12,14 @@ describe ROM::Env do
12
12
 
13
13
  setup.relation(:tasks)
14
14
 
15
+ setup.commands(:users) do
16
+ define(:create)
17
+ end
18
+
19
+ setup.commands(:tasks) do
20
+ define(:create)
21
+ end
22
+
15
23
  setup.mappers do
16
24
  define(:users) do
17
25
  attribute :name
@@ -25,6 +33,24 @@ describe ROM::Env do
25
33
  end
26
34
  end
27
35
 
36
+ describe '#command' do
37
+ it 'returns registered command registry' do
38
+ expect(rom.command(:users)).to be_instance_of(ROM::CommandRegistry)
39
+ end
40
+
41
+ it 'returns registered command' do
42
+ expect(rom.command(:users).create).to be_kind_of(ROM::Commands::Create)
43
+ end
44
+
45
+ it 'accepts an array with graph options and input' do
46
+ expect(rom.command([:users, [:create]])).to be_kind_of(ROM::Commands::Lazy)
47
+ end
48
+
49
+ it 'raises ArgumentError when unsupported arg was passed' do
50
+ expect { rom.command({ oops: 'sorry' }) }.to raise_error(ArgumentError)
51
+ end
52
+ end
53
+
28
54
  describe '#relation' do
29
55
  it 'yields selected relation to the block and returns a loaded relation' do
30
56
  result = rom.relation(:users) { |r| r.by_name('Jane') }.as(:name_list)
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ describe ROM::Gateway do
4
+ describe '.setup' do
5
+ it 'sets up a gateway based on a type' do
6
+ gateway_class = Class.new(ROM::Gateway) do
7
+ attr_reader :args
8
+
9
+ def initialize(*args)
10
+ @args = args
11
+ end
12
+ end
13
+
14
+ allow(ROM::Gateway).to receive(:class_from_symbol)
15
+ .with(:wormhole).and_return(gateway_class)
16
+
17
+ args = %w(hello world)
18
+ gateway = ROM::Gateway.setup(:wormhole, *args)
19
+
20
+ expect(gateway).to be_instance_of(gateway_class)
21
+ expect(gateway.args).to eq(args)
22
+ end
23
+
24
+ it 'raises an exception if the type is not supported' do
25
+ expect {
26
+ ROM::Gateway.setup(:bogus, "memory://test")
27
+ }.to raise_error(ROM::AdapterLoadError, /bogus/)
28
+ end
29
+
30
+ it 'accepts a gateway instance' do
31
+ gateway = ROM::Gateway.new
32
+ expect(ROM::Gateway.setup(gateway)).to be(gateway)
33
+ end
34
+
35
+ it 'raises an exception if instance and arguments are passed' do
36
+ gateway = ROM::Gateway.new
37
+
38
+ expect { ROM::Gateway.setup(gateway, 'foo://bar') }.to raise_error(
39
+ ArgumentError,
40
+ "Can't accept arguments when passing an instance"
41
+ )
42
+ end
43
+
44
+ it 'raises an exception if a URI string is passed' do
45
+ expect { ROM::Gateway.setup('memory://test') }.to raise_error(
46
+ ArgumentError,
47
+ /URIs without an explicit scheme are not supported anymore/
48
+ )
49
+ end
50
+ end
51
+
52
+ describe '.class_from_symbol' do
53
+ context 'when adapter is already present' do
54
+ before do
55
+ module Test
56
+ module Adapter
57
+ class Gateway
58
+ end
59
+ end
60
+ end
61
+
62
+ ROM.register_adapter(:test_adapter, Test::Adapter)
63
+ end
64
+
65
+ it 'does not try to require an adapter if it is already present' do
66
+ klass = ROM::Gateway.class_from_symbol(:test_adapter)
67
+
68
+ expect(klass).to be(Test::Adapter::Gateway)
69
+ end
70
+ end
71
+
72
+ it 'instantiates a gateway based on type' do
73
+ klass = ROM::Gateway.class_from_symbol(:memory)
74
+ expect(klass).to be(ROM::Memory::Gateway)
75
+ end
76
+
77
+ it 'raises an exception if the type is not supported' do
78
+ expect { ROM::Gateway.class_from_symbol(:bogus) }
79
+ .to raise_error(ROM::AdapterLoadError, /bogus/)
80
+ end
81
+ end
82
+
83
+ describe '#disconnect' do
84
+ it 'does nothing' do
85
+ gateway_class = Class.new(ROM::Gateway)
86
+ gateway = gateway_class.new
87
+ expect(gateway.disconnect).to be(nil)
88
+ end
89
+ end
90
+ end
@@ -2,13 +2,14 @@ require 'spec_helper'
2
2
 
3
3
  describe ROM do
4
4
  describe '.setup' do
5
- it 'allows passing in repository instances' do
6
- klass = Class.new(ROM::Repository)
5
+ it 'allows passing in gateway instances' do
6
+ klass = Class.new(ROM::Gateway)
7
7
  repo = klass.new
8
8
 
9
9
  setup = ROM.setup(test: repo)
10
10
 
11
- expect(setup.repositories[:test]).to be(repo)
11
+ expect(setup.gateways[:test]).to be(repo)
12
12
  end
13
13
  end
14
+
14
15
  end
@@ -232,6 +232,19 @@ describe ROM::Mapper do
232
232
 
233
233
  expect(header).to eql(expected_header)
234
234
  end
235
+
236
+ it 'raises an exception when using a block and options to define attributes' do
237
+ expect {
238
+ mapper.wrap(city: [:name]) { attribute :other_name }
239
+ }.to raise_error(ROM::MapperMisconfiguredError)
240
+ end
241
+
242
+ it 'raises an exception when using options and a mapper to define attributes' do
243
+ task_mapper = Class.new(ROM::Mapper) { attribute :title }
244
+ expect {
245
+ mapper.wrap city: [:name], mapper: task_mapper
246
+ }.to raise_error(ROM::MapperMisconfiguredError)
247
+ end
235
248
  end
236
249
 
237
250
  describe '#group' do
@@ -250,6 +263,19 @@ describe ROM::Mapper do
250
263
 
251
264
  expect(header).to eql(expected_header)
252
265
  end
266
+
267
+ it 'raises an exception when using a block and options to define attributes' do
268
+ expect {
269
+ mapper.group(cities: [:name]) { attribute :other_name }
270
+ }.to raise_error(ROM::MapperMisconfiguredError)
271
+ end
272
+
273
+ it 'raises an exception when using options and a mapper to define attributes' do
274
+ task_mapper = Class.new(ROM::Mapper) { attribute :title }
275
+ expect {
276
+ mapper.group cities: [:name], mapper: task_mapper
277
+ }.to raise_error(ROM::MapperMisconfiguredError)
278
+ end
253
279
  end
254
280
 
255
281
  describe 'top-level :prefix option' do
@@ -386,7 +412,7 @@ describe ROM::Mapper do
386
412
  let(:attributes) do
387
413
  [
388
414
  [:name],
389
- [:task, type: :array, header: task_mapper.header]
415
+ [:task, type: :hash, header: task_mapper.header]
390
416
  ]
391
417
  end
392
418
 
@@ -416,6 +442,21 @@ describe ROM::Mapper do
416
442
 
417
443
  expect(header).to eql(expected_header)
418
444
  end
445
+
446
+ it 'works without a block' do
447
+ expected_header = ROM::Header.coerce(
448
+ [
449
+ [:title],
450
+ [:tasks, combine: true, type: :array, header: []]
451
+ ]
452
+ )
453
+
454
+ mapper.attribute :title
455
+
456
+ mapper.combine :tasks, on: { title: :title }
457
+
458
+ expect(header).to eql(expected_header)
459
+ end
419
460
  end
420
461
 
421
462
  describe '#method_missing' do
@@ -30,7 +30,10 @@ describe ROM::Mapper do
30
30
  describe '.registry' do
31
31
  it 'builds mapper class registry for base and virtual relations' do
32
32
  users = Class.new(ROM::Mapper) { relation(:users) }
33
- entity = Class.new(ROM::Mapper) { relation(:users); register_as(:entity) }
33
+ entity = Class.new(ROM::Mapper) do
34
+ relation(:users)
35
+ register_as(:entity)
36
+ end
34
37
  active = Class.new(users) { relation(:active) }
35
38
  admins = Class.new(users) { relation(:admins) }
36
39
  custom = Class.new(users) { register_as(:custom) }
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ require 'rom/memory'
4
+
5
+ describe ROM::Memory::Commands::Delete do
6
+ include_context 'users and tasks'
7
+
8
+ subject(:command) { ROM::Memory::Commands::Delete.build(users) }
9
+
10
+ let(:users) { rom.relations[:users] }
11
+
12
+ before do
13
+ setup.relation(:users) do
14
+ def by_id(id)
15
+ restrict(id: id)
16
+ end
17
+ end
18
+ end
19
+
20
+ it_behaves_like 'a command'
21
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ require 'rom/memory'
4
+
5
+ describe ROM::Memory::Commands::Create do
6
+ include_context 'users and tasks'
7
+
8
+ subject(:command) { ROM::Memory::Commands::Create.build(users) }
9
+
10
+ let(:users) { rom.relations[:users] }
11
+
12
+ before do
13
+ setup.relation(:users) do
14
+ def by_id(id)
15
+ restrict(id: id)
16
+ end
17
+ end
18
+ end
19
+
20
+ it_behaves_like 'a command'
21
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ require 'rom/memory'
4
+
5
+ describe ROM::Memory::Commands::Update do
6
+ include_context 'users and tasks'
7
+
8
+ subject(:command) { ROM::Memory::Commands::Update.build(users) }
9
+
10
+ let(:users) { rom.relations[:users] }
11
+
12
+ before do
13
+ setup.relation(:users) do
14
+ def by_id(id)
15
+ restrict(id: id)
16
+ end
17
+ end
18
+ end
19
+
20
+ it_behaves_like 'a command'
21
+ end
@@ -11,7 +11,11 @@ describe ROM::Memory::Relation do
11
11
  ROM::Memory::Dataset.new([
12
12
  { name: 'Jane', email: 'jane@doe.org', age: 10 },
13
13
  { name: 'Jade', email: 'jade@doe.org', age: 11 },
14
- { name: 'Joe', email: 'joe@doe.org', age: 12 }
14
+ { name: 'Joe', email: 'joe@doe.org', age: 12 },
15
+ { name: 'Jack', age: 11 },
16
+ { name: 'Jill', email: 'jill@doe.org' },
17
+ { name: 'John' },
18
+ { name: 'Judy', email: 'judy@doe.org', age: 11 }
15
19
  ])
16
20
  end
17
21
 
@@ -29,7 +33,11 @@ describe ROM::Memory::Relation do
29
33
  expect(relation.project(:name, :age)).to match_array([
30
34
  { name: 'Jane', age: 10 },
31
35
  { name: 'Jade', age: 11 },
32
- { name: 'Joe', age: 12 }
36
+ { name: 'Joe', age: 12 },
37
+ { name: 'Jack', age: 11 },
38
+ { name: 'Jill' },
39
+ { name: 'John' },
40
+ { name: 'Judy', age: 11 }
33
41
  ])
34
42
  end
35
43
  end
@@ -44,33 +52,57 @@ describe ROM::Memory::Relation do
44
52
  end
45
53
 
46
54
  it 'restricts data using block' do
47
- expect(relation.restrict { |tuple| tuple[:age] > 10 }).to match_array([
55
+ expect(relation.restrict { |tuple| tuple[:age].to_i > 10 }).to match_array([
48
56
  { name: 'Jade', email: 'jade@doe.org', age: 11 },
49
- { name: 'Joe', email: 'joe@doe.org', age: 12 }
57
+ { name: 'Joe', email: 'joe@doe.org', age: 12 },
58
+ { name: 'Jack', age: 11 },
59
+ { name: 'Judy', email: 'judy@doe.org', age: 11 }
50
60
  ])
51
61
  end
52
62
 
53
63
  it 'allows to use array as a value' do
54
64
  expect(relation.restrict(age: [10, 11])).to match_array([
55
65
  { name: 'Jane', email: 'jane@doe.org', age: 10 },
56
- { name: 'Jade', email: 'jade@doe.org', age: 11 }
66
+ { name: 'Jade', email: 'jade@doe.org', age: 11 },
67
+ { name: 'Jack', age: 11 },
68
+ { name: 'Judy', email: 'judy@doe.org', age: 11 }
57
69
  ])
58
70
  end
59
71
 
60
72
  it 'allows to use regexp as a value' do
61
73
  expect(relation.restrict(name: /\w{4}/)).to match_array([
62
74
  { name: 'Jane', email: 'jane@doe.org', age: 10 },
63
- { name: 'Jade', email: 'jade@doe.org', age: 11 }
75
+ { name: 'Jade', email: 'jade@doe.org', age: 11 },
76
+ { name: 'Jack', age: 11 },
77
+ { name: 'Jill', email: 'jill@doe.org' },
78
+ { name: 'John' },
79
+ { name: 'Judy', email: 'judy@doe.org', age: 11 }
64
80
  ])
65
81
  end
66
82
  end
67
83
 
68
84
  describe '#order' do
69
85
  it 'sorts data using provided attribute names' do
70
- expect(relation.order(:name).to_a).to eq([
71
- { name: 'Jade', email: 'jade@doe.org', age: 11 },
72
- { name: 'Jane', email: 'jane@doe.org', age: 10 },
73
- { name: 'Joe', email: 'joe@doe.org', age: 12 }
86
+ expect(relation.order(:age, :email).to_a).to eq([
87
+ { name: 'Jane', age: 10, email: 'jane@doe.org' },
88
+ { name: 'Jade', age: 11, email: 'jade@doe.org' },
89
+ { name: 'Judy', age: 11, email: 'judy@doe.org' },
90
+ { name: 'Jack', age: 11 },
91
+ { name: 'Joe', age: 12, email: 'joe@doe.org' },
92
+ { name: 'Jill', email: 'jill@doe.org' },
93
+ { name: 'John' }
94
+ ])
95
+ end
96
+
97
+ it 'places nil before other values when required' do
98
+ expect(relation.order(:age, :email, nils_first: true).to_a).to eq([
99
+ { name: 'John' },
100
+ { name: 'Jill', email: 'jill@doe.org' },
101
+ { name: 'Jane', age: 10, email: 'jane@doe.org' },
102
+ { name: 'Jack', age: 11 },
103
+ { name: 'Jade', age: 11, email: 'jade@doe.org' },
104
+ { name: 'Judy', age: 11, email: 'judy@doe.org' },
105
+ { name: 'Joe', age: 12, email: 'joe@doe.org' }
74
106
  ])
75
107
  end
76
108
  end