rom 0.4.2 → 0.5.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 (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