mongoid 7.1.1 → 7.1.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/lib/mongoid/association/embedded/embeds_many.rb +2 -1
- data/lib/mongoid/association/embedded/embeds_one.rb +2 -1
- data/lib/mongoid/association/proxy.rb +1 -1
- data/lib/mongoid/atomic.rb +13 -3
- data/lib/mongoid/criteria.rb +7 -1
- data/lib/mongoid/criteria/modifiable.rb +2 -1
- data/lib/mongoid/criteria/queryable/extensions/numeric.rb +1 -1
- data/lib/mongoid/criteria/queryable/extensions/regexp.rb +3 -3
- data/lib/mongoid/criteria/queryable/mergeable.rb +75 -8
- data/lib/mongoid/criteria/queryable/selectable.rb +28 -8
- data/lib/mongoid/extensions/hash.rb +4 -2
- data/lib/mongoid/extensions/regexp.rb +1 -1
- data/lib/mongoid/fields.rb +2 -1
- data/lib/mongoid/matchable/regexp.rb +2 -2
- data/lib/mongoid/persistable/pushable.rb +4 -1
- data/lib/mongoid/persistence_context.rb +6 -6
- data/lib/mongoid/query_cache.rb +2 -1
- data/lib/mongoid/validatable/uniqueness.rb +1 -1
- data/lib/mongoid/version.rb +1 -1
- data/lib/rails/generators/mongoid/model/templates/model.rb.tt +1 -1
- data/spec/integration/app_spec.rb +192 -0
- data/spec/integration/associations/embedded_spec.rb +54 -0
- data/spec/integration/criteria/logical_spec.rb +13 -0
- data/spec/lite_spec_helper.rb +11 -4
- data/spec/mongoid/association/embedded/embeds_many_models.rb +19 -0
- data/spec/mongoid/association/embedded/embeds_many_spec.rb +10 -0
- data/spec/mongoid/association/embedded/embeds_one_spec.rb +0 -2
- data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +2 -1
- data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +2 -1
- data/spec/mongoid/clients/options_spec.rb +2 -2
- data/spec/mongoid/clients/sessions_spec.rb +8 -4
- data/spec/mongoid/clients/transactions_spec.rb +20 -8
- data/spec/mongoid/clients_spec.rb +2 -2
- data/spec/mongoid/contextual/atomic_spec.rb +22 -11
- data/spec/mongoid/contextual/map_reduce_spec.rb +20 -5
- data/spec/mongoid/contextual/mongo_spec.rb +76 -53
- data/spec/mongoid/criteria/queryable/extensions/regexp_spec.rb +7 -7
- data/spec/mongoid/criteria/queryable/extensions/string_spec.rb +1 -1
- data/spec/mongoid/criteria/queryable/mergeable_spec.rb +45 -12
- data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +480 -198
- data/spec/mongoid/criteria_spec.rb +4 -2
- data/spec/mongoid/document_persistence_context_spec.rb +33 -0
- data/spec/mongoid/indexable_spec.rb +6 -4
- data/spec/mongoid/matchable/default_spec.rb +1 -1
- data/spec/mongoid/matchable/regexp_spec.rb +2 -2
- data/spec/mongoid/matchable_spec.rb +2 -2
- data/spec/mongoid/query_cache_spec.rb +2 -1
- data/spec/mongoid/relations/proxy_spec.rb +1 -1
- data/spec/mongoid/scopable_spec.rb +2 -1
- data/spec/mongoid/shardable_models.rb +1 -1
- data/spec/mongoid/shardable_spec.rb +2 -2
- data/spec/mongoid/tasks/database_rake_spec.rb +13 -13
- data/spec/mongoid/tasks/database_spec.rb +1 -1
- data/spec/spec_helper.rb +0 -31
- data/spec/support/child_process_helper.rb +76 -0
- data/spec/support/cluster_config.rb +3 -3
- data/spec/support/constraints.rb +26 -10
- data/spec/support/spec_config.rb +12 -4
- metadata +8 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fd29f8f1d696ba937654fcd72eb3669ac9ba501465828b7eba0f7b707e606f5b
|
4
|
+
data.tar.gz: c9f4a8dbb8a105a7e596019760d100913ddd008972b34f7595674ad7c49d4159
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e00e8d47e7af2fec23380035bcdbd0badc438552aed52aa0f462e246274e7f77220190d99bb6a83a1a90b8e0a41d19103c94fa454382635a2c193d52260c322
|
7
|
+
data.tar.gz: 709300f2570059e460cd341cbaffe7da6328df142825bb726b65cbc377d6ba5129cf7fc973a40fc2de10a50307f49ac0f71db98b0edbb8a65c9cb33910e9d66f
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
@@ -200,7 +200,8 @@ module Mongoid
|
|
200
200
|
def determine_inverses(other)
|
201
201
|
matches = relation_class.relations.values.select do |rel|
|
202
202
|
relation_complements.include?(rel.class) &&
|
203
|
-
|
203
|
+
# https://jira.mongodb.org/browse/MONGOID-4882
|
204
|
+
rel.relation_class_name.sub(/\A::/, '') == inverse_class_name
|
204
205
|
end
|
205
206
|
if matches.size > 1
|
206
207
|
raise Errors::AmbiguousRelationship.new(relation_class, @owner_class, name, matches)
|
@@ -162,7 +162,8 @@ module Mongoid
|
|
162
162
|
def determine_inverses(other)
|
163
163
|
matches = relation_class.relations.values.select do |rel|
|
164
164
|
relation_complements.include?(rel.class) &&
|
165
|
-
|
165
|
+
# https://jira.mongodb.org/browse/MONGOID-4882
|
166
|
+
rel.relation_class_name.sub(/\A::/, '') == inverse_class_name
|
166
167
|
|
167
168
|
end
|
168
169
|
if matches.size > 1
|
@@ -16,7 +16,7 @@ module Mongoid
|
|
16
16
|
# We undefine most methods to get them sent through to the target.
|
17
17
|
instance_methods.each do |method|
|
18
18
|
undef_method(method) unless
|
19
|
-
method =~
|
19
|
+
method =~ /\A(__.*|send|object_id|equal\?|respond_to\?|tap|public_send|extend_proxy|extend_proxies)\z/
|
20
20
|
end
|
21
21
|
|
22
22
|
include Threaded::Lifecycle
|
data/lib/mongoid/atomic.rb
CHANGED
@@ -38,7 +38,9 @@ module Mongoid
|
|
38
38
|
# @since 2.2.0
|
39
39
|
def add_atomic_pull(document)
|
40
40
|
document.flagged_for_destroy = true
|
41
|
-
|
41
|
+
key = document.association_name.to_s
|
42
|
+
delayed_atomic_pulls[key] ||= []
|
43
|
+
delayed_atomic_pulls[key] << document
|
42
44
|
end
|
43
45
|
|
44
46
|
# Add an atomic unset for the document.
|
@@ -53,7 +55,9 @@ module Mongoid
|
|
53
55
|
# @since 3.0.0
|
54
56
|
def add_atomic_unset(document)
|
55
57
|
document.flagged_for_destroy = true
|
56
|
-
|
58
|
+
key = document.association_name.to_s
|
59
|
+
delayed_atomic_unsets[key] ||= []
|
60
|
+
delayed_atomic_unsets[key] << document
|
57
61
|
end
|
58
62
|
|
59
63
|
# Returns path of the attribute for modification
|
@@ -191,7 +195,13 @@ module Mongoid
|
|
191
195
|
#
|
192
196
|
# @since 2.1.0
|
193
197
|
def atomic_paths
|
194
|
-
@atomic_paths ||=
|
198
|
+
@atomic_paths ||= begin
|
199
|
+
if _association
|
200
|
+
_association.path(self)
|
201
|
+
else
|
202
|
+
Atomic::Paths::Root.new(self)
|
203
|
+
end
|
204
|
+
end
|
195
205
|
end
|
196
206
|
|
197
207
|
# Get all the attributes that need to be pulled.
|
data/lib/mongoid/criteria.rb
CHANGED
@@ -450,7 +450,13 @@ module Mongoid
|
|
450
450
|
#
|
451
451
|
# @since 3.1.0
|
452
452
|
def for_js(javascript, scope = {})
|
453
|
-
|
453
|
+
code = if scope.empty?
|
454
|
+
# CodeWithScope is not supported for $where as of MongoDB 4.4
|
455
|
+
BSON::Code.new(javascript)
|
456
|
+
else
|
457
|
+
BSON::CodeWithScope.new(javascript, scope)
|
458
|
+
end
|
459
|
+
js_query(code)
|
454
460
|
end
|
455
461
|
|
456
462
|
private
|
@@ -60,7 +60,7 @@ module Mongoid
|
|
60
60
|
#
|
61
61
|
# @since 1.0.0
|
62
62
|
def __numeric__(object)
|
63
|
-
object.to_s =~ /(
|
63
|
+
object.to_s =~ /(\A[-+]?[0-9]+\z)|(\.0+\z)|(\.\z)/ ? object.to_i : Float(object)
|
64
64
|
end
|
65
65
|
|
66
66
|
# Evolve the object to an integer.
|
@@ -12,7 +12,7 @@ module Mongoid
|
|
12
12
|
# Is the object a regexp?
|
13
13
|
#
|
14
14
|
# @example Is the object a regex?
|
15
|
-
#
|
15
|
+
# /\A[123]/.regexp?
|
16
16
|
#
|
17
17
|
# @return [ true ] Always true.
|
18
18
|
#
|
@@ -24,7 +24,7 @@ module Mongoid
|
|
24
24
|
# Evolve the object into a regex.
|
25
25
|
#
|
26
26
|
# @example Evolve the object to a regex.
|
27
|
-
# Regexp.evolve("
|
27
|
+
# Regexp.evolve("\A[123]")
|
28
28
|
#
|
29
29
|
# @param [ Regexp, String ] object The object to evolve.
|
30
30
|
#
|
@@ -55,7 +55,7 @@ module Mongoid
|
|
55
55
|
# Evolve the object into a raw bson regex.
|
56
56
|
#
|
57
57
|
# @example Evolve the object to a regex.
|
58
|
-
# BSON::Regexp::Raw.evolve("
|
58
|
+
# BSON::Regexp::Raw.evolve("\\A[123]")
|
59
59
|
#
|
60
60
|
# @param [ BSON::Regexp::Raw, String ] object The object to evolve.
|
61
61
|
#
|
@@ -163,7 +163,7 @@ module Mongoid
|
|
163
163
|
if expr.is_a?(Selectable)
|
164
164
|
expr = expr.selector
|
165
165
|
end
|
166
|
-
normalized =
|
166
|
+
normalized = _mongoid_expand_keys(expr)
|
167
167
|
sel.store(operator, result_criteria.push(normalized))
|
168
168
|
end
|
169
169
|
end
|
@@ -190,9 +190,9 @@ module Mongoid
|
|
190
190
|
sel = query.selector
|
191
191
|
_mongoid_flatten_arrays(criteria).each do |criterion|
|
192
192
|
if criterion.is_a?(Selectable)
|
193
|
-
expr =
|
193
|
+
expr = _mongoid_expand_keys(criterion.selector)
|
194
194
|
else
|
195
|
-
expr = criterion
|
195
|
+
expr = _mongoid_expand_keys(criterion)
|
196
196
|
end
|
197
197
|
if sel.empty?
|
198
198
|
sel.store(operator, [expr])
|
@@ -212,7 +212,7 @@ module Mongoid
|
|
212
212
|
# explicitly only expands Array objects and Array subclasses.
|
213
213
|
private def _mongoid_flatten_arrays(array)
|
214
214
|
out = []
|
215
|
-
pending = array
|
215
|
+
pending = array.dup
|
216
216
|
until pending.empty?
|
217
217
|
item = pending.shift
|
218
218
|
if item.nil?
|
@@ -226,11 +226,78 @@ module Mongoid
|
|
226
226
|
out
|
227
227
|
end
|
228
228
|
|
229
|
-
#
|
230
|
-
|
231
|
-
|
232
|
-
|
229
|
+
# Takes a criteria hash and expands Key objects into hashes containing
|
230
|
+
# MQL corresponding to said key objects.
|
231
|
+
#
|
232
|
+
# Ruby does not permit multiple symbol operators. For example,
|
233
|
+
# {:foo.gt => 1, :foo.gt => 2} is collapsed to {:foo.gt => 2} by the
|
234
|
+
# language. Therefore this method never has to deal with multiple
|
235
|
+
# identical operators.
|
236
|
+
#
|
237
|
+
# Similarly, this method should never need to expand a literal value
|
238
|
+
# and an operator at the same time.
|
239
|
+
#
|
240
|
+
# @param [ Hash ] Criteria including Key instances.
|
241
|
+
#
|
242
|
+
# @return [ Hash ] Expanded criteria.
|
243
|
+
private def _mongoid_expand_keys(expr)
|
244
|
+
unless expr.is_a?(Hash)
|
245
|
+
raise ArgumentError, 'Argument must be a Hash'
|
246
|
+
end
|
247
|
+
|
248
|
+
result = {}
|
249
|
+
expr.each do |field, value|
|
250
|
+
field.__expr_part__(value.__expand_complex__).each do |k, v|
|
251
|
+
if result[k]
|
252
|
+
if result[k].is_a?(Hash)
|
253
|
+
# Existing value is an operator.
|
254
|
+
# If new value is also an operator, ensure there are no
|
255
|
+
# conflicts and add
|
256
|
+
if v.is_a?(Hash)
|
257
|
+
# The new value is also an operator.
|
258
|
+
# If there are no conflicts, combine the hashes, otherwise
|
259
|
+
# add new conditions to top level with $and.
|
260
|
+
if (v.keys & result[k].keys).empty?
|
261
|
+
result[k].update(v)
|
262
|
+
else
|
263
|
+
raise NotImplementedError, 'Ruby does not allow same symbol operator with different values'
|
264
|
+
result['$and'] ||= []
|
265
|
+
result['$and'] << {k => v}
|
266
|
+
end
|
267
|
+
else
|
268
|
+
# The new value is a simple value.
|
269
|
+
# If there isn't an $eq operator already in the query,
|
270
|
+
# transform the new value into an $eq operator and add it
|
271
|
+
# to the existing hash. Otherwise add the new condition
|
272
|
+
# with $and to the top level.
|
273
|
+
if result[k].key?('$eq')
|
274
|
+
raise NotImplementedError, 'Ruby does not allow same symbol operator with different values'
|
275
|
+
result['$and'] ||= []
|
276
|
+
result['$and'] << {k => v}
|
277
|
+
else
|
278
|
+
result[k].update('$eq' => v)
|
279
|
+
end
|
280
|
+
end
|
281
|
+
else
|
282
|
+
# Existing value is a simple value.
|
283
|
+
# If we are adding an operator, and the operator is not $eq,
|
284
|
+
# convert existing value into $eq and add the new operator
|
285
|
+
# to the same hash. Otherwise add the new condition with $and
|
286
|
+
# to the top level.
|
287
|
+
if v.is_a?(Hash) && !v.key?('$eq')
|
288
|
+
result[k] = {'$eq' => result[k]}.update(v)
|
289
|
+
else
|
290
|
+
raise NotImplementedError, 'Ruby does not allow same symbol operator with different values'
|
291
|
+
result['$and'] ||= []
|
292
|
+
result['$and'] << {k => v}
|
293
|
+
end
|
294
|
+
end
|
295
|
+
else
|
296
|
+
result[k] = v
|
297
|
+
end
|
298
|
+
end
|
233
299
|
end
|
300
|
+
result
|
234
301
|
end
|
235
302
|
|
236
303
|
# Adds the criterion to the existing selection.
|
@@ -89,11 +89,27 @@ module Mongoid
|
|
89
89
|
if new_s.is_a?(Selectable)
|
90
90
|
new_s = new_s.selector
|
91
91
|
end
|
92
|
-
normalized =
|
92
|
+
normalized = _mongoid_expand_keys(new_s)
|
93
93
|
normalized.each do |k, v|
|
94
94
|
k = k.to_s
|
95
95
|
if c.selector[k]
|
96
|
-
|
96
|
+
# There is already a condition on k.
|
97
|
+
# If v is an operator, and all existing conditions are
|
98
|
+
# also operators, and v isn't present in existing conditions,
|
99
|
+
# we can add to existing conditions.
|
100
|
+
# Otherwise use $and.
|
101
|
+
if v.is_a?(Hash) &&
|
102
|
+
v.length == 1 &&
|
103
|
+
(new_k = v.keys.first).start_with?('$') &&
|
104
|
+
(existing_kv = c.selector[k]).is_a?(Hash) &&
|
105
|
+
!existing_kv.key?(new_k) &&
|
106
|
+
existing_kv.keys.all? { |sub_k| sub_k.start_with?('$') }
|
107
|
+
then
|
108
|
+
merged_v = c.selector[k].merge(v)
|
109
|
+
c.selector.store(k, merged_v)
|
110
|
+
else
|
111
|
+
c = c.send(:__multi__, [k => v], '$and')
|
112
|
+
end
|
97
113
|
else
|
98
114
|
c.selector.store(k, v)
|
99
115
|
end
|
@@ -567,17 +583,21 @@ module Mongoid
|
|
567
583
|
if new_s.is_a?(Selectable)
|
568
584
|
new_s = new_s.selector
|
569
585
|
end
|
570
|
-
new_s.each do |k, v|
|
586
|
+
_mongoid_expand_keys(new_s).each do |k, v|
|
571
587
|
k = k.to_s
|
572
588
|
if c.selector[k] || k[0] == ?$
|
573
589
|
c = c.send(:__multi__, [{'$nor' => [{k => v}]}], '$and')
|
574
590
|
else
|
575
|
-
if v.is_a?(
|
576
|
-
|
591
|
+
if v.is_a?(Hash)
|
592
|
+
c = c.send(:__multi__, [{'$nor' => [{k => v}]}], '$and')
|
577
593
|
else
|
578
|
-
|
594
|
+
if v.is_a?(Regexp)
|
595
|
+
negated_operator = '$not'
|
596
|
+
else
|
597
|
+
negated_operator = '$ne'
|
598
|
+
end
|
599
|
+
c = c.send(:__override__, {k => v}, negated_operator)
|
579
600
|
end
|
580
|
-
c = c.send(:__override__, {k => v}, negated_operator)
|
581
601
|
end
|
582
602
|
end
|
583
603
|
c
|
@@ -665,7 +685,7 @@ module Mongoid
|
|
665
685
|
# and add the result to self.
|
666
686
|
exprs = criteria.map do |criterion|
|
667
687
|
if criterion.is_a?(Selectable)
|
668
|
-
|
688
|
+
_mongoid_expand_keys(criterion.selector)
|
669
689
|
else
|
670
690
|
Hash[criterion.map do |k, v|
|
671
691
|
if k.is_a?(Symbol)
|
@@ -48,9 +48,11 @@ module Mongoid
|
|
48
48
|
value.each_pair do |_key, _value|
|
49
49
|
value[_key] = (key == "$rename") ? _value.to_s : mongoize_for(key, klass, _key, _value)
|
50
50
|
end
|
51
|
-
|
51
|
+
consolidated[key] ||= {}
|
52
|
+
consolidated[key].update(value)
|
52
53
|
else
|
53
|
-
|
54
|
+
consolidated["$set"] ||= {}
|
55
|
+
consolidated["$set"].update(key => mongoize_for(key, klass, key, value))
|
54
56
|
end
|
55
57
|
end
|
56
58
|
consolidated
|
data/lib/mongoid/fields.rb
CHANGED
@@ -500,7 +500,8 @@ module Mongoid
|
|
500
500
|
def create_translations_getter(name, meth)
|
501
501
|
generated_methods.module_eval do
|
502
502
|
re_define_method("#{meth}_translations") do
|
503
|
-
|
503
|
+
attributes[name] ||= {}
|
504
|
+
attributes[name].with_indifferent_access
|
504
505
|
end
|
505
506
|
alias_method :"#{meth}_t", :"#{meth}_translations"
|
506
507
|
end
|
@@ -10,8 +10,8 @@ module Mongoid
|
|
10
10
|
# Does the supplied query match the attribute?
|
11
11
|
#
|
12
12
|
# @example Does this match?
|
13
|
-
# matcher._matches?(
|
14
|
-
# matcher._matches?(BSON::Regex::Raw.new("
|
13
|
+
# matcher._matches?(/\AEm/)
|
14
|
+
# matcher._matches?(BSON::Regex::Raw.new("\\AEm"))
|
15
15
|
#
|
16
16
|
# @param [ BSON::Regexp::Raw, Regexp ] regexp The regular expression object.
|
17
17
|
#
|
@@ -57,7 +57,10 @@ module Mongoid
|
|
57
57
|
def push(pushes)
|
58
58
|
prepare_atomic_operation do |ops|
|
59
59
|
process_atomic_operations(pushes) do |field, value|
|
60
|
-
existing = send(field) ||
|
60
|
+
existing = send(field) || begin
|
61
|
+
attributes[field] ||= []
|
62
|
+
attributes[field]
|
63
|
+
end
|
61
64
|
values = [ value ].flatten(1)
|
62
65
|
values.each{ |val| existing.push(val) }
|
63
66
|
ops[atomic_attribute_name(field)] = { "$each" => values }
|
@@ -122,6 +122,12 @@ module Mongoid
|
|
122
122
|
end
|
123
123
|
end
|
124
124
|
|
125
|
+
def client_name
|
126
|
+
@client_name ||= options[:client] ||
|
127
|
+
Threaded.client_override ||
|
128
|
+
storage_options && __evaluate__(storage_options[:client])
|
129
|
+
end
|
130
|
+
|
125
131
|
# Determine if this persistence context is equal to another.
|
126
132
|
#
|
127
133
|
# @example Compare two persistence contexts.
|
@@ -139,12 +145,6 @@ module Mongoid
|
|
139
145
|
|
140
146
|
private
|
141
147
|
|
142
|
-
def client_name
|
143
|
-
@client_name ||= options[:client] ||
|
144
|
-
Threaded.client_override ||
|
145
|
-
storage_options && __evaluate__(storage_options[:client])
|
146
|
-
end
|
147
|
-
|
148
148
|
def set_options!(opts)
|
149
149
|
@options ||= opts.each.reduce({}) do |_options, (key, value)|
|
150
150
|
unless VALID_OPTIONS.include?(key.to_sym)
|
data/lib/mongoid/query_cache.rb
CHANGED
@@ -168,7 +168,8 @@ module Mongoid
|
|
168
168
|
@coll_name ||= result.namespace.sub("#{database.name}.", '') if result.namespace
|
169
169
|
documents = result.documents
|
170
170
|
if @cursor_id.zero? && !@after_first_batch
|
171
|
-
|
171
|
+
@cached_documents ||= []
|
172
|
+
@cached_documents.concat(documents)
|
172
173
|
end
|
173
174
|
@after_first_batch = true
|
174
175
|
documents
|
@@ -150,7 +150,7 @@ module Mongoid
|
|
150
150
|
#
|
151
151
|
# @since 2.3.0
|
152
152
|
def filter(value)
|
153
|
-
!case_sensitive? && value ? /\A#{Regexp.escape(value.to_s)}
|
153
|
+
!case_sensitive? && value ? /\A#{Regexp.escape(value.to_s)}\z/i : value
|
154
154
|
end
|
155
155
|
|
156
156
|
# Scope the criteria to the scope options provided.
|
data/lib/mongoid/version.rb
CHANGED
@@ -13,7 +13,7 @@ class <%= class_name %><%= " < #{options[:parent].classify}" if options[:parent]
|
|
13
13
|
field :<%= attribute.name %>, type: <%= attribute.type_class %>
|
14
14
|
<% end -%>
|
15
15
|
<% attributes.select{|attr| attr.reference? }.each do |attribute| -%>
|
16
|
-
|
16
|
+
belongs_to :<%= attribute.name%>
|
17
17
|
<% end -%>
|
18
18
|
end
|
19
19
|
<% end -%>
|