ranked-model 0.4.4 → 0.4.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,351 +1,356 @@
1
- module RankedModel
2
-
3
- class InvalidScope < StandardError; end
4
- class InvalidField < StandardError; end
5
-
6
- class Ranker
7
- attr_accessor :name, :column, :scope, :with_same, :class_name, :unless
8
-
9
- def initialize name, options={}
10
- self.name = name.to_sym
11
- self.column = options[:column] || name
12
- self.class_name = options[:class_name]
13
-
14
- [ :scope, :with_same, :unless ].each do |key|
15
- self.send "#{key}=", options[key]
16
- end
17
- end
18
-
19
- def with instance
20
- Mapper.new self, instance
21
- end
22
-
23
- class Mapper
24
- attr_accessor :ranker, :instance
25
-
26
- def initialize ranker, instance
27
- self.ranker = ranker
28
- self.instance = instance
29
-
30
- validate_ranker_for_instance!
31
- end
32
-
33
- def validate_ranker_for_instance!
34
- if ranker.scope && !instance_class.respond_to?(ranker.scope)
35
- raise RankedModel::InvalidScope, %Q{No scope called "#{ranker.scope}" found in model}
36
- end
37
-
38
- if ranker.with_same
39
- if (case ranker.with_same
40
- when Symbol
41
- !instance.respond_to?(ranker.with_same)
42
- when Array
43
- array_element = ranker.with_same.detect {|attr| !instance.respond_to?(attr) }
44
- else
45
- false
46
- end)
47
- raise RankedModel::InvalidField, %Q{No field called "#{array_element || ranker.with_same}" found in model}
48
- end
49
- end
50
- end
51
-
52
- def handle_ranking
53
- case ranker.unless
54
- when Proc
55
- return if ranker.unless.call(instance)
56
- when Symbol
57
- return if instance.send(ranker.unless)
58
- end
59
-
60
- update_index_from_position
61
- assure_unique_position
62
- end
63
-
64
- def update_rank! value
65
- # Bypass callbacks
66
- #
67
- instance_class.
68
- where(instance_class.primary_key => instance.id).
69
- update_all(ranker.column => value)
70
- end
71
-
72
- def position
73
- instance.send "#{ranker.name}_position"
74
- end
75
-
76
- def relative_rank
77
- finder.where("#{ranker.column} < #{rank}").count(:all)
78
- end
79
-
80
- def rank
81
- instance.send "#{ranker.column}"
82
- end
83
-
84
- def current_at_position _pos
85
- if (ordered_instance = finder.offset(_pos).first)
86
- RankedModel::Ranker::Mapper.new ranker, ordered_instance
87
- end
88
- end
89
-
90
- def has_rank?
91
- !rank.nil?
92
- end
93
-
94
- private
95
-
96
- def reset_cache
97
- @finder, @current_order, @current_first, @current_last = nil
98
- end
99
-
100
- def instance_class
101
- ranker.class_name.nil? ? instance.class : ranker.class_name.constantize
102
- end
103
-
104
- def position_at value
105
- instance.send "#{ranker.name}_position=", value
106
- update_index_from_position
107
- end
108
-
109
- def rank_at value
110
- instance.send "#{ranker.column}=", value
111
- end
112
-
113
- def rank_changed?
114
- instance.send "#{ranker.column}_changed?"
115
- end
116
-
117
- def new_record?
118
- instance.new_record?
119
- end
120
-
121
- def update_index_from_position
122
- case position
123
- when :first, 'first'
124
- if current_first && current_first.rank
125
- rank_at_average current_first.rank, RankedModel::MIN_RANK_VALUE
126
- else
127
- position_at :middle
128
- end
129
- when :last, 'last'
130
- if current_last && current_last.rank
131
- rank_at_average current_last.rank, RankedModel::MAX_RANK_VALUE
132
- else
133
- position_at :middle
134
- end
135
- when :middle, 'middle'
136
- rank_at_average RankedModel::MIN_RANK_VALUE, RankedModel::MAX_RANK_VALUE
137
- when :down, 'down'
138
- neighbors = find_next_two(rank)
139
- if neighbors[:lower]
140
- min = neighbors[:lower].rank
141
- max = neighbors[:upper] ? neighbors[:upper].rank : RankedModel::MAX_RANK_VALUE
142
- rank_at_average min, max
143
- end
144
- when :up, 'up'
145
- neighbors = find_previous_two(rank)
146
- if neighbors[:upper]
147
- max = neighbors[:upper].rank
148
- min = neighbors[:lower] ? neighbors[:lower].rank : RankedModel::MIN_RANK_VALUE
149
- rank_at_average min, max
150
- end
151
- when String
152
- position_at position.to_i
153
- when 0
154
- position_at :first
155
- when Integer
156
- neighbors = neighbors_at_position(position)
157
- min = ((neighbors[:lower] && neighbors[:lower].has_rank?) ? neighbors[:lower].rank : RankedModel::MIN_RANK_VALUE)
158
- max = ((neighbors[:upper] && neighbors[:upper].has_rank?) ? neighbors[:upper].rank : RankedModel::MAX_RANK_VALUE)
159
- rank_at_average min, max
160
- when NilClass
161
- if !rank
162
- position_at :last
163
- end
164
- end
165
- end
166
-
167
- def rank_at_average(min, max)
168
- if (max - min).between?(-1, 1) # No room at the inn...
169
- rebalance_ranks
170
- position_at position
171
- else
172
- rank_at( ( ( max - min ).to_f / 2 ).ceil + min )
173
- end
174
- end
175
-
176
- def assure_unique_position
177
- if ( new_record? || rank_changed? )
178
- if (rank > RankedModel::MAX_RANK_VALUE) || current_at_rank(rank)
179
- rearrange_ranks
180
- end
181
- end
182
- end
183
-
184
- def rearrange_ranks
185
- _scope = finder
186
- escaped_column = ActiveRecord::Base.connection.quote_column_name ranker.column
187
- # If there is room at the bottom of the list and we're added to the very top of the list...
188
- if current_first.rank && current_first.rank > RankedModel::MIN_RANK_VALUE && rank == RankedModel::MAX_RANK_VALUE
189
- # ...then move everyone else down 1 to make room for us at the end
190
- _scope.
191
- where( instance_class.arel_table[ranker.column].lteq(rank) ).
192
- update_all( "#{escaped_column} = #{escaped_column} - 1" )
193
- # If there is room at the top of the list and we're added below the last value in the list...
194
- elsif current_last.rank && current_last.rank < (RankedModel::MAX_RANK_VALUE - 1) && rank < current_last.rank
195
- # ...then move everyone else at or above our desired rank up 1 to make room for us
196
- _scope.
197
- where( instance_class.arel_table[ranker.column].gteq(rank) ).
198
- update_all( "#{escaped_column} = #{escaped_column} + 1" )
199
- # If there is room at the bottom of the list and we're added above the lowest value in the list...
200
- elsif current_first.rank && current_first.rank > RankedModel::MIN_RANK_VALUE && rank > current_first.rank
201
- # ...then move everyone else below us down 1 and change our rank down 1 to avoid the collission
202
- _scope.
203
- where( instance_class.arel_table[ranker.column].lt(rank) ).
204
- update_all( "#{escaped_column} = #{escaped_column} - 1" )
205
- rank_at( rank - 1 )
206
- else
207
- rebalance_ranks
208
- end
209
- end
210
-
211
- def rebalance_ranks
212
- if rank && instance.persisted?
213
- origin = current_order.index { |item| item.instance.id == instance.id }
214
- destination = current_order.index { |item| rank <= item.rank }
215
- destination -= 1 if origin < destination
216
-
217
- current_order.insert destination, current_order.delete_at(origin)
218
- end
219
-
220
- gaps = current_order.size + 1
221
- range = (RankedModel::MAX_RANK_VALUE - RankedModel::MIN_RANK_VALUE).to_f
222
- gap_size = (range / gaps).ceil
223
-
224
- current_order.each.with_index(1) do |item, position|
225
- new_rank = (gap_size * position) + RankedModel::MIN_RANK_VALUE
226
-
227
- if item.instance.id == instance.id
228
- rank_at new_rank
229
- else
230
- item.update_rank! new_rank
231
- end
232
- end
233
-
234
- reset_cache
235
- end
236
-
237
- def finder(order = :asc)
238
- @finder ||= {}
239
- @finder[order] ||= begin
240
- _finder = instance_class
241
- columns = [instance_class.primary_key.to_sym, ranker.column]
242
-
243
- if ranker.scope
244
- _finder = _finder.send ranker.scope
245
- end
246
-
247
- case ranker.with_same
248
- when Symbol
249
- columns << ranker.with_same
250
- _finder = _finder.where \
251
- ranker.with_same => instance.attributes[ranker.with_same.to_s]
252
- when Array
253
- ranker.with_same.each do |column|
254
- columns << column
255
- _finder = _finder.where column => instance.attributes[column.to_s]
256
- end
257
- end
258
-
259
- unless new_record?
260
- _finder = _finder.where.not instance_class.primary_key.to_sym => instance.id
261
- end
262
-
263
- _finder.reorder(ranker.column.to_sym => order).select(columns)
264
- end
265
- end
266
-
267
- def current_order
268
- @current_order ||= begin
269
- finder.unscope(where: instance_class.primary_key.to_sym).collect { |ordered_instance|
270
- RankedModel::Ranker::Mapper.new ranker, ordered_instance
271
- }
272
- end
273
- end
274
-
275
- def current_first
276
- @current_first ||= begin
277
- if (ordered_instance = finder.first)
278
- RankedModel::Ranker::Mapper.new ranker, ordered_instance
279
- end
280
- end
281
- end
282
-
283
- def current_last
284
- @current_last ||= begin
285
- if (ordered_instance = finder.
286
- reverse.
287
- first)
288
- RankedModel::Ranker::Mapper.new ranker, ordered_instance
289
- end
290
- end
291
- end
292
-
293
- def current_at_rank _rank
294
- if (ordered_instance = finder.
295
- except( :order ).
296
- where( ranker.column => _rank ).
297
- first)
298
- RankedModel::Ranker::Mapper.new ranker, ordered_instance
299
- end
300
- end
301
-
302
- def neighbors_at_position _pos
303
- if _pos > 0
304
- if (ordered_instances = finder.offset(_pos-1).limit(2).to_a)
305
- if ordered_instances[1]
306
- { :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ),
307
- :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[1] ) }
308
- elsif ordered_instances[0]
309
- { :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ) }
310
- else
311
- { :lower => current_last }
312
- end
313
- end
314
- else
315
- if (ordered_instance = finder.first)
316
- { :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instance ) }
317
- else
318
- {}
319
- end
320
- end
321
- end
322
-
323
- def find_next_two _rank
324
- ordered_instances = finder.where(instance_class.arel_table[ranker.column].gt _rank).limit(2)
325
- if ordered_instances[1]
326
- { :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ),
327
- :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[1] ) }
328
- elsif ordered_instances[0]
329
- { :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ) }
330
- else
331
- {}
332
- end
333
- end
334
-
335
- def find_previous_two _rank
336
- ordered_instances = finder(:desc).where(instance_class.arel_table[ranker.column].lt _rank).limit(2)
337
- if ordered_instances[1]
338
- { :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ),
339
- :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[1] ) }
340
- elsif ordered_instances[0]
341
- { :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ) }
342
- else
343
- {}
344
- end
345
- end
346
-
347
- end
348
-
349
- end
350
-
351
- end
1
+ module RankedModel
2
+
3
+ class InvalidScope < StandardError; end
4
+ class InvalidField < StandardError; end
5
+
6
+ class Ranker
7
+ attr_accessor :name, :column, :scope, :with_same, :class_name, :unless
8
+
9
+ def initialize name, options={}
10
+ self.name = name.to_sym
11
+ self.column = options[:column] || name
12
+ self.class_name = options[:class_name]
13
+
14
+ [ :scope, :with_same, :unless ].each do |key|
15
+ self.send "#{key}=", options[key]
16
+ end
17
+ end
18
+
19
+ def with instance
20
+ Mapper.new self, instance
21
+ end
22
+
23
+ class Mapper
24
+ attr_accessor :ranker, :instance
25
+
26
+ def initialize ranker, instance
27
+ self.ranker = ranker
28
+ self.instance = instance
29
+
30
+ validate_ranker_for_instance!
31
+ end
32
+
33
+ def validate_ranker_for_instance!
34
+ if ranker.scope && !instance_class.respond_to?(ranker.scope)
35
+ raise RankedModel::InvalidScope, %Q{No scope called "#{ranker.scope}" found in model}
36
+ end
37
+
38
+ if ranker.with_same
39
+ if (case ranker.with_same
40
+ when Symbol
41
+ !instance.respond_to?(ranker.with_same)
42
+ when Array
43
+ array_element = ranker.with_same.detect {|attr| !instance.respond_to?(attr) }
44
+ else
45
+ false
46
+ end)
47
+ raise RankedModel::InvalidField, %Q{No field called "#{array_element || ranker.with_same}" found in model}
48
+ end
49
+ end
50
+ end
51
+
52
+ def handle_ranking
53
+ case ranker.unless
54
+ when Proc
55
+ return if ranker.unless.call(instance)
56
+ when Symbol
57
+ return if instance.send(ranker.unless)
58
+ end
59
+
60
+ update_index_from_position
61
+ assure_unique_position
62
+ end
63
+
64
+ def update_rank! value
65
+ # Bypass callbacks
66
+ #
67
+ instance_class.
68
+ where(instance_class.primary_key => instance.id).
69
+ update_all(ranker.column => value)
70
+ end
71
+
72
+ def reset_ranks!
73
+ finder.update_all(ranker.column => nil)
74
+ end
75
+
76
+ def position
77
+ instance.send "#{ranker.name}_position"
78
+ end
79
+
80
+ def relative_rank
81
+ escaped_column = instance_class.connection.quote_column_name ranker.column
82
+
83
+ finder.where("#{escaped_column} < #{rank}").count(:all)
84
+ end
85
+
86
+ def rank
87
+ instance.send "#{ranker.column}"
88
+ end
89
+
90
+ def current_at_position _pos
91
+ if (ordered_instance = finder.offset(_pos).first)
92
+ RankedModel::Ranker::Mapper.new ranker, ordered_instance
93
+ end
94
+ end
95
+
96
+ def has_rank?
97
+ !rank.nil?
98
+ end
99
+
100
+ private
101
+
102
+ def reset_cache
103
+ @finder, @current_order, @current_first, @current_last = nil
104
+ end
105
+
106
+ def instance_class
107
+ ranker.class_name.nil? ? instance.class : ranker.class_name.constantize
108
+ end
109
+
110
+ def position_at value
111
+ instance.send "#{ranker.name}_position=", value
112
+ update_index_from_position
113
+ end
114
+
115
+ def rank_at value
116
+ instance.send "#{ranker.column}=", value
117
+ end
118
+
119
+ def rank_changed?
120
+ instance.send "#{ranker.column}_changed?"
121
+ end
122
+
123
+ def new_record?
124
+ instance.new_record?
125
+ end
126
+
127
+ def update_index_from_position
128
+ case position
129
+ when :first, 'first'
130
+ if current_first && current_first.rank
131
+ rank_at_average current_first.rank, RankedModel::MIN_RANK_VALUE
132
+ else
133
+ position_at :middle
134
+ end
135
+ when :last, 'last'
136
+ if current_last && current_last.rank
137
+ rank_at_average current_last.rank, RankedModel::MAX_RANK_VALUE
138
+ else
139
+ position_at :middle
140
+ end
141
+ when :middle, 'middle'
142
+ rank_at_average RankedModel::MIN_RANK_VALUE, RankedModel::MAX_RANK_VALUE
143
+ when :down, 'down'
144
+ neighbors = find_next_two(rank)
145
+ if neighbors[:lower]
146
+ min = neighbors[:lower].rank
147
+ max = neighbors[:upper] ? neighbors[:upper].rank : RankedModel::MAX_RANK_VALUE
148
+ rank_at_average min, max
149
+ end
150
+ when :up, 'up'
151
+ neighbors = find_previous_two(rank)
152
+ if neighbors[:upper]
153
+ max = neighbors[:upper].rank
154
+ min = neighbors[:lower] ? neighbors[:lower].rank : RankedModel::MIN_RANK_VALUE
155
+ rank_at_average min, max
156
+ end
157
+ when String
158
+ position_at position.to_i
159
+ when 0
160
+ position_at :first
161
+ when Integer
162
+ neighbors = neighbors_at_position(position)
163
+ min = ((neighbors[:lower] && neighbors[:lower].has_rank?) ? neighbors[:lower].rank : RankedModel::MIN_RANK_VALUE)
164
+ max = ((neighbors[:upper] && neighbors[:upper].has_rank?) ? neighbors[:upper].rank : RankedModel::MAX_RANK_VALUE)
165
+ rank_at_average min, max
166
+ when NilClass
167
+ if !rank
168
+ position_at :last
169
+ end
170
+ end
171
+ end
172
+
173
+ def rank_at_average(min, max)
174
+ if (max - min).between?(-1, 1) # No room at the inn...
175
+ rebalance_ranks
176
+ position_at position
177
+ else
178
+ rank_at( ( ( max - min ).to_f / 2 ).ceil + min )
179
+ end
180
+ end
181
+
182
+ def assure_unique_position
183
+ if ( new_record? || rank_changed? )
184
+ if (rank > RankedModel::MAX_RANK_VALUE) || rank_taken?
185
+ rearrange_ranks
186
+ end
187
+ end
188
+ end
189
+
190
+ def rearrange_ranks
191
+ _scope = finder
192
+ escaped_column = instance_class.connection.quote_column_name ranker.column
193
+ # If there is room at the bottom of the list and we're added to the very top of the list...
194
+ if current_first.rank && current_first.rank > RankedModel::MIN_RANK_VALUE && rank == RankedModel::MAX_RANK_VALUE
195
+ # ...then move everyone else down 1 to make room for us at the end
196
+ _scope.
197
+ where( instance_class.arel_table[ranker.column].lteq(rank) ).
198
+ update_all( "#{escaped_column} = #{escaped_column} - 1" )
199
+ # If there is room at the top of the list and we're added below the last value in the list...
200
+ elsif current_last.rank && current_last.rank < (RankedModel::MAX_RANK_VALUE - 1) && rank < current_last.rank
201
+ # ...then move everyone else at or above our desired rank up 1 to make room for us
202
+ _scope.
203
+ where( instance_class.arel_table[ranker.column].gteq(rank) ).
204
+ update_all( "#{escaped_column} = #{escaped_column} + 1" )
205
+ # If there is room at the bottom of the list and we're added above the lowest value in the list...
206
+ elsif current_first.rank && current_first.rank > RankedModel::MIN_RANK_VALUE && rank > current_first.rank
207
+ # ...then move everyone else below us down 1 and change our rank down 1 to avoid the collission
208
+ _scope.
209
+ where( instance_class.arel_table[ranker.column].lt(rank) ).
210
+ update_all( "#{escaped_column} = #{escaped_column} - 1" )
211
+ rank_at( rank - 1 )
212
+ else
213
+ rebalance_ranks
214
+ end
215
+ end
216
+
217
+ def rebalance_ranks
218
+ ActiveRecord::Base.transaction do
219
+ if rank && instance.persisted?
220
+ origin = current_order.index { |item| item.instance.id == instance.id }
221
+ if origin
222
+ destination = current_order.index { |item| rank <= item.rank }
223
+ destination -= 1 if origin < destination
224
+
225
+ current_order.insert destination, current_order.delete_at(origin)
226
+ end
227
+ end
228
+
229
+ gaps = current_order.size + 1
230
+ range = (RankedModel::MAX_RANK_VALUE - RankedModel::MIN_RANK_VALUE).to_f
231
+ gap_size = (range / gaps).ceil
232
+
233
+ reset_ranks!
234
+
235
+ current_order.each.with_index(1) do |item, position|
236
+ new_rank = (gap_size * position) + RankedModel::MIN_RANK_VALUE
237
+
238
+ if item.instance.id == instance.id
239
+ rank_at new_rank
240
+ else
241
+ item.update_rank! new_rank
242
+ end
243
+ end
244
+
245
+ reset_cache
246
+ end
247
+ end
248
+
249
+ def finder(order = :asc)
250
+ @finder ||= {}
251
+ @finder[order] ||= begin
252
+ _finder = instance_class
253
+ columns = [instance_class.primary_key.to_sym, ranker.column]
254
+
255
+ if ranker.scope
256
+ _finder = _finder.send ranker.scope
257
+ end
258
+
259
+ case ranker.with_same
260
+ when Symbol
261
+ columns << ranker.with_same
262
+ _finder = _finder.where \
263
+ ranker.with_same => instance.attributes[ranker.with_same.to_s]
264
+ when Array
265
+ ranker.with_same.each do |column|
266
+ columns << column
267
+ _finder = _finder.where column => instance.attributes[column.to_s]
268
+ end
269
+ end
270
+
271
+ unless new_record?
272
+ _finder = _finder.where.not instance_class.primary_key.to_sym => instance.id
273
+ end
274
+
275
+ _finder.reorder(ranker.column.to_sym => order).select(columns)
276
+ end
277
+ end
278
+
279
+ def current_order
280
+ @current_order ||= begin
281
+ finder.unscope(where: instance_class.primary_key.to_sym).collect { |ordered_instance|
282
+ RankedModel::Ranker::Mapper.new ranker, ordered_instance
283
+ }
284
+ end
285
+ end
286
+
287
+ def current_first
288
+ @current_first ||= begin
289
+ if (ordered_instance = finder.first)
290
+ RankedModel::Ranker::Mapper.new ranker, ordered_instance
291
+ end
292
+ end
293
+ end
294
+
295
+ def current_last
296
+ @current_last ||= begin
297
+ if (ordered_instance = finder.last)
298
+ RankedModel::Ranker::Mapper.new ranker, ordered_instance
299
+ end
300
+ end
301
+ end
302
+
303
+ def rank_taken?
304
+ finder.except(:order).where(ranker.column => rank).exists?
305
+ end
306
+
307
+ def neighbors_at_position _pos
308
+ if _pos > 0
309
+ if (ordered_instances = finder.offset(_pos-1).limit(2).to_a)
310
+ if ordered_instances[1]
311
+ { :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ),
312
+ :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[1] ) }
313
+ elsif ordered_instances[0]
314
+ { :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ) }
315
+ else
316
+ { :lower => current_last }
317
+ end
318
+ end
319
+ else
320
+ if (ordered_instance = finder.first)
321
+ { :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instance ) }
322
+ else
323
+ {}
324
+ end
325
+ end
326
+ end
327
+
328
+ def find_next_two _rank
329
+ ordered_instances = finder.where(instance_class.arel_table[ranker.column].gt _rank).limit(2)
330
+ if ordered_instances[1]
331
+ { :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ),
332
+ :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[1] ) }
333
+ elsif ordered_instances[0]
334
+ { :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ) }
335
+ else
336
+ {}
337
+ end
338
+ end
339
+
340
+ def find_previous_two _rank
341
+ ordered_instances = finder(:desc).where(instance_class.arel_table[ranker.column].lt _rank).limit(2)
342
+ if ordered_instances[1]
343
+ { :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ),
344
+ :lower => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[1] ) }
345
+ elsif ordered_instances[0]
346
+ { :upper => RankedModel::Ranker::Mapper.new( ranker, ordered_instances[0] ) }
347
+ else
348
+ {}
349
+ end
350
+ end
351
+
352
+ end
353
+
354
+ end
355
+
356
+ end