mongomatic 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/lib/mongomatic/base.rb +1 -1
  2. data/lib/mongomatic/validatable/child_validation.rb +17 -0
  3. data/lib/mongomatic/validatable/errors.rb +108 -0
  4. data/lib/mongomatic/validatable/included_validation.rb +11 -0
  5. data/lib/mongomatic/validatable/macros.rb +316 -0
  6. data/lib/{validatable → mongomatic/validatable}/object_extension.rb +0 -0
  7. data/lib/mongomatic/validatable/requireable.rb +28 -0
  8. data/lib/mongomatic/validatable/understandable.rb +33 -0
  9. data/lib/mongomatic/validatable/validatable_class_methods.rb +89 -0
  10. data/lib/mongomatic/validatable/validatable_instance_methods.rb +108 -0
  11. data/lib/mongomatic/validatable/validations/validates_acceptance_of.rb +16 -0
  12. data/lib/mongomatic/validatable/validations/validates_associated.rb +15 -0
  13. data/lib/mongomatic/validatable/validations/validates_confirmation_of.rb +25 -0
  14. data/lib/mongomatic/validatable/validations/validates_each.rb +16 -0
  15. data/lib/mongomatic/validatable/validations/validates_exclusion_of.rb +19 -0
  16. data/lib/mongomatic/validatable/validations/validates_format_of.rb +18 -0
  17. data/lib/mongomatic/validatable/validations/validates_inclusion_of.rb +19 -0
  18. data/lib/mongomatic/validatable/validations/validates_length_of.rb +32 -0
  19. data/lib/mongomatic/validatable/validations/validates_numericality_of.rb +28 -0
  20. data/lib/mongomatic/validatable/validations/validates_presence_of.rb +18 -0
  21. data/lib/mongomatic/validatable/validations/validates_true_for.rb +15 -0
  22. data/lib/mongomatic/validatable/validations/validation_base.rb +93 -0
  23. data/lib/{validatable.rb → mongomatic/validatable.rb} +4 -2
  24. data/lib/mongomatic.rb +1 -1
  25. metadata +26 -35
  26. data/lib/validatable/child_validation.rb +0 -15
  27. data/lib/validatable/errors.rb +0 -106
  28. data/lib/validatable/included_validation.rb +0 -9
  29. data/lib/validatable/macros.rb +0 -314
  30. data/lib/validatable/requireable.rb +0 -26
  31. data/lib/validatable/understandable.rb +0 -31
  32. data/lib/validatable/validatable_class_methods.rb +0 -87
  33. data/lib/validatable/validatable_instance_methods.rb +0 -106
  34. data/lib/validatable/validations/validates_acceptance_of.rb +0 -14
  35. data/lib/validatable/validations/validates_associated.rb +0 -13
  36. data/lib/validatable/validations/validates_confirmation_of.rb +0 -23
  37. data/lib/validatable/validations/validates_each.rb +0 -14
  38. data/lib/validatable/validations/validates_exclusion_of.rb +0 -17
  39. data/lib/validatable/validations/validates_format_of.rb +0 -16
  40. data/lib/validatable/validations/validates_inclusion_of.rb +0 -17
  41. data/lib/validatable/validations/validates_length_of.rb +0 -30
  42. data/lib/validatable/validations/validates_numericality_of.rb +0 -27
  43. data/lib/validatable/validations/validates_presence_of.rb +0 -17
  44. data/lib/validatable/validations/validates_true_for.rb +0 -13
  45. data/lib/validatable/validations/validation_base.rb +0 -91
@@ -1,7 +1,7 @@
1
1
  module Mongomatic
2
2
  class Base
3
3
  include Mongomatic::Modifiers
4
- include Validatable
4
+ include Mongomatic::Validatable
5
5
 
6
6
  class << self
7
7
  def settings
@@ -0,0 +1,17 @@
1
+ module Mongomatic
2
+ module Validatable
3
+ class ChildValidation #:nodoc:
4
+ attr_accessor :attribute, :map, :should_validate_proc
5
+
6
+ def initialize(attribute, map, should_validate_proc)
7
+ @attribute = attribute
8
+ @map = map
9
+ @should_validate_proc = should_validate_proc
10
+ end
11
+
12
+ def should_validate?(instance)
13
+ instance.instance_eval &should_validate_proc
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,108 @@
1
+ module Mongomatic
2
+ module Validatable
3
+ class Errors
4
+ extend Forwardable
5
+ include Enumerable
6
+
7
+ def_delegators :errors, :clear, :each, :each_pair, :empty?, :length, :size
8
+
9
+ # Returns true if the specified +attribute+ has errors associated with it.
10
+ #
11
+ # class Company < ActiveRecord::Base
12
+ # validates_presence_of :name, :address, :email
13
+ # validates_length_of :name, :in => 5..30
14
+ # end
15
+ #
16
+ # company = Company.create(:address => '123 First St.')
17
+ # company.errors.invalid?(:name) # => true
18
+ # company.errors.invalid?(:address) # => false
19
+ def invalid?(attribute)
20
+ !@errors[attribute.to_sym].nil?
21
+ end
22
+
23
+ # Adds an error to the base object instead of any particular attribute. This is used
24
+ # to report errors that don't tie to any specific attribute, but rather to the object
25
+ # as a whole. These error messages don't get prepended with any field name when iterating
26
+ # with +each_full+, so they should be complete sentences.
27
+ def add_to_base(msg)
28
+ add(:base, msg)
29
+ end
30
+
31
+ # Returns errors assigned to the base object through +add_to_base+ according to the normal rules of <tt>on(attribute)</tt>.
32
+ def on_base
33
+ on(:base)
34
+ end
35
+
36
+ # call-seq: on(attribute)
37
+ #
38
+ # * Returns nil, if no errors are associated with the specified +attribute+.
39
+ # * Returns the error message, if one error is associated with the specified +attribute+.
40
+ # * Returns an array of error messages, if more than one error is associated with the specified +attribute+.
41
+ def on(attribute)
42
+ return nil if errors[attribute.to_sym].nil?
43
+ errors[attribute.to_sym].size == 1 ? errors[attribute.to_sym].first : errors[attribute.to_sym]
44
+ end
45
+
46
+ # Rails 3 API for errors, always return array.
47
+ def [](attribute)
48
+ errors[attribute.to_sym] || []
49
+ end
50
+
51
+ def add(attribute, message) #:nodoc:
52
+ errors[attribute.to_sym] = [] if errors[attribute.to_sym].nil?
53
+ errors[attribute.to_sym] << message
54
+ end
55
+
56
+ def merge!(errors) #:nodoc:
57
+ errors.each_pair{|k, v| add(k,v)}
58
+ self
59
+ end
60
+
61
+ # call-seq: replace(attribute)
62
+ #
63
+ # * Replaces the errors value for the given +attribute+
64
+ def replace(attribute, value)
65
+ errors[attribute.to_sym] = value
66
+ end
67
+
68
+ # call-seq: raw(attribute)
69
+ #
70
+ # * Returns an array of error messages associated with the specified +attribute+.
71
+ def raw(attribute)
72
+ errors[attribute.to_sym]
73
+ end
74
+
75
+ def errors #:nodoc:
76
+ @errors ||= {}
77
+ end
78
+
79
+ def count #:nodoc:
80
+ errors.values.flatten.size
81
+ end
82
+
83
+ # call-seq: full_messages -> an_array_of_messages
84
+ #
85
+ # Returns an array containing the full list of error messages.
86
+ def full_messages
87
+ full_messages = []
88
+
89
+ errors.each_key do |attribute|
90
+ errors[attribute].each do |msg|
91
+ next if msg.nil?
92
+
93
+ if attribute.to_s == "base"
94
+ full_messages << msg
95
+ else
96
+ full_messages << humanize(attribute.to_s) + " " + msg
97
+ end
98
+ end
99
+ end
100
+ full_messages
101
+ end
102
+
103
+ def humanize(lower_case_and_underscored_word) #:nodoc:
104
+ lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,11 @@
1
+ module Mongomatic
2
+ module Validatable
3
+ class IncludedValidation #:nodoc:
4
+ attr_accessor :attribute
5
+
6
+ def initialize(attribute)
7
+ @attribute = attribute
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,316 @@
1
+ module Mongomatic
2
+ module Validatable
3
+ module Macros
4
+ # call-seq: validates_each(*args)
5
+ #
6
+ # Validates that the logic evaluates to true
7
+ #
8
+ # class Address
9
+ # include Validatable
10
+ # validates_each :zip_code, :logic => lambda { errors.add(:zip_code, "is not valid") if ZipCodeService.allows(zip_code) }
11
+ # end
12
+ #
13
+ # The logic option is required.
14
+ #
15
+ # Configuration options:
16
+ #
17
+ # * after_validate - A block that executes following the run of a validation
18
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
19
+ # * if - A block that when executed must return true of the validation will not occur
20
+ # * level - The level at which the validation should occur
21
+ # * logic - A block that executes to perform the validation
22
+ # * message - The message to add to the errors collection when the validation fails
23
+ # * times - The number of times the validation applies
24
+ def validates_each(*args)
25
+ add_validations(args, ValidatesEach)
26
+ end
27
+
28
+ # call-seq: validates_format_of(*args)
29
+ #
30
+ # Validates whether the value of the specified attribute is of the
31
+ # correct form by matching it against the regular expression provided.
32
+ #
33
+ # class Person
34
+ # include Validatable
35
+ # validates_format_of :first_name, :with => /[ A-Za-z]/
36
+ # end
37
+ #
38
+ # A regular expression must be provided or else an exception will be raised.
39
+ #
40
+ # Configuration options:
41
+ #
42
+ # * after_validate - A block that executes following the run of a validation
43
+ # * message - The message to add to the errors collection when the validation fails
44
+ # * times - The number of times the validation applies
45
+ # * level - The level at which the validation should occur
46
+ # * if - A block that when executed must return true of the validation will not occur
47
+ # * with - The regular expression used to validate the format
48
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
49
+ def validates_format_of(*args)
50
+ add_validations(args, ValidatesFormatOf)
51
+ end
52
+
53
+ # call-seq: validates_length_of(*args)
54
+ #
55
+ # Validates that the specified attribute matches the length restrictions supplied.
56
+ #
57
+ # class Person
58
+ # include Validatable
59
+ # validates_length_of :first_name, :maximum=>30
60
+ # validates_length_of :last_name, :minimum=>30
61
+ # end
62
+ #
63
+ # Configuration options:
64
+ #
65
+ # * after_validate - A block that executes following the run of a validation
66
+ # * message - The message to add to the errors collection when the validation fails
67
+ # * times - The number of times the validation applies
68
+ # * level - The level at which the validation should occur
69
+ # * if - A block that when executed must return true of the validation will not occur
70
+ # * minimum - The minimum size of the attribute
71
+ # * maximum - The maximum size of the attribute
72
+ # * is - The size the attribute must be
73
+ # * within - A range that the size of the attribute must fall within
74
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
75
+ def validates_length_of(*args)
76
+ add_validations(args, ValidatesLengthOf)
77
+ end
78
+
79
+ # call-seq: validates_numericality_of(*args)
80
+ #
81
+ # Validates that the specified attribute is numeric.
82
+ #
83
+ # class Person
84
+ # include Validatable
85
+ # validates_numericality_of :age
86
+ # end
87
+ #
88
+ # Configuration options:
89
+ #
90
+ # * after_validate - A block that executes following the run of a validation
91
+ # * message - The message to add to the errors collection when the validation fails
92
+ # * times - The number of times the validation applies
93
+ # * level - The level at which the validation should occur
94
+ # * if - A block that when executed must return true of the validation will not occur
95
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
96
+ # * only_integer - Whether the attribute must be an integer (default is false)
97
+ def validates_numericality_of(*args)
98
+ add_validations(args, ValidatesNumericalityOf)
99
+ end
100
+
101
+ # call-seq: validates_acceptance_of(*args)
102
+ #
103
+ # Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
104
+ #
105
+ # class Person
106
+ # include Validatable
107
+ # validates_acceptance_of :terms_of_service
108
+ # validates_acceptance_of :eula, :message => "must be abided"
109
+ # end
110
+ #
111
+ # Configuration options:
112
+ #
113
+ # * after_validate - A block that executes following the run of a validation
114
+ # * message - The message to add to the errors collection when the validation fails
115
+ # * times - The number of times the validation applies
116
+ # * level - The level at which the validation should occur
117
+ # * if - A block that when executed must return true of the validation will not occur
118
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
119
+ def validates_acceptance_of(*args)
120
+ add_validations(args, ValidatesAcceptanceOf)
121
+ end
122
+
123
+ # call-seq: validates_confirmation_of(*args)
124
+ #
125
+ # Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
126
+ #
127
+ # Class:
128
+ # class PersonPresenter
129
+ # include Validatable
130
+ # validates_confirmation_of :user_name, :password
131
+ # validates_confirmation_of :email_address, :message => "should match confirmation"
132
+ # end
133
+ #
134
+ # View:
135
+ # <%= password_field "person", "password" %>
136
+ # <%= password_field "person", "password_confirmation" %>
137
+ #
138
+ # Configuration options:
139
+ #
140
+ # * after_validate - A block that executes following the run of a validation
141
+ # * case_sensitive - Whether or not to apply case-sensitivity on the comparison. Defaults to true.
142
+ # * message - The message to add to the errors collection when the validation fails
143
+ # * times - The number of times the validation applies
144
+ # * level - The level at which the validation should occur
145
+ # * if - A block that when executed must return true of the validation will not occur
146
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
147
+ def validates_confirmation_of(*args)
148
+ add_validations(args, ValidatesConfirmationOf)
149
+ end
150
+
151
+ # call-seq: validates_presence_of(*args)
152
+ #
153
+ # Validates that the specified attributes are not nil or an empty string
154
+ #
155
+ # class Person
156
+ # include Validatable
157
+ # validates_presence_of :first_name
158
+ # end
159
+ #
160
+ # The first_name attribute must be in the object and it cannot be nil or empty.
161
+ #
162
+ # Configuration options:
163
+ #
164
+ # * after_validate - A block that executes following the run of a validation
165
+ # * message - The message to add to the errors collection when the validation fails
166
+ # * times - The number of times the validation applies
167
+ # * level - The level at which the validation should occur
168
+ # * if - A block that when executed must return true of the validation will not occur
169
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
170
+ def validates_presence_of(*args)
171
+ add_validations(args, ValidatesPresenceOf)
172
+ end
173
+
174
+ # call-seq: validates_true_for(*args)
175
+ #
176
+ # Validates that the logic evaluates to true
177
+ #
178
+ # class Person
179
+ # include Validatable
180
+ # validates_true_for :first_name, :logic => lambda { first_name == 'Jamie' }
181
+ # end
182
+ #
183
+ # The logic option is required.
184
+ #
185
+ # Configuration options:
186
+ #
187
+ # * after_validate - A block that executes following the run of a validation
188
+ # * message - The message to add to the errors collection when the validation fails
189
+ # * times - The number of times the validation applies
190
+ # * level - The level at which the validation should occur
191
+ # * if - A block that when executed must return true of the validation will not occur
192
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
193
+ # * logic - A block that executes to perform the validation
194
+ def validates_true_for(*args)
195
+ add_validations(args, ValidatesTrueFor)
196
+ end
197
+
198
+ def validates_exclusion_of(*args)
199
+ add_validations(args, ValidatesExclusionOf)
200
+ end
201
+
202
+ def validates_inclusion_of(*args)
203
+ add_validations(args, ValidatesInclusionOf)
204
+ end
205
+
206
+ # call-seq: validates_associated(*args)
207
+ #
208
+ # Checks the validity of an associated object or objects and adds a single
209
+ # error if validation fails.
210
+ #
211
+ # class Person
212
+ # include Validatable
213
+ # attr_accessor :addresses
214
+ # validates_associated :addresses
215
+ # end
216
+ #
217
+ # Configuration options:
218
+ #
219
+ # * after_validate - A block that executes following the run of a validation
220
+ # * message - The message to add to the errors collection when the validation fails
221
+ # * times - The number of times the validation applies
222
+ # * level - The level at which the validation should occur
223
+ # * if - A block that when executed must return true of the validation will not occur
224
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
225
+ def validates_associated(*args)
226
+ add_validations(args, ValidatesAssociated)
227
+ end
228
+
229
+ # call-seq: include_validations_from(attribute)
230
+ #
231
+ # Includes all the validations that are defined on the attribute.
232
+ # class Person
233
+ # include Validatable
234
+ # validates_presence_of :name
235
+ # end
236
+ #
237
+ # class PersonPresenter
238
+ # include Validatable
239
+ # include_validataions_from :person
240
+ # attr_accessor :person
241
+ # def name
242
+ # person.name
243
+ # end
244
+ #
245
+ # def initialize(person)
246
+ # @person = person
247
+ # end
248
+ # end
249
+ #
250
+ # presenter = PersonPresenter.new(Person.new)
251
+ # presenter.valid? #=> false
252
+ # presenter.errors.on(:name) #=> "can't be blank"
253
+ #
254
+ # The name attribute whose validations should be added.
255
+ def include_validations_from(attribute_to_validate, options = {})
256
+ validations_to_include << IncludedValidation.new(attribute_to_validate)
257
+ end
258
+
259
+ # call-seq: include_errors_from(attribute_to_validate, options = {})
260
+ #
261
+ # Validates the specified attributes.
262
+ # class Person
263
+ # include Validatable
264
+ # validates_presence_of :name
265
+ # attr_accessor :name
266
+ # end
267
+ #
268
+ # class PersonPresenter
269
+ # include Validatable
270
+ # include_errors_from :person, :map => { :name => :namen }, :if => lambda { not person.nil? }
271
+ # attr_accessor :person
272
+ #
273
+ # def initialize(person)
274
+ # @person = person
275
+ # end
276
+ # end
277
+ #
278
+ # presenter = PersonPresenter.new(Person.new)
279
+ # presenter.valid? #=> false
280
+ # presenter.errors.on(:namen) #=> "can't be blank"
281
+ #
282
+ # The person attribute will be validated.
283
+ # If person is invalid the errors will be added to the PersonPresenter errors collection.
284
+ #
285
+ # Configuration options:
286
+ #
287
+ # * map - A hash that maps attributes of the child to attributes of the parent.
288
+ # * if - A block that when executed must return true of the validation will not occur.
289
+ def include_errors_from(attribute_to_validate, options = {})
290
+ children_to_validate << ChildValidation.new(attribute_to_validate, options[:map] || {}, options[:if] || lambda { true })
291
+ end
292
+
293
+ def include_validations_for(attribute_to_validate, options = {}) #:nodoc:
294
+ puts "include_validations_for is deprecated; use include_errors_from instead"
295
+ children_to_validate << ChildValidation.new(attribute_to_validate, options[:map] || {}, options[:if] || lambda { true })
296
+ end
297
+
298
+ # call-seq: before_validation(&block)
299
+ #
300
+ # Is called before valid? or valid_for_*?
301
+ #
302
+ # class Person
303
+ # include Validatable
304
+ # before_validation do
305
+ # self.name = "default name"
306
+ # end
307
+ #
308
+ # attr_accessor :name
309
+ # end
310
+ #
311
+ def before_validation(&block)
312
+ before_validations << block
313
+ end
314
+ end
315
+ end
316
+ end
@@ -0,0 +1,28 @@
1
+ module Mongomatic
2
+ module Validatable
3
+ module Requireable #:nodoc:
4
+ module ClassMethods #:nodoc:
5
+ def requires(*args)
6
+ required_options.concat args
7
+ end
8
+
9
+ def required_options
10
+ @required_options ||= []
11
+ end
12
+ end
13
+
14
+ def self.included(klass)
15
+ klass.extend ClassMethods
16
+ end
17
+
18
+ def requires(options)
19
+ required_options = self.class.required_options.inject([]) do |errors, attribute|
20
+ errors << attribute.to_s unless options.has_key?(attribute)
21
+ errors
22
+ end
23
+ raise ArgumentError.new("#{self.class} requires options: #{required_options.join(', ')}") if required_options.any?
24
+ true
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,33 @@
1
+ module Mongomatic
2
+ module Validatable
3
+ module Understandable #:nodoc:
4
+ module ClassMethods #:nodoc:
5
+ def understands(*args)
6
+ understandings.concat args
7
+ end
8
+
9
+ def understandings
10
+ @understandings ||= []
11
+ end
12
+
13
+ def all_understandings
14
+ return understandings + self.superclass.all_understandings if self.superclass.respond_to? :all_understandings
15
+ understandings
16
+ end
17
+ end
18
+
19
+ def self.included(klass)
20
+ klass.extend ClassMethods
21
+ end
22
+
23
+ def must_understand(hash)
24
+ invalid_options = hash.inject([]) do |errors, (key, value)|
25
+ errors << key.to_s unless self.class.all_understandings.include?(key)
26
+ errors
27
+ end
28
+ raise ArgumentError.new("invalid options: #{invalid_options.join(', ')}") if invalid_options.any?
29
+ true
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,89 @@
1
+ module Mongomatic
2
+ module Validatable
3
+ module ClassMethods #:nodoc:
4
+
5
+ def validate_children(instance, group)
6
+ self.children_to_validate.each do |child_validation|
7
+ next unless child_validation.should_validate?(instance)
8
+ child_or_children = instance.send child_validation.attribute
9
+ [child_or_children].flatten.each do |child|
10
+ if (child.respond_to?(:valid_for_group?))
11
+ child.valid_for_group?(group)
12
+ else
13
+ child.valid?
14
+ end
15
+ child.errors.each do |attribute, messages|
16
+ if messages.is_a?(String)
17
+ add_error(instance, child_validation.map[attribute.to_sym] || attribute, messages)
18
+ else
19
+ messages.each do |message|
20
+ add_error(instance, child_validation.map[attribute.to_sym] || attribute, message)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ def all_before_validations
29
+ if self.superclass.respond_to? :all_before_validations
30
+ return before_validations + self.superclass.all_before_validations
31
+ end
32
+ before_validations
33
+ end
34
+
35
+ def before_validations
36
+ @before_validations ||= []
37
+ end
38
+
39
+ def all_validations
40
+ if self.respond_to?(:superclass) && self.superclass.respond_to?(:all_validations)
41
+ return validations + self.superclass.all_validations
42
+ end
43
+ validations
44
+ end
45
+
46
+ def validations
47
+ @validations ||= []
48
+ end
49
+
50
+ def add_error(instance, attribute, msg)
51
+ instance.errors.add(attribute, msg)
52
+ end
53
+
54
+ def validation_keys_include?(key)
55
+ validations.map { |validation| validation.key }.include?(key)
56
+ end
57
+
58
+ def validations_to_include
59
+ @validations_to_include ||= []
60
+ end
61
+
62
+ protected
63
+
64
+ def add_validations(args, klass)
65
+ options = args.last.is_a?(Hash) ? args.pop : {}
66
+ args.each do |attribute|
67
+ new_validation = klass.new self, attribute, options
68
+ self.validations << new_validation
69
+ self.create_valid_method_for_groups new_validation.groups
70
+ end
71
+ end
72
+
73
+ def create_valid_method_for_groups(groups)
74
+ groups.each do |group|
75
+ self.class_eval do
76
+ define_method "valid_for_#{group}?".to_sym do
77
+ valid_for_group?(group)
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ def children_to_validate
84
+ @children_to_validate ||= []
85
+ end
86
+
87
+ end
88
+ end
89
+ end