mongoid_orderable 5.0.0 → 6.0.2
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 +5 -5
- data/CHANGELOG.md +83 -22
- data/LICENSE.txt +20 -0
- data/README.md +256 -149
- data/Rakefile +24 -6
- data/lib/config/locales/en.yml +12 -9
- data/lib/mongoid/orderable.rb +29 -22
- data/lib/mongoid/orderable/configs/field_config.rb +79 -0
- data/lib/mongoid/orderable/configs/global_config.rb +26 -0
- data/lib/mongoid/orderable/errors/invalid_target_position.rb +19 -18
- data/lib/mongoid/orderable/errors/transaction_failed.rb +20 -0
- data/lib/mongoid/orderable/generators/base.rb +21 -0
- data/lib/mongoid/orderable/generators/helpers.rb +29 -0
- data/lib/mongoid/orderable/generators/listable.rb +41 -0
- data/lib/mongoid/orderable/generators/lock_collection.rb +37 -0
- data/lib/mongoid/orderable/generators/movable.rb +62 -0
- data/lib/mongoid/orderable/generators/position.rb +26 -0
- data/lib/mongoid/orderable/generators/scope.rb +26 -0
- data/lib/mongoid/orderable/handlers/base.rb +167 -0
- data/lib/mongoid/orderable/handlers/document.rb +24 -0
- data/lib/mongoid/orderable/handlers/document_embedded.rb +14 -0
- data/lib/mongoid/orderable/handlers/document_transactional.rb +35 -0
- data/lib/mongoid/orderable/handlers/transaction.rb +71 -0
- data/lib/mongoid/orderable/installer.rb +63 -0
- data/lib/mongoid/orderable/mixins/callbacks.rb +43 -0
- data/lib/mongoid/orderable/mixins/helpers.rb +39 -0
- data/lib/mongoid/orderable/mixins/listable.rb +49 -0
- data/lib/mongoid/orderable/mixins/movable.rb +60 -0
- data/lib/mongoid/orderable/version.rb +7 -0
- data/lib/mongoid_orderable.rb +33 -54
- data/spec/integration/concurrency_spec.rb +232 -0
- data/spec/integration/customized_spec.rb +31 -0
- data/spec/integration/embedded_spec.rb +41 -0
- data/spec/integration/foreign_key_spec.rb +33 -0
- data/spec/integration/inherited_spec.rb +54 -0
- data/spec/integration/multiple_fields_spec.rb +554 -0
- data/spec/integration/multiple_scoped_spec.rb +63 -0
- data/spec/integration/no_indexed_spec.rb +23 -0
- data/spec/integration/scoped_spec.rb +151 -0
- data/spec/integration/simple_spec.rb +184 -0
- data/spec/integration/string_scoped_spec.rb +28 -0
- data/spec/integration/zero_based_spec.rb +161 -0
- data/spec/spec_helper.rb +42 -30
- data/spec/support/models.rb +122 -0
- metadata +75 -41
- data/.gitignore +0 -4
- data/.rspec +0 -2
- data/.rvmrc +0 -1
- data/.travis.yml +0 -14
- data/Gemfile +0 -18
- data/lib/mongoid/orderable/callbacks.rb +0 -75
- data/lib/mongoid/orderable/configuration.rb +0 -60
- data/lib/mongoid/orderable/errors.rb +0 -2
- data/lib/mongoid/orderable/errors/mongoid_orderable_error.rb +0 -14
- data/lib/mongoid/orderable/generator.rb +0 -34
- data/lib/mongoid/orderable/generator/helpers.rb +0 -29
- data/lib/mongoid/orderable/generator/listable.rb +0 -41
- data/lib/mongoid/orderable/generator/movable.rb +0 -62
- data/lib/mongoid/orderable/generator/position.rb +0 -26
- data/lib/mongoid/orderable/generator/scope.rb +0 -17
- data/lib/mongoid/orderable/helpers.rb +0 -50
- data/lib/mongoid/orderable/listable.rb +0 -49
- data/lib/mongoid/orderable/movable.rb +0 -58
- data/lib/mongoid/orderable/orderable_class.rb +0 -51
- data/lib/mongoid_orderable/mongoid/contexts/enumerable.rb +0 -15
- data/lib/mongoid_orderable/mongoid/contexts/mongo.rb +0 -18
- data/lib/mongoid_orderable/mongoid/contextual/memory.rb +0 -15
- data/lib/mongoid_orderable/mongoid/criteria.rb +0 -4
- data/lib/mongoid_orderable/version.rb +0 -3
- data/mongoid_orderable.gemspec +0 -26
- data/spec/mongoid/orderable_spec.rb +0 -1413
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Orderable
|
5
|
+
module Mixins
|
6
|
+
module Helpers
|
7
|
+
def orderable_keys
|
8
|
+
Array(orderable_inherited_class.orderable_configs.try(:keys))
|
9
|
+
end
|
10
|
+
|
11
|
+
def default_orderable_field
|
12
|
+
self.class.orderable_configs.detect {|_c, conf| conf[:default] }.try(:first) || orderable_keys.first
|
13
|
+
end
|
14
|
+
|
15
|
+
def orderable_scope(field = nil)
|
16
|
+
field ||= default_orderable_field
|
17
|
+
|
18
|
+
if embedded?
|
19
|
+
_parent.send(_association.name).send("orderable_#{field}_scope", self)
|
20
|
+
else
|
21
|
+
orderable_inherited_class.send("orderable_#{field}_scope", self)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def orderable_scope_changed?(field)
|
26
|
+
!orderable_scope(field).where(_id: _id).exists?
|
27
|
+
end
|
28
|
+
|
29
|
+
def orderable_bottom(field = nil, in_list = true)
|
30
|
+
field ||= default_orderable_field
|
31
|
+
f = orderable_field(field)
|
32
|
+
max = orderable_scope(field).ne(f => nil).max(f)
|
33
|
+
return orderable_top(field) unless max
|
34
|
+
in_list ? max : max.next
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Orderable
|
5
|
+
module Mixins
|
6
|
+
module Listable
|
7
|
+
def in_list?(field = nil)
|
8
|
+
persisted? && !orderable_position(field).nil?
|
9
|
+
end
|
10
|
+
|
11
|
+
# Returns items above the current document.
|
12
|
+
# Items with a position lower than this document's position.
|
13
|
+
def previous_items(field = nil)
|
14
|
+
field ||= default_orderable_field
|
15
|
+
orderable_scope(field).lt(orderable_field(field) => send(field))
|
16
|
+
end
|
17
|
+
alias prev_items previous_items
|
18
|
+
|
19
|
+
# Returns items below the current document.
|
20
|
+
# Items with a position greater than this document's position.
|
21
|
+
def next_items(field = nil)
|
22
|
+
field ||= default_orderable_field
|
23
|
+
orderable_scope(field).gt(orderable_field(field) => send(field))
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the previous item in the list
|
27
|
+
def previous_item(field = nil)
|
28
|
+
field ||= default_orderable_field
|
29
|
+
orderable_scope(field).where(orderable_field(field) => send(field) - 1).first
|
30
|
+
end
|
31
|
+
alias prev_item previous_item
|
32
|
+
|
33
|
+
# Returns the next item in the list
|
34
|
+
def next_item(field = nil)
|
35
|
+
field ||= default_orderable_field
|
36
|
+
orderable_scope(field).where(orderable_field(field) => send(field) + 1).first
|
37
|
+
end
|
38
|
+
|
39
|
+
def first?(field = nil)
|
40
|
+
in_list?(field) && orderable_position(field) == orderable_top(field)
|
41
|
+
end
|
42
|
+
|
43
|
+
def last?(field = nil)
|
44
|
+
in_list?(field) && orderable_position(field) == orderable_bottom(field)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Orderable
|
5
|
+
module Mixins
|
6
|
+
module Movable
|
7
|
+
def move_to!(target_position, options = {})
|
8
|
+
move_field_to target_position, options
|
9
|
+
save
|
10
|
+
end
|
11
|
+
alias insert_at! move_to!
|
12
|
+
|
13
|
+
def move_to(target_position, options = {})
|
14
|
+
move_field_to target_position, options
|
15
|
+
end
|
16
|
+
alias insert_at move_to
|
17
|
+
|
18
|
+
def move_to=(target_position, options = {})
|
19
|
+
move_field_to target_position, options
|
20
|
+
end
|
21
|
+
alias insert_at= move_to=
|
22
|
+
|
23
|
+
%i[top bottom].each do |symbol|
|
24
|
+
class_eval <<~KLASS, __FILE__, __LINE__ + 1
|
25
|
+
def move_to_#{symbol}(options = {})
|
26
|
+
move_to :#{symbol}, options
|
27
|
+
end
|
28
|
+
|
29
|
+
def move_to_#{symbol}!(options = {})
|
30
|
+
move_to! :#{symbol}, options
|
31
|
+
end
|
32
|
+
KLASS
|
33
|
+
end
|
34
|
+
|
35
|
+
%i[higher lower].each do |symbol|
|
36
|
+
class_eval <<~KLASS, __FILE__, __LINE__ + 1
|
37
|
+
def move_#{symbol}(options = {})
|
38
|
+
move_to :#{symbol}, options
|
39
|
+
end
|
40
|
+
|
41
|
+
def move_#{symbol}!(options = {})
|
42
|
+
move_to! :#{symbol}, options
|
43
|
+
end
|
44
|
+
KLASS
|
45
|
+
end
|
46
|
+
|
47
|
+
protected
|
48
|
+
|
49
|
+
def move_all
|
50
|
+
@move_all || {}
|
51
|
+
end
|
52
|
+
|
53
|
+
def move_field_to(position, options)
|
54
|
+
field = options[:field] || default_orderable_field
|
55
|
+
@move_all = move_all.merge(field => position)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/mongoid_orderable.rb
CHANGED
@@ -1,54 +1,33 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
require '
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
require 'mongoid_orderable/mongoid/criteria'
|
35
|
-
else
|
36
|
-
require 'mongoid_orderable/mongoid/contextual/memory'
|
37
|
-
end
|
38
|
-
|
39
|
-
require 'mongoid/orderable'
|
40
|
-
require 'mongoid/orderable/errors'
|
41
|
-
require 'mongoid/orderable/configuration'
|
42
|
-
require 'mongoid/orderable/helpers'
|
43
|
-
require 'mongoid/orderable/callbacks'
|
44
|
-
require 'mongoid/orderable/listable'
|
45
|
-
require 'mongoid/orderable/movable'
|
46
|
-
|
47
|
-
require 'mongoid/orderable/generator/listable'
|
48
|
-
require 'mongoid/orderable/generator/movable'
|
49
|
-
require 'mongoid/orderable/generator/position'
|
50
|
-
require 'mongoid/orderable/generator/scope'
|
51
|
-
require 'mongoid/orderable/generator/helpers'
|
52
|
-
require 'mongoid/orderable/generator'
|
53
|
-
|
54
|
-
require 'mongoid/orderable/orderable_class'
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
|
5
|
+
I18n.enforce_available_locales = false if I18n.respond_to?(:enforce_available_locales)
|
6
|
+
I18n.load_path << File.join(File.dirname(__FILE__), 'config', 'locales', 'en.yml')
|
7
|
+
|
8
|
+
require 'mongoid'
|
9
|
+
|
10
|
+
require 'mongoid/orderable/version'
|
11
|
+
|
12
|
+
require 'mongoid/orderable'
|
13
|
+
require 'mongoid/orderable/configs/global_config'
|
14
|
+
require 'mongoid/orderable/configs/field_config'
|
15
|
+
require 'mongoid/orderable/errors/invalid_target_position'
|
16
|
+
require 'mongoid/orderable/errors/transaction_failed'
|
17
|
+
require 'mongoid/orderable/mixins/helpers'
|
18
|
+
require 'mongoid/orderable/mixins/callbacks'
|
19
|
+
require 'mongoid/orderable/mixins/listable'
|
20
|
+
require 'mongoid/orderable/mixins/movable'
|
21
|
+
require 'mongoid/orderable/generators/base'
|
22
|
+
require 'mongoid/orderable/generators/listable'
|
23
|
+
require 'mongoid/orderable/generators/lock_collection'
|
24
|
+
require 'mongoid/orderable/generators/movable'
|
25
|
+
require 'mongoid/orderable/generators/position'
|
26
|
+
require 'mongoid/orderable/generators/scope'
|
27
|
+
require 'mongoid/orderable/generators/helpers'
|
28
|
+
require 'mongoid/orderable/handlers/base'
|
29
|
+
require 'mongoid/orderable/handlers/document'
|
30
|
+
require 'mongoid/orderable/handlers/document_embedded'
|
31
|
+
require 'mongoid/orderable/handlers/document_transactional'
|
32
|
+
require 'mongoid/orderable/handlers/transaction'
|
33
|
+
require 'mongoid/orderable/installer'
|
@@ -0,0 +1,232 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'concurrency' do
|
4
|
+
enable_transactions!
|
5
|
+
|
6
|
+
describe 'simple create' do
|
7
|
+
|
8
|
+
it 'should correctly insert at the top' do
|
9
|
+
20.times.map do
|
10
|
+
Thread.new do
|
11
|
+
SimpleOrderable.create!(move_to: :top)
|
12
|
+
end
|
13
|
+
end.each(&:join)
|
14
|
+
|
15
|
+
expect(SimpleOrderable.pluck(:position).sort).to eq((1..20).to_a)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should correctly insert at the bottom' do
|
19
|
+
20.times.map do
|
20
|
+
Thread.new do
|
21
|
+
SimpleOrderable.create!
|
22
|
+
end
|
23
|
+
end.each(&:join)
|
24
|
+
|
25
|
+
expect(SimpleOrderable.pluck(:position).sort).to eq((1..20).to_a)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should correctly insert at a random position' do
|
29
|
+
20.times.map do
|
30
|
+
Thread.new do
|
31
|
+
SimpleOrderable.create!(move_to: (1..10).to_a.sample)
|
32
|
+
end
|
33
|
+
end.each(&:join)
|
34
|
+
|
35
|
+
expect(SimpleOrderable.pluck(:position).sort).to eq((1..20).to_a)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'simple update' do
|
40
|
+
before :each do
|
41
|
+
5.times { SimpleOrderable.create! }
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should correctly move items to top' do
|
45
|
+
20.times.map do
|
46
|
+
Thread.new do
|
47
|
+
record = SimpleOrderable.all.sample
|
48
|
+
record.update_attributes move_to: :top
|
49
|
+
end
|
50
|
+
end.each(&:join)
|
51
|
+
|
52
|
+
expect(SimpleOrderable.pluck(:position).sort).to eq([1, 2, 3, 4, 5])
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should correctly move items to bottom' do
|
56
|
+
20.times.map do
|
57
|
+
Thread.new do
|
58
|
+
record = SimpleOrderable.all.sample
|
59
|
+
record.update_attributes move_to: :bottom
|
60
|
+
end
|
61
|
+
end.each(&:join)
|
62
|
+
|
63
|
+
expect(SimpleOrderable.pluck(:position).sort).to eq([1, 2, 3, 4, 5])
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should correctly move items higher' do
|
67
|
+
20.times.map do
|
68
|
+
Thread.new do
|
69
|
+
record = SimpleOrderable.all.sample
|
70
|
+
record.update_attributes move_to: :higher
|
71
|
+
end
|
72
|
+
end.each(&:join)
|
73
|
+
|
74
|
+
expect(SimpleOrderable.pluck(:position).sort).to eq([1, 2, 3, 4, 5])
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should correctly move items lower' do
|
78
|
+
20.times.map do
|
79
|
+
Thread.new do
|
80
|
+
record = SimpleOrderable.all.sample
|
81
|
+
record.update_attributes move_to: :lower
|
82
|
+
end
|
83
|
+
end.each(&:join)
|
84
|
+
|
85
|
+
expect(SimpleOrderable.pluck(:position).sort).to eq([1, 2, 3, 4, 5])
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'should correctly insert at the top' do
|
89
|
+
20.times.map do
|
90
|
+
Thread.new do
|
91
|
+
SimpleOrderable.create!(move_to: :top)
|
92
|
+
end
|
93
|
+
end.each(&:join)
|
94
|
+
|
95
|
+
expect(SimpleOrderable.pluck(:position).sort).to eq((1..25).to_a)
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should correctly insert at the bottom' do
|
99
|
+
20.times.map do
|
100
|
+
Thread.new do
|
101
|
+
SimpleOrderable.create!
|
102
|
+
end
|
103
|
+
end.each(&:join)
|
104
|
+
|
105
|
+
expect(SimpleOrderable.pluck(:position).sort).to eq((1..25).to_a)
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'should correctly insert at a random position' do
|
109
|
+
20.times.map do
|
110
|
+
Thread.new do
|
111
|
+
SimpleOrderable.create!(move_to: (1..10).to_a.sample)
|
112
|
+
end
|
113
|
+
end.each(&:join)
|
114
|
+
|
115
|
+
expect(SimpleOrderable.pluck(:position).sort).to eq((1..25).to_a)
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'should correctly move items to a random position' do
|
119
|
+
20.times.map do
|
120
|
+
Thread.new do
|
121
|
+
record = SimpleOrderable.all.sample
|
122
|
+
record.update_attributes move_to: (1..5).to_a.sample
|
123
|
+
end
|
124
|
+
end.each(&:join)
|
125
|
+
|
126
|
+
expect(SimpleOrderable.pluck(:position).sort).to eq([1, 2, 3, 4, 5])
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe 'scoped update' do
|
131
|
+
|
132
|
+
before :each do
|
133
|
+
2.times { ScopedOrderable.create! group_id: 1 }
|
134
|
+
3.times { ScopedOrderable.create! group_id: 2 }
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'should correctly move items to top' do
|
138
|
+
20.times.map do
|
139
|
+
Thread.new do
|
140
|
+
record = ScopedOrderable.all.sample
|
141
|
+
record.update_attributes move_to: :top
|
142
|
+
end
|
143
|
+
end.each(&:join)
|
144
|
+
|
145
|
+
expect(ScopedOrderable.pluck(:position).sort).to eq([1, 1, 2, 2, 3])
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'should correctly move items to bottom' do
|
149
|
+
20.times.map do
|
150
|
+
Thread.new do
|
151
|
+
record = ScopedOrderable.all.sample
|
152
|
+
record.update_attributes move_to: :bottom
|
153
|
+
end
|
154
|
+
end.each(&:join)
|
155
|
+
|
156
|
+
expect(ScopedOrderable.pluck(:position).sort).to eq([1, 1, 2, 2, 3])
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'should correctly move items higher' do
|
160
|
+
20.times.map do
|
161
|
+
Thread.new do
|
162
|
+
record = ScopedOrderable.all.sample
|
163
|
+
record.update_attributes move_to: :higher
|
164
|
+
end
|
165
|
+
end.each(&:join)
|
166
|
+
|
167
|
+
expect(ScopedOrderable.pluck(:position).sort).to eq([1, 1, 2, 2, 3])
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'should correctly move items lower' do
|
171
|
+
20.times.map do
|
172
|
+
Thread.new do
|
173
|
+
record = ScopedOrderable.all.sample
|
174
|
+
record.update_attributes move_to: :lower
|
175
|
+
end
|
176
|
+
end.each(&:join)
|
177
|
+
|
178
|
+
expect(ScopedOrderable.pluck(:position).sort).to eq([1, 1, 2, 2, 3])
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'should correctly move items to a random position' do
|
182
|
+
20.times.map do
|
183
|
+
Thread.new do
|
184
|
+
record = ScopedOrderable.all.sample
|
185
|
+
record.update_attributes move_to: (1..5).to_a.sample
|
186
|
+
end
|
187
|
+
end.each(&:join)
|
188
|
+
|
189
|
+
expect(ScopedOrderable.pluck(:position).sort).to eq([1, 1, 2, 2, 3])
|
190
|
+
end
|
191
|
+
|
192
|
+
# This spec fails randomly
|
193
|
+
it 'should correctly move items to a random scope', retry: 5 do
|
194
|
+
20.times.map do
|
195
|
+
Thread.new do
|
196
|
+
record = ScopedOrderable.all.sample
|
197
|
+
group_id = ([1, 2, 3] - [record.group_id]).sample
|
198
|
+
record.update_attributes group_id: group_id
|
199
|
+
end
|
200
|
+
end.each(&:join)
|
201
|
+
|
202
|
+
result = ScopedOrderable.all.to_a.each_with_object({}) do |obj, hash|
|
203
|
+
hash[obj.group_id] ||= []
|
204
|
+
hash[obj.group_id] << obj.position
|
205
|
+
end
|
206
|
+
|
207
|
+
result.values.each do |ary|
|
208
|
+
expect(ary.sort).to eq((1..(ary.size)).to_a)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'should correctly move items to a random position and scope' do
|
213
|
+
20.times.map do
|
214
|
+
Thread.new do
|
215
|
+
record = ScopedOrderable.all.sample
|
216
|
+
group_id = ([1, 2, 3] - [record.group_id]).sample
|
217
|
+
position = (1..5).to_a.sample
|
218
|
+
record.update_attributes group_id: group_id, move_to: position
|
219
|
+
end
|
220
|
+
end.each(&:join)
|
221
|
+
|
222
|
+
result = ScopedOrderable.all.to_a.each_with_object({}) do |obj, hash|
|
223
|
+
hash[obj.group_id] ||= []
|
224
|
+
hash[obj.group_id] << obj.position
|
225
|
+
end
|
226
|
+
|
227
|
+
result.values.each do |ary|
|
228
|
+
expect(ary.sort).to eq((1..(ary.size)).to_a)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|