rom 1.0.0 → 2.0.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.
- checksums.yaml +4 -4
- data/.rspec +1 -1
- data/.travis.yml +5 -3
- data/CHANGELOG.md +38 -0
- data/Gemfile +2 -14
- data/README.md +11 -17
- data/lib/rom.rb +2 -0
- data/lib/rom/association_set.rb +26 -0
- data/lib/rom/command.rb +50 -45
- data/lib/rom/command_registry.rb +26 -3
- data/lib/rom/commands/class_interface.rb +52 -19
- data/lib/rom/commands/composite.rb +5 -0
- data/lib/rom/commands/delete.rb +1 -5
- data/lib/rom/commands/graph.rb +11 -0
- data/lib/rom/commands/lazy.rb +2 -0
- data/lib/rom/commands/update.rb +1 -5
- data/lib/rom/configuration.rb +2 -0
- data/lib/rom/container.rb +3 -3
- data/lib/rom/global.rb +1 -23
- data/lib/rom/memory/commands.rb +2 -0
- data/lib/rom/memory/relation.rb +3 -0
- data/lib/rom/memory/storage.rb +4 -7
- data/lib/rom/memory/types.rb +9 -0
- data/lib/rom/pipeline.rb +26 -12
- data/lib/rom/plugin_registry.rb +2 -2
- data/lib/rom/plugins/command/schema.rb +26 -0
- data/lib/rom/plugins/configuration/configuration_dsl.rb +2 -1
- data/lib/rom/plugins/relation/key_inference.rb +18 -3
- data/lib/rom/plugins/relation/registry_reader.rb +3 -1
- data/lib/rom/plugins/relation/view.rb +11 -6
- data/lib/rom/relation.rb +76 -16
- data/lib/rom/relation/class_interface.rb +44 -3
- data/lib/rom/relation/curried.rb +13 -4
- data/lib/rom/relation/graph.rb +15 -5
- data/lib/rom/relation/loaded.rb +42 -6
- data/lib/rom/relation/name.rb +102 -0
- data/lib/rom/relation_registry.rb +5 -0
- data/lib/rom/schema.rb +87 -0
- data/lib/rom/schema/dsl.rb +58 -0
- data/lib/rom/setup/auto_registration.rb +2 -2
- data/lib/rom/setup/finalize.rb +5 -5
- data/lib/rom/setup/finalize/{commands.rb → finalize_commands.rb} +2 -22
- data/lib/rom/setup/finalize/{mappers.rb → finalize_mappers.rb} +0 -0
- data/lib/rom/setup/finalize/finalize_relations.rb +60 -0
- data/lib/rom/types.rb +18 -0
- data/lib/rom/version.rb +1 -1
- data/log/.gitkeep +0 -0
- data/rom.gemspec +4 -2
- data/spec/integration/command_registry_spec.rb +13 -0
- data/spec/integration/commands/delete_spec.rb +0 -17
- data/spec/integration/commands/graph_builder_spec.rb +1 -1
- data/spec/integration/commands/graph_spec.rb +1 -1
- data/spec/integration/commands/update_spec.rb +0 -19
- data/spec/integration/commands_spec.rb +10 -3
- data/spec/integration/multi_repo_spec.rb +1 -1
- data/spec/integration/relations/default_dataset_spec.rb +27 -4
- data/spec/integration/setup_spec.rb +1 -4
- data/spec/shared/command_behavior.rb +17 -7
- data/spec/shared/container.rb +2 -2
- data/spec/shared/gateway_only.rb +1 -1
- data/spec/spec_helper.rb +5 -6
- data/spec/unit/rom/association_set_spec.rb +23 -0
- data/spec/unit/rom/auto_registration_spec.rb +1 -1
- data/spec/unit/rom/commands/lazy_spec.rb +8 -0
- data/spec/unit/rom/commands_spec.rb +45 -7
- data/spec/unit/rom/configurable_spec.rb +1 -1
- data/spec/unit/rom/container_spec.rb +6 -0
- data/spec/unit/rom/create_container_spec.rb +1 -1
- data/spec/unit/rom/environment_spec.rb +1 -1
- data/spec/unit/rom/memory/commands_spec.rb +43 -0
- data/spec/unit/rom/plugins/relation/key_inference_spec.rb +70 -12
- data/spec/unit/rom/plugins/relation/view_spec.rb +4 -0
- data/spec/unit/rom/relation/graph_spec.rb +10 -0
- data/spec/unit/rom/relation/lazy_spec.rb +3 -3
- data/spec/unit/rom/relation/loaded_spec.rb +15 -0
- data/spec/unit/rom/relation/name_spec.rb +51 -0
- data/spec/unit/rom/relation/schema_spec.rb +117 -0
- data/spec/unit/rom/relation_spec.rb +37 -7
- data/spec/unit/rom/schema_spec.rb +10 -0
- metadata +51 -12
- data/lib/rom/setup/finalize/relations.rb +0 -53
- data/spec/unit/rom/global_spec.rb +0 -18
- 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::
|
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)
|
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(:
|
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(
|
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 {
|
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)
|
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
|
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).
|
12
|
-
|
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)
|
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 '#
|
3
|
-
it '
|
4
|
-
|
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
|
-
|
7
|
-
|
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
|
-
|
11
|
-
|
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
|
data/spec/shared/container.rb
CHANGED
@@ -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)
|
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
|
data/spec/shared/gateway_only.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -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
|
-
|
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
|
@@ -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
|