rom 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +81 -0
  3. data/.travis.yml +2 -1
  4. data/CHANGELOG.md +41 -0
  5. data/Gemfile +12 -8
  6. data/Guardfile +17 -11
  7. data/README.md +7 -7
  8. data/Rakefile +29 -0
  9. data/lib/rom.rb +9 -66
  10. data/lib/rom/adapter.rb +45 -12
  11. data/lib/rom/adapter/memory.rb +0 -4
  12. data/lib/rom/adapter/memory/commands.rb +0 -10
  13. data/lib/rom/adapter/memory/dataset.rb +18 -6
  14. data/lib/rom/adapter/memory/storage.rb +0 -3
  15. data/lib/rom/command_registry.rb +24 -43
  16. data/lib/rom/commands.rb +5 -6
  17. data/lib/rom/commands/create.rb +5 -5
  18. data/lib/rom/commands/delete.rb +8 -6
  19. data/lib/rom/commands/result.rb +82 -0
  20. data/lib/rom/commands/update.rb +5 -4
  21. data/lib/rom/commands/with_options.rb +1 -4
  22. data/lib/rom/config.rb +70 -0
  23. data/lib/rom/env.rb +11 -3
  24. data/lib/rom/global.rb +107 -0
  25. data/lib/rom/header.rb +122 -89
  26. data/lib/rom/header/attribute.rb +148 -0
  27. data/lib/rom/mapper.rb +46 -67
  28. data/lib/rom/mapper_builder.rb +20 -73
  29. data/lib/rom/mapper_builder/mapper_dsl.rb +114 -0
  30. data/lib/rom/mapper_builder/model_dsl.rb +29 -0
  31. data/lib/rom/mapper_registry.rb +21 -0
  32. data/lib/rom/model_builder.rb +11 -17
  33. data/lib/rom/processor.rb +28 -0
  34. data/lib/rom/processor/transproc.rb +105 -0
  35. data/lib/rom/reader.rb +81 -21
  36. data/lib/rom/reader_builder.rb +14 -4
  37. data/lib/rom/relation.rb +19 -5
  38. data/lib/rom/relation_builder.rb +20 -6
  39. data/lib/rom/repository.rb +0 -2
  40. data/lib/rom/setup.rb +156 -0
  41. data/lib/rom/{boot → setup}/base_relation_dsl.rb +4 -8
  42. data/lib/rom/setup/command_dsl.rb +46 -0
  43. data/lib/rom/setup/finalize.rb +125 -0
  44. data/lib/rom/setup/mapper_dsl.rb +19 -0
  45. data/lib/rom/{boot → setup}/relation_dsl.rb +1 -4
  46. data/lib/rom/setup/schema_dsl.rb +33 -0
  47. data/lib/rom/support/registry.rb +10 -6
  48. data/lib/rom/version.rb +1 -1
  49. data/rom.gemspec +3 -1
  50. data/spec/integration/adapters/extending_relations_spec.rb +0 -2
  51. data/spec/integration/commands/create_spec.rb +2 -9
  52. data/spec/integration/commands/delete_spec.rb +4 -5
  53. data/spec/integration/commands/error_handling_spec.rb +4 -3
  54. data/spec/integration/commands/update_spec.rb +3 -8
  55. data/spec/integration/mappers/deep_embedded_spec.rb +52 -0
  56. data/spec/integration/mappers/definition_dsl_spec.rb +0 -118
  57. data/spec/integration/mappers/embedded_spec.rb +82 -0
  58. data/spec/integration/mappers/group_spec.rb +170 -0
  59. data/spec/integration/mappers/prefixing_attributes_spec.rb +2 -2
  60. data/spec/integration/mappers/renaming_attributes_spec.rb +8 -6
  61. data/spec/integration/mappers/symbolizing_attributes_spec.rb +80 -0
  62. data/spec/integration/mappers/wrap_spec.rb +162 -0
  63. data/spec/integration/multi_repo_spec.rb +64 -0
  64. data/spec/integration/relations/reading_spec.rb +12 -8
  65. data/spec/integration/relations/registry_dsl_spec.rb +1 -3
  66. data/spec/integration/schema_spec.rb +10 -0
  67. data/spec/integration/setup_spec.rb +57 -6
  68. data/spec/spec_helper.rb +2 -1
  69. data/spec/unit/config_spec.rb +60 -0
  70. data/spec/unit/rom/adapter/memory/dataset_spec.rb +52 -0
  71. data/spec/unit/rom/adapter_spec.rb +31 -11
  72. data/spec/unit/rom/header_spec.rb +60 -16
  73. data/spec/unit/rom/mapper_builder_spec.rb +311 -0
  74. data/spec/unit/rom/mapper_registry_spec.rb +25 -0
  75. data/spec/unit/rom/mapper_spec.rb +4 -5
  76. data/spec/unit/rom/model_builder_spec.rb +15 -13
  77. data/spec/unit/rom/processor/transproc_spec.rb +331 -0
  78. data/spec/unit/rom/reader_spec.rb +73 -0
  79. data/spec/unit/rom/registry_spec.rb +38 -0
  80. data/spec/unit/rom/relation_spec.rb +0 -1
  81. data/spec/unit/rom/setup_spec.rb +55 -0
  82. data/spec/unit/rom_spec.rb +14 -0
  83. metadata +62 -22
  84. data/Gemfile.devtools +0 -71
  85. data/lib/rom/boot.rb +0 -197
  86. data/lib/rom/boot/command_dsl.rb +0 -48
  87. data/lib/rom/boot/dsl.rb +0 -37
  88. data/lib/rom/boot/mapper_dsl.rb +0 -23
  89. data/lib/rom/boot/schema_dsl.rb +0 -27
  90. data/lib/rom/ra.rb +0 -172
  91. data/lib/rom/ra/operation/group.rb +0 -47
  92. data/lib/rom/ra/operation/join.rb +0 -39
  93. data/lib/rom/ra/operation/wrap.rb +0 -45
  94. data/lib/rom/transformer.rb +0 -77
  95. data/spec/integration/ra/group_spec.rb +0 -46
  96. data/spec/integration/ra/join_spec.rb +0 -50
  97. data/spec/integration/ra/wrap_spec.rb +0 -37
  98. data/spec/unit/rom/ra/operation/group_spec.rb +0 -55
  99. data/spec/unit/rom/ra/operation/wrap_spec.rb +0 -29
  100. data/spec/unit/rom/transformer_spec.rb +0 -41
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Using in-memory adapter for cross-repo access' do
4
+ it 'works' do
5
+ setup = ROM.setup(
6
+ left: 'memory://localhost/users',
7
+ right: 'memory://localhost/tasks',
8
+ main: 'memory://localhost/main'
9
+ )
10
+
11
+ setup.schema do
12
+ base_relation :users do
13
+ repository :left
14
+ attribute :user_id
15
+ attribute :name
16
+ end
17
+
18
+ base_relation :tasks do
19
+ repository :right
20
+ attribute :user_id
21
+ attribute :title
22
+ end
23
+
24
+ base_relation :users_and_tasks do
25
+ repository :main
26
+
27
+ attribute :user_id
28
+ attribute :name
29
+ attribute :title
30
+ end
31
+ end
32
+
33
+ setup.relation(:users) do
34
+ def by_name(name)
35
+ restrict(name: name)
36
+ end
37
+ end
38
+
39
+ setup.relation(:tasks)
40
+
41
+ setup.relation(:users_and_tasks) do
42
+ def by_user(name)
43
+ join(users.by_name(name), tasks)
44
+ end
45
+ end
46
+
47
+ setup.mappers do
48
+ define(:users_and_tasks) do
49
+ group tasks: [:title]
50
+ end
51
+ end
52
+
53
+ rom = setup.finalize
54
+
55
+ rom.left.users << { user_id: 1, name: 'Joe' }
56
+ rom.left.users << { user_id: 2, name: 'Jane' }
57
+ rom.right.tasks << { user_id: 1, title: 'Have fun' }
58
+ rom.right.tasks << { user_id: 2, title: 'Have fun' }
59
+
60
+ expect(rom.read(:users_and_tasks).by_user('Jane').to_a).to eql([
61
+ { user_id: 2, name: 'Jane', tasks: [{ title: 'Have fun' }] }
62
+ ])
63
+ end
64
+ end
@@ -4,6 +4,8 @@ describe 'Reading relations' do
4
4
  include_context 'users and tasks'
5
5
 
6
6
  it 'exposes a relation reader' do
7
+ setup.relation(:tasks)
8
+
7
9
  setup.relation(:users) do
8
10
  def by_name(name)
9
11
  restrict(name: name)
@@ -20,8 +22,6 @@ describe 'Reading relations' do
20
22
  end
21
23
  end
22
24
 
23
- rom = setup.finalize
24
-
25
25
  users = rom.read(:users).sorted.by_name('Jane')
26
26
  user = users.first
27
27
 
@@ -31,9 +31,9 @@ describe 'Reading relations' do
31
31
  end
32
32
 
33
33
  it 'maps grouped relations' do
34
- setup.relation(:users) do
35
- include ROM::RA
34
+ setup.relation(:tasks)
36
35
 
36
+ setup.relation(:users) do
37
37
  def with_tasks
38
38
  join(tasks)
39
39
  end
@@ -60,7 +60,8 @@ describe 'Reading relations' do
60
60
  User.send(:include, Equalizer.new(:name, :email))
61
61
  UserWithTasks.send(:include, Equalizer.new(:name, :email, :tasks))
62
62
 
63
- expect(rom.read(:users).with_tasks.header.keys).to eql([:name, :email, :tasks])
63
+ keys = rom.read(:users).with_tasks.header.keys
64
+ expect(keys).to eql([:name, :email, :tasks])
64
65
 
65
66
  user = rom.read(:users).sorted.first
66
67
 
@@ -68,6 +69,8 @@ describe 'Reading relations' do
68
69
  User.new(name: "Jane", email: "jane@doe.org")
69
70
  )
70
71
 
72
+ expect(rom.read(:users)).to_not respond_to(:join)
73
+
71
74
  user = rom.read(:users).with_tasks.sorted.first
72
75
 
73
76
  expect(user).to eql(
@@ -79,9 +82,9 @@ describe 'Reading relations' do
79
82
  end
80
83
 
81
84
  it 'maps wrapped relations' do
82
- setup.relation(:users) do
83
- include ROM::RA
85
+ setup.relation(:tasks)
84
86
 
87
+ setup.relation(:users) do
85
88
  def with_task
86
89
  join(tasks)
87
90
  end
@@ -108,7 +111,8 @@ describe 'Reading relations' do
108
111
  User.send(:include, Equalizer.new(:name, :email))
109
112
  UserWithTask.send(:include, Equalizer.new(:name, :email, :task))
110
113
 
111
- expect(rom.read(:users).with_task.header.keys).to eql([:name, :email, :task])
114
+ keys = rom.read(:users).with_task.header.keys
115
+ expect(keys).to eql([:name, :email, :task])
112
116
 
113
117
  user = rom.read(:users).sorted.with_task.first
114
118
 
@@ -15,8 +15,6 @@ describe 'Relation registration DSL' do
15
15
  end
16
16
 
17
17
  setup.relation(:users) do
18
- include ROM::RA
19
-
20
18
  def with_tasks
21
19
  join(tasks)
22
20
  end
@@ -37,7 +35,7 @@ describe 'Relation registration DSL' do
37
35
 
38
36
  users = rom.relations.users
39
37
 
40
- expect(users.with_tasks.to_a).to eql(
38
+ expect(users.with_tasks).to match_array(
41
39
  [{ name: "Joe", email: "joe@doe.org", title: "be nice", priority: 1 },
42
40
  { name: "Joe", email: "joe@doe.org", title: "sleep well", priority: 2 },
43
41
  { name: "Jane", email: "jane@doe.org", title: "be cool", priority: 2 }]
@@ -42,12 +42,22 @@ describe 'Defining schema' do
42
42
  attribute :title
43
43
  end
44
44
  end
45
+
46
+ setup.schema do
47
+ base_relation(:tags) do
48
+ repository :memory
49
+ attribute :name
50
+ end
51
+ end
45
52
  end
46
53
 
47
54
  it_behaves_like 'valid schema' do
48
55
  it 'registers all base relations' do
49
56
  expect(schema.tasks.dataset).to be(rom.memory.tasks)
50
57
  expect(schema.tasks.header).to eql([:title])
58
+
59
+ expect(schema.tags.dataset).to be(rom.memory.tags)
60
+ expect(schema.tags.header).to eql([:name])
51
61
  end
52
62
  end
53
63
  end
@@ -7,10 +7,23 @@ describe 'Setting up ROM' do
7
7
  let(:jane) { { name: 'Jane', email: 'jane@doe.org' } }
8
8
  let(:joe) { { name: 'Joe', email: 'joe@doe.org' } }
9
9
 
10
- it 'configures relations' do
10
+ it 'configures schema relations' do
11
11
  expect(rom.memory.users).to match_array([joe, jane])
12
12
  end
13
13
 
14
+ it 'configures rom relations' do
15
+ users = rom.relations.users
16
+
17
+ expect(users).to be_kind_of(ROM::Relation)
18
+ expect(users).to respond_to(:tasks)
19
+
20
+ tasks = users.tasks
21
+
22
+ expect(tasks).to be_kind_of(ROM::Relation)
23
+ expect(tasks).to respond_to(:users)
24
+ expect(tasks.users).to be(users)
25
+ end
26
+
14
27
  it 'raises on double-finalize' do
15
28
  expect {
16
29
  2.times { setup.finalize }
@@ -33,7 +46,10 @@ describe 'Setting up ROM' do
33
46
 
34
47
  describe 'quick setup' do
35
48
  it 'exposes boot DSL inside the setup block' do
36
- User = Class.new { include Virtus.value_object; values { attribute :name, String } }
49
+ User = Class.new do
50
+ include Virtus.value_object
51
+ values { attribute :name, String }
52
+ end
37
53
 
38
54
  rom = ROM.setup(memory: 'memory://test') do
39
55
  schema do
@@ -60,15 +76,19 @@ describe 'Setting up ROM' do
60
76
  end
61
77
  end
62
78
 
63
- rom.command(:users).create.call(name: 'Jane')
79
+ rom.command(:users).try { create(name: 'Jane') }
64
80
 
65
- expect(rom.read(:users).by_name('Jane').to_a).to eql([User.new(name: 'Jane')])
81
+ expect(rom.read(:users).by_name('Jane').to_a)
82
+ .to eql([User.new(name: 'Jane')])
66
83
  end
67
84
  end
68
85
 
69
86
  describe 'multi-step setup' do
70
87
  it 'exposes boot DSL that can be invoked multiple times' do
71
- User = Class.new { include Virtus.value_object; values { attribute :name, String } }
88
+ User = Class.new do
89
+ include Virtus.value_object
90
+ values { attribute :name, String }
91
+ end
72
92
 
73
93
  ROM.setup(memory: 'memory://test')
74
94
 
@@ -99,7 +119,38 @@ describe 'Setting up ROM' do
99
119
 
100
120
  rom.command(:users).create.call(name: 'Jane')
101
121
 
102
- expect(rom.read(:users).by_name('Jane').to_a).to eql([User.new(name: 'Jane')])
122
+ expect(rom.read(:users).by_name('Jane').to_a)
123
+ .to eql([User.new(name: 'Jane')])
124
+ end
125
+ end
126
+
127
+ describe 'setup with extra options' do
128
+ shared_examples 'adapter with extra options' do
129
+ subject(:adapter) { setup.default.adapter }
130
+
131
+ it 'has connection uri' do
132
+ expect(adapter.uri).to eql(
133
+ Addressable::URI.parse('memory://localhost/test')
134
+ )
135
+ end
136
+
137
+ it 'has extra options' do
138
+ expect(adapter.options).to eql(super: 'option')
139
+ end
140
+ end
141
+
142
+ context 'with a connection uri and options passed separately' do
143
+ let(:setup) { ROM.setup('memory://localhost/test', super: 'option') }
144
+
145
+ it_behaves_like 'adapter with extra options'
146
+ end
147
+
148
+ context 'with option hash' do
149
+ let(:setup) do
150
+ ROM.setup(adapter: 'memory', database: 'test', super: 'option')
151
+ end
152
+
153
+ it_behaves_like 'adapter with extra options'
103
154
  end
104
155
  end
105
156
  end
@@ -24,6 +24,7 @@ RSpec.configure do |config|
24
24
  end
25
25
 
26
26
  config.after do
27
- (Object.constants - @constants).each { |name| Object.send(:remove_const, name) }
27
+ added_constants = Object.constants - @constants
28
+ added_constants.each { |name| Object.send(:remove_const, name) }
28
29
  end
29
30
  end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe ROM::Config do
4
+ describe '.build' do
5
+ let(:raw_config) do
6
+ { adapter: 'memory', hostname: 'localhost', database: 'test' }
7
+ end
8
+
9
+ it 'returns rom repository configuration hash' do
10
+ config = ROM::Config.build(raw_config)
11
+
12
+ expect(config).to eql(default: 'memory://localhost/test')
13
+ end
14
+
15
+ it 'sets additional options' do
16
+ config = ROM::Config.build(raw_config.update(port: 312, root: '/somewhere'))
17
+
18
+ expect(config).to eql(
19
+ default: { uri: 'memory://localhost/test', options: { port: 312 } }
20
+ )
21
+
22
+ config = ROM::Config.build('memory://localhost/test', super: :option)
23
+
24
+ expect(config).to eql(
25
+ default: { uri: 'memory://localhost/test', options: { super: :option } }
26
+ )
27
+ end
28
+
29
+ it 'builds absolute path to the database file when database is a file' do
30
+ expect(ROM::Adapter[:memory]).to receive(:database_file?)
31
+ .with('memory').and_return(true)
32
+
33
+ config = ROM::Config.build(
34
+ adapter: 'memory', database: 'test', root: '/somewhere'
35
+ )
36
+
37
+ expect(config).to eql(default: 'memory:///somewhere/test')
38
+ end
39
+
40
+ it 'sets default password to an empty string' do
41
+ config = ROM::Config.build(raw_config.update(username: 'root'))
42
+ expect(config).to eql(default: 'memory://root:@localhost/test')
43
+ end
44
+
45
+ it 'turns a uri into configuration hash' do
46
+ config = ROM::Config.build('test://localhost/rom')
47
+ expect(config).to eql(default: 'test://localhost/rom')
48
+ end
49
+
50
+ it 'returns original config hash if it is already in rom format' do
51
+ config = ROM::Config.build(test: 'test://localhost/rom')
52
+ expect(config).to eql(test: 'test://localhost/rom')
53
+ end
54
+
55
+ it 'asks adapters to normalize scheme' do
56
+ expect(ROM::Adapter[:memory]).to receive(:normalize_scheme).with('memory')
57
+ ROM::Config.build(raw_config)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ describe ROM::Adapter::Memory::Dataset do
4
+ subject(:dataset) do
5
+ ROM::Adapter::Memory::Dataset.new(data, [:name, :email, :age])
6
+ end
7
+
8
+ let(:data) do
9
+ [
10
+ { name: 'Jane', email: 'jane@doe.org', age: 10 },
11
+ { name: 'Jade', email: 'jade@doe.org', age: 11 },
12
+ { name: 'Joe', email: 'joe@doe.org', age: 12 }
13
+ ]
14
+ end
15
+
16
+ describe '#project' do
17
+ it 'projects tuples with the provided keys' do
18
+ expect(dataset.project(:name, :age)).to match_array([
19
+ { name: 'Jane', age: 10 },
20
+ { name: 'Jade', age: 11 },
21
+ { name: 'Joe', age: 12 }
22
+ ])
23
+ end
24
+ end
25
+
26
+ describe '#restrict' do
27
+ it 'restricts data using criteria hash' do
28
+ expect(dataset.restrict(age: 10)).to match_array([
29
+ { name: 'Jane', email: 'jane@doe.org', age: 10 }
30
+ ])
31
+
32
+ expect(dataset.restrict(age: 10.0)).to match_array([])
33
+ end
34
+
35
+ it 'restricts data using block' do
36
+ expect(dataset.restrict { |tuple| tuple[:age] > 10 }).to match_array([
37
+ { name: 'Jade', email: 'jade@doe.org', age: 11 },
38
+ { name: 'Joe', email: 'joe@doe.org', age: 12 }
39
+ ])
40
+ end
41
+ end
42
+
43
+ describe '#order' do
44
+ it 'sorts data using provided attribute names' do
45
+ expect(dataset.order(:name)).to match_array([
46
+ { name: 'Jade', email: 'jade@doe.org', age: 11 },
47
+ { name: 'Jane', email: 'jane@doe.org', age: 10 },
48
+ { name: 'Joe', email: 'joe@doe.org', age: 12 }
49
+ ])
50
+ end
51
+ end
52
+ end
@@ -6,10 +6,6 @@ describe ROM::Adapter do
6
6
  def self.schemes
7
7
  [:test_scheme]
8
8
  end
9
-
10
- def initialize(uri); end
11
-
12
- ROM::Adapter.register(self)
13
9
  end
14
10
  end
15
11
 
@@ -22,8 +18,8 @@ describe ROM::Adapter do
22
18
 
23
19
  it 'raises an exception if the scheme is not supported' do
24
20
  expect {
25
- ROM::Adapter.setup("bogus:///non-existent")
26
- }.to raise_error(ArgumentError, '"bogus:///non-existent" uri is not supported')
21
+ ROM::Adapter.setup("bogus://any-host")
22
+ }.to raise_error(ArgumentError, '"bogus://any-host" uri is not supported')
27
23
  end
28
24
  end
29
25
 
@@ -39,16 +35,12 @@ describe ROM::Adapter do
39
35
  def self.schemes
40
36
  [:order_test]
41
37
  end
42
-
43
- ROM::Adapter.register(self)
44
38
  end
45
39
 
46
40
  adapter = ROM::Adapter.setup("order_test::memory")
47
41
  expect(adapter).to be_instance_of(OrderTestFirst)
48
42
 
49
- class OrderTestSecond < OrderTestFirst
50
- ROM::Adapter.register(self)
51
- end
43
+ OrderTestSecond = Class.new(OrderTestFirst)
52
44
 
53
45
  adapter = ROM::Adapter.setup("order_test::memory")
54
46
 
@@ -56,4 +48,32 @@ describe ROM::Adapter do
56
48
  end
57
49
  end
58
50
 
51
+ describe '#disconnect' do
52
+ it 'does nothing' do
53
+ adapter_class = Class.new(ROM::Adapter) {
54
+ def self.schemes
55
+ [:bazinga]
56
+ end
57
+ }
58
+
59
+ adapter = adapter_class.new('bazinga://localhost')
60
+
61
+ expect(adapter.disconnect).to be(nil)
62
+ end
63
+ end
64
+
65
+ describe '.setup' do
66
+ it 'supports connection uri and additional options' do
67
+ Class.new(ROM::Adapter) {
68
+ def self.schemes
69
+ [:bazinga]
70
+ end
71
+ }
72
+
73
+ adapter = ROM::Adapter.setup('bazinga://localhost', super: :option)
74
+
75
+ expect(adapter.uri).to eql(Addressable::URI.parse('bazinga://localhost'))
76
+ expect(adapter.options).to eql(super: :option)
77
+ end
78
+ end
59
79
  end