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,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