glue 0.0.1 → 0.13.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.
- data/CHANGELOG +19 -0
- data/INSTALL +56 -0
- data/README +3 -0
- data/Rakefile +73 -10
- data/doc/AUTHORS +16 -0
- data/doc/LICENSE +33 -0
- data/doc/RELEASES +5 -0
- data/install.rb +47 -0
- data/lib/glue.rb +57 -59
- data/lib/glue/array.rb +61 -0
- data/lib/glue/attribute.rb +83 -0
- data/lib/glue/cache.rb +138 -0
- data/lib/glue/flexob.rb +12 -0
- data/lib/glue/hash.rb +122 -0
- data/lib/glue/inflector.rb +91 -0
- data/lib/glue/logger.rb +147 -0
- data/lib/glue/misc.rb +14 -0
- data/lib/glue/mixins.rb +36 -0
- data/lib/glue/number.rb +24 -0
- data/lib/glue/object.rb +32 -0
- data/lib/glue/pool.rb +60 -0
- data/lib/glue/property.rb +408 -0
- data/lib/glue/string.rb +162 -0
- data/lib/glue/time.rb +85 -0
- data/lib/glue/validation.rb +461 -0
- data/test/glue/tc_attribute.rb +22 -0
- data/test/glue/tc_cache.rb +45 -0
- data/test/glue/tc_hash.rb +38 -0
- data/test/glue/tc_logger.rb +39 -0
- data/test/glue/tc_numbers.rb +20 -0
- data/test/glue/tc_property.rb +91 -0
- data/test/glue/tc_property_mixins.rb +93 -0
- data/test/glue/tc_property_type_checking.rb +35 -0
- data/test/glue/tc_strings.rb +103 -0
- data/test/glue/tc_validation.rb +214 -0
- metadata +95 -89
- data/History.txt +0 -6
- data/Manifest.txt +0 -7
- data/README.txt +0 -127
- data/bin/glue +0 -1
- data/test/test_glue.rb +0 -218
data/lib/glue/string.rb
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
# * George Moschovitis <gm@navel.gr>
|
2
|
+
# * Anastasios Koutoumanos <ak@navel.gr>
|
3
|
+
# * Elias Karakoulakis <ekarak@ktismata.com>
|
4
|
+
# (c) 2004-2005 Navel, all rights reserved.
|
5
|
+
# $Id: string.rb 282 2005-03-10 12:24:53Z gmosx $
|
6
|
+
|
7
|
+
require "uri"
|
8
|
+
|
9
|
+
module N;
|
10
|
+
|
11
|
+
# General string utilities collection.
|
12
|
+
#
|
13
|
+
# === Design:
|
14
|
+
#
|
15
|
+
# Implement as a module to avoid class polution. You can
|
16
|
+
# still Ruby's advanced features to include the module in your
|
17
|
+
# class. Passing the object to act upon allows to check for nil,
|
18
|
+
# which isn't possible if you use self.
|
19
|
+
#
|
20
|
+
# === TODO:
|
21
|
+
#
|
22
|
+
# - implement a method that returns easy to remember
|
23
|
+
# pseudo-random strings
|
24
|
+
# - add aliases for those methods in Kernel.
|
25
|
+
|
26
|
+
module StringUtils
|
27
|
+
|
28
|
+
# Move this in String class?
|
29
|
+
#
|
30
|
+
# Tests a string for a valid value (non nil, not empty)
|
31
|
+
#
|
32
|
+
def self.valid?(string)
|
33
|
+
return (not ((nil == string) or (string.empty?)))
|
34
|
+
end
|
35
|
+
|
36
|
+
# returns short abstract of long strings (first 'count'
|
37
|
+
# characters, chopped at the nearest word, appended by '...')
|
38
|
+
# force_cutoff: break forcibly at 'count' chars. Does not accept
|
39
|
+
# count < 2.
|
40
|
+
|
41
|
+
def self.head(string, count = 128, force_cutoff = false, ellipsis="...")
|
42
|
+
return nil unless string
|
43
|
+
return nil if count < 2
|
44
|
+
|
45
|
+
if string.size > count
|
46
|
+
cut_at = force_cutoff ? count : (string.index(' ', count-1) || count)
|
47
|
+
xstring = string.slice(0, cut_at)
|
48
|
+
return xstring.chomp(" ") + ellipsis
|
49
|
+
else
|
50
|
+
return string
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Apply a set of rules (regular expression matches) to the
|
55
|
+
# string
|
56
|
+
#
|
57
|
+
# === Requirements:
|
58
|
+
# - the rules must be applied in order! So we cannot use a
|
59
|
+
# hash because the ordering is not guaranteed! we use an
|
60
|
+
# array instead.
|
61
|
+
#
|
62
|
+
# === Input:
|
63
|
+
# the string to rewrite
|
64
|
+
# the array containing rule-pairs (match, rewrite)
|
65
|
+
#
|
66
|
+
# === Output:
|
67
|
+
# the rewritten string
|
68
|
+
|
69
|
+
MATCH = 0
|
70
|
+
REWRITE = 1
|
71
|
+
|
72
|
+
def self.rewrite(string, rules)
|
73
|
+
return nil unless string
|
74
|
+
|
75
|
+
# gmosx: helps to find bugs
|
76
|
+
raise ArgumentError.new('The rules parameter is nil') unless rules
|
77
|
+
|
78
|
+
rewritten_string = string.dup
|
79
|
+
|
80
|
+
for rule in rules
|
81
|
+
rewritten_string.gsub!(rule[MATCH], rule[REWRITE])
|
82
|
+
end
|
83
|
+
|
84
|
+
return (rewritten_string or string)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Enforces a maximum width of a string inside an
|
88
|
+
# html container. If the string exceeds this maximum width
|
89
|
+
# the string gets wraped.
|
90
|
+
#
|
91
|
+
# Not really useful, better use the CSS overflow: hidden
|
92
|
+
# functionality.
|
93
|
+
#
|
94
|
+
# === Input:
|
95
|
+
# the string to be wrapped
|
96
|
+
# the enforced width
|
97
|
+
# the separator used for wrapping
|
98
|
+
#
|
99
|
+
# === Output:
|
100
|
+
# the wrapped string
|
101
|
+
#
|
102
|
+
# === Example:
|
103
|
+
# text = "1111111111111111111111111111111111111111111"
|
104
|
+
# text = wrap(text, 10, " ")
|
105
|
+
# p text # => "1111111111 1111111111 1111111111"
|
106
|
+
#
|
107
|
+
# See the test cases to better understand the behaviour!
|
108
|
+
|
109
|
+
def self.wrap(string, width = 20, separator = " ")
|
110
|
+
return nil unless string
|
111
|
+
|
112
|
+
re = /([^#{separator}]{1,#{width}})/
|
113
|
+
wrapped_string = string.scan(re).join(separator)
|
114
|
+
|
115
|
+
return wrapped_string
|
116
|
+
end
|
117
|
+
|
118
|
+
# Replace dangerours chars in filenames
|
119
|
+
=begin
|
120
|
+
def self.rationalize_filename(filename)
|
121
|
+
return nil unless filename
|
122
|
+
# gmosx: rationalize a copy!!! (add unit test)
|
123
|
+
xfilename = filename.dup()
|
124
|
+
# gmosx: replace some dangerous chars!
|
125
|
+
xfilename.gsub!(/ /, "-")
|
126
|
+
xfilename.gsub!(/!/, "")
|
127
|
+
xfilename.gsub!(/'/, "")
|
128
|
+
xfilename.gsub!(/\(/, "")
|
129
|
+
xfilename.gsub!(/\)/, "")
|
130
|
+
# xfilename = self.to_greeklish(xfilename)
|
131
|
+
return xfilename
|
132
|
+
end
|
133
|
+
=end
|
134
|
+
|
135
|
+
# Returns a random string. one possible use is
|
136
|
+
# password initialization.
|
137
|
+
#
|
138
|
+
# === Input:
|
139
|
+
# the maximum length of the string
|
140
|
+
#
|
141
|
+
# === Output:
|
142
|
+
# the random string
|
143
|
+
|
144
|
+
def self.random(max_length = 8, char_re = /[\w\d]/)
|
145
|
+
# gmosx: this is a nice example of input parameter checking.
|
146
|
+
# this is NOT a real time called method so we can add this
|
147
|
+
# check. Congrats to the author.
|
148
|
+
raise ArgumentError.new('char_re must be a regular expression!') unless char_re.is_a?(Regexp)
|
149
|
+
|
150
|
+
string = ""
|
151
|
+
|
152
|
+
while string.length < max_length
|
153
|
+
ch = rand(255).chr
|
154
|
+
string << ch if ch =~ char_re
|
155
|
+
end
|
156
|
+
|
157
|
+
return string
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
data/lib/glue/time.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# * George Moschovitis <gm@navel.gr>
|
2
|
+
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
+
# $Id: time.rb 282 2005-03-10 12:24:53Z gmosx $
|
4
|
+
|
5
|
+
require 'time.rb'
|
6
|
+
|
7
|
+
module N;
|
8
|
+
|
9
|
+
# General time utilities collection
|
10
|
+
#
|
11
|
+
# Implement as a module to avoid class polution. You can
|
12
|
+
# still Ruby's advanced features to include the module in your
|
13
|
+
# class. Passing the object to act upon allows to check for nil,
|
14
|
+
# which isn't possible if you use self.
|
15
|
+
#
|
16
|
+
# == TODO
|
17
|
+
#
|
18
|
+
# - SOS: add test units.
|
19
|
+
# - add aliases for those methods in Kernel ?
|
20
|
+
|
21
|
+
module TimeUtils
|
22
|
+
|
23
|
+
NOW = Time.now
|
24
|
+
NEVER = Time.mktime(2038)
|
25
|
+
ZERO = Time.mktime(1972)
|
26
|
+
|
27
|
+
# Convert the time to a nice String representation.
|
28
|
+
|
29
|
+
def self.date_time(time)
|
30
|
+
return nil unless time
|
31
|
+
return time.strftime("%d-%m-%Y %H:%M")
|
32
|
+
end
|
33
|
+
|
34
|
+
# This method calculates the days extrema given two time objects.
|
35
|
+
# start time is the given time1 at 00:00:00
|
36
|
+
# end time is the given time2 at 23:59:59:999
|
37
|
+
#
|
38
|
+
# Input:
|
39
|
+
# - the two times (if only time1 is provided then you get an extrema
|
40
|
+
# of exactly one day extrema.
|
41
|
+
#
|
42
|
+
# Output
|
43
|
+
# - the time range. you can get the start/end times using
|
44
|
+
# range methods.
|
45
|
+
|
46
|
+
def self.days_extrema(time1, time2=nil)
|
47
|
+
time2 = time1 if (not time2.valid? Time)
|
48
|
+
time2 = NEVER if (time2 <= time1)
|
49
|
+
start_time = Time.self.start_of_day(time1)
|
50
|
+
end_time = self.end_of_day(time2)
|
51
|
+
return (start_time..end_time)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Set time to start of day
|
55
|
+
|
56
|
+
def self.start_of_day(time)
|
57
|
+
return Time.mktime(time.year, time.month, time.day, 0, 0, 0, 0)
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
# Set time to end of day
|
62
|
+
|
63
|
+
def self.end_of_day(time)
|
64
|
+
return Time.mktime(time.year, time.month, time.day, 23, 59, 59, 999)
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
# Returns true only if day of time is included in the
|
69
|
+
# range (stime..etime). Only year days are checked.
|
70
|
+
|
71
|
+
def self.time_in_day_range(time, stime=ZERO, etime=NEVER)
|
72
|
+
if (etime <= stime)
|
73
|
+
Logger.debug "Invalid end time (#{etime} < #{stime})" if $DBG
|
74
|
+
etime = NEVER
|
75
|
+
end
|
76
|
+
|
77
|
+
stime = start_of_day(stime)
|
78
|
+
etime = end_of_day(etime)
|
79
|
+
|
80
|
+
return (stime..etime).include?(time)
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,461 @@
|
|
1
|
+
# * George Moschovitis <gm@navel.gr>
|
2
|
+
# (c) 2004-2005 Navel, all rights reserved.
|
3
|
+
# $Id$
|
4
|
+
|
5
|
+
module N
|
6
|
+
|
7
|
+
# Implements a meta-language for validating managed
|
8
|
+
# objects. Typically used in Validator objects but can be
|
9
|
+
# included in managed objects too.
|
10
|
+
#
|
11
|
+
# The following validation macros are available:
|
12
|
+
#
|
13
|
+
# * validate_value
|
14
|
+
# * validate_confirmation
|
15
|
+
# * validate_format
|
16
|
+
# * validate_length
|
17
|
+
# * validate_inclusion
|
18
|
+
#
|
19
|
+
# Og/Database specific validation methods are added in the
|
20
|
+
# file og/validation.rb
|
21
|
+
#
|
22
|
+
# === Example
|
23
|
+
#
|
24
|
+
# class User
|
25
|
+
# prop_accessor :name, String
|
26
|
+
# prop_accessor :level, Fixnum
|
27
|
+
#
|
28
|
+
# validate_length :name, :range => 2..6
|
29
|
+
# validate_unique :name, :msg => :name_allready_exists
|
30
|
+
# validate_format :name, :format => /[a-z]*/, :msg => 'invalid format', :on => :create
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# class N::CustomUserValidator
|
34
|
+
# include N::Validation
|
35
|
+
# validate_length :name, :range => 2..6, :msg_short => :name_too_short, :msg_long => :name_too_long
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# user = @request.fill(User.new)
|
39
|
+
# user.level = 15
|
40
|
+
#
|
41
|
+
# unless user.valid?
|
42
|
+
# user.save
|
43
|
+
# else
|
44
|
+
# p user.errors[:name]
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# unless user.save
|
48
|
+
# p user.errors.on(:name)
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# unless errors = N::CustomUserValidator.errors(user)
|
52
|
+
# user.save
|
53
|
+
# else
|
54
|
+
# p errors[:name]
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
#--
|
58
|
+
# TODO: all validation methods should imply validate_value
|
59
|
+
# TODO: add validate_unique
|
60
|
+
#++
|
61
|
+
|
62
|
+
module Validation
|
63
|
+
|
64
|
+
# Encapsulates a list of validation errors.
|
65
|
+
|
66
|
+
class Errors
|
67
|
+
attr_accessor :errors
|
68
|
+
|
69
|
+
cattr_accessor :no_value, 'No value provided'
|
70
|
+
cattr_accessor :no_confirmation, 'Invalid confirmation'
|
71
|
+
cattr_accessor :invalid_format, 'Invalid format'
|
72
|
+
cattr_accessor :too_short, 'Too short, must be more than %d characters long'
|
73
|
+
cattr_accessor :too_long, 'Too long, must be less than %d characters long'
|
74
|
+
cattr_accessor :invalid_length, 'Must be %d characters long'
|
75
|
+
cattr_accessor :no_inclusion, 'The value is invalid'
|
76
|
+
cattr_accessor :no_numeric, 'The value must be numeric'
|
77
|
+
cattr_accessor :not_unique, 'The value is already used'
|
78
|
+
|
79
|
+
def initialize
|
80
|
+
@errors = {}
|
81
|
+
end
|
82
|
+
|
83
|
+
def add(attr, message)
|
84
|
+
(@errors[attr] ||= []) << message
|
85
|
+
end
|
86
|
+
|
87
|
+
def on(attr)
|
88
|
+
@errors[attr]
|
89
|
+
end
|
90
|
+
alias_method :[], :on
|
91
|
+
|
92
|
+
# Yields each attribute and associated message.
|
93
|
+
|
94
|
+
def each
|
95
|
+
@errors.each_key do |attr|
|
96
|
+
@errors[attr].each { |msg| yield attr, msg }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def size
|
101
|
+
@errors.size
|
102
|
+
end
|
103
|
+
alias_method :count, :size
|
104
|
+
|
105
|
+
def empty?
|
106
|
+
@errors.empty?
|
107
|
+
end
|
108
|
+
|
109
|
+
def clear
|
110
|
+
@errors.clear
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# If the validate method returns true, this
|
115
|
+
# attributes holds the errors found.
|
116
|
+
|
117
|
+
attr_accessor :errors
|
118
|
+
|
119
|
+
# Call the #validate method for this object.
|
120
|
+
# If validation errors are found, sets the
|
121
|
+
# @errors attribute to the Errors object and
|
122
|
+
# returns true.
|
123
|
+
|
124
|
+
def valid?
|
125
|
+
begin
|
126
|
+
@errors = self.class.validate(self)
|
127
|
+
return @errors.empty?
|
128
|
+
rescue NoMethodError => e
|
129
|
+
# gmosx: hmm this is potentially dangerous.
|
130
|
+
N::Validation.eval_validate(self.class)
|
131
|
+
retry
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Evaluate the 'validate' method for the calling
|
136
|
+
# class.
|
137
|
+
#
|
138
|
+
# WARNING: for the moment only evaluates for
|
139
|
+
# on == :save
|
140
|
+
|
141
|
+
def self.eval_validate(klass)
|
142
|
+
code = %{
|
143
|
+
def self.validate(obj, on = :save)
|
144
|
+
errors = Errors.new
|
145
|
+
}
|
146
|
+
|
147
|
+
for val, on in klass.__meta[:validations]
|
148
|
+
code << %{
|
149
|
+
#{val}
|
150
|
+
}
|
151
|
+
end
|
152
|
+
|
153
|
+
code << %{
|
154
|
+
return errors
|
155
|
+
end
|
156
|
+
}
|
157
|
+
|
158
|
+
klass.module_eval(code)
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.append_features(base)
|
162
|
+
super
|
163
|
+
|
164
|
+
base.module_eval <<-"end_eval", __FILE__, __LINE__
|
165
|
+
meta :validations, []
|
166
|
+
end_eval
|
167
|
+
|
168
|
+
base.extend(MetaLanguage)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Implements the Validation meta-language.
|
172
|
+
|
173
|
+
module MetaLanguage
|
174
|
+
|
175
|
+
# the postfix attached to confirmation attributes.
|
176
|
+
|
177
|
+
mattr_accessor :confirmation_postfix, '_confirmation'
|
178
|
+
|
179
|
+
# Validates that the attributes have a values, ie they are
|
180
|
+
# neither nil or empty.
|
181
|
+
#
|
182
|
+
# === Example
|
183
|
+
#
|
184
|
+
# validate_value :param, :msg => 'No confirmation'
|
185
|
+
|
186
|
+
def validate_value(*params)
|
187
|
+
c = {
|
188
|
+
:msg => N::Validation::Errors.no_value,
|
189
|
+
:on => :save
|
190
|
+
}
|
191
|
+
c.update(params.pop) if params.last.is_a?(Hash)
|
192
|
+
|
193
|
+
idx = 0
|
194
|
+
for name in params
|
195
|
+
code = %{
|
196
|
+
if obj.#{name}.nil?
|
197
|
+
errors.add(:#{name}, '#{c[:msg]}')
|
198
|
+
elsif obj.#{name}.respond_to?(:empty?)
|
199
|
+
errors.add(:#{name}, '#{c[:msg]}') if obj.#{name}.empty?
|
200
|
+
end
|
201
|
+
}
|
202
|
+
|
203
|
+
__meta[:validations] << [code, c[:on]]
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# Validates the confirmation of +String+ attributes.
|
208
|
+
#
|
209
|
+
# === Example
|
210
|
+
#
|
211
|
+
# validate_confirmation :password, :msg => 'No confirmation'
|
212
|
+
|
213
|
+
def validate_confirmation(*params)
|
214
|
+
c = {
|
215
|
+
:msg => N::Validation::Errors.no_confirmation,
|
216
|
+
:postfix => N::Validation::MetaLanguage.confirmation_postfix,
|
217
|
+
:on => :save
|
218
|
+
}
|
219
|
+
c.update(params.pop) if params.last.is_a?(Hash)
|
220
|
+
|
221
|
+
|
222
|
+
for name in params
|
223
|
+
confirm_name = "#{name}#{c[:postfix]}"
|
224
|
+
eval "attr_accessor :#{confirm_name}"
|
225
|
+
|
226
|
+
code = %{
|
227
|
+
if obj.#{confirm_name}.nil? or (obj.#{confirm_name} != obj.#{name})
|
228
|
+
errors.add(:#{name}, '#{c[:msg]}')
|
229
|
+
end
|
230
|
+
}
|
231
|
+
|
232
|
+
__meta[:validations] << [code, c[:on]]
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# Validates the format of +String+ attributes.
|
237
|
+
# WARNING: regexp options are ignored.
|
238
|
+
#
|
239
|
+
# === Example
|
240
|
+
#
|
241
|
+
# validate_format :name, :format => /$A*/, :msg => 'My error', :on => :create
|
242
|
+
#
|
243
|
+
#--
|
244
|
+
# FIXME: how to get the Regexp options?
|
245
|
+
#++
|
246
|
+
|
247
|
+
def validate_format(*params)
|
248
|
+
c = {
|
249
|
+
:format => nil,
|
250
|
+
:msg_no_value => N::Validation::Errors.no_value,
|
251
|
+
:msg => N::Validation::Errors.invalid_format,
|
252
|
+
:on => :save
|
253
|
+
}
|
254
|
+
c.update(params.pop) if params.last.is_a?(Hash)
|
255
|
+
|
256
|
+
unless c[:format].is_a?(Regexp)
|
257
|
+
raise(ArgumentError,
|
258
|
+
'A regular expression must be supplied as the :format option')
|
259
|
+
end
|
260
|
+
|
261
|
+
for name in params
|
262
|
+
code = %{
|
263
|
+
if obj.#{name}.nil?
|
264
|
+
errors.add(:#{name}, '#{c[:msg_no_value]}')
|
265
|
+
else
|
266
|
+
unless obj.#{name}.to_s.match(/#{c[:format].source}/)
|
267
|
+
errors.add(:#{name}, '#{c[:msg]}')
|
268
|
+
end
|
269
|
+
end;
|
270
|
+
}
|
271
|
+
|
272
|
+
__meta[:validations] << [code, c[:on]]
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Validates the length of +String+ attributes.
|
277
|
+
#
|
278
|
+
# === Example
|
279
|
+
#
|
280
|
+
# validate_length :name, :max => 30, :msg => 'Too long'
|
281
|
+
# validate_length :name, :min => 2, :msg => 'Too sort'
|
282
|
+
# validate_length :name, :range => 2..30
|
283
|
+
# validate_length :name, :length => 15, :msg => 'Name should be %d chars long'
|
284
|
+
|
285
|
+
def validate_length(*params)
|
286
|
+
c = {
|
287
|
+
:min => nil, :max => nil, :range => nil, :length => nil,
|
288
|
+
:msg => nil,
|
289
|
+
:msg_no_value => N::Validation::Errors.no_value,
|
290
|
+
:msg_short => N::Validation::Errors.too_short,
|
291
|
+
:msg_long => N::Validation::Errors.too_long,
|
292
|
+
:on => :save
|
293
|
+
}
|
294
|
+
c.update(params.pop) if params.last.is_a?(Hash)
|
295
|
+
|
296
|
+
min, max = c[:min], c[:max]
|
297
|
+
range, length = c[:range], c[:length]
|
298
|
+
|
299
|
+
count = 0
|
300
|
+
[min, max, range, length].each { |o| count += 1 if o }
|
301
|
+
|
302
|
+
if count == 0
|
303
|
+
raise(ArgumentError,
|
304
|
+
'One of :min, :max, :range, :length must be provided!')
|
305
|
+
end
|
306
|
+
|
307
|
+
if count > 1
|
308
|
+
raise(ArgumentError,
|
309
|
+
'The :min, :max, :range, :length options are mutually exclusive!')
|
310
|
+
end
|
311
|
+
|
312
|
+
for name in params
|
313
|
+
if min
|
314
|
+
c[:msg] ||= N::Validation::Errors.too_short
|
315
|
+
code = %{
|
316
|
+
if obj.#{name}.nil?
|
317
|
+
errors.add(:#{name}, '#{c[:msg_no_value]}')
|
318
|
+
elsif obj.#{name}.to_s.length < #{min}
|
319
|
+
msg = '#{c[:msg]}'
|
320
|
+
msg = (msg % #{min}) rescue msg
|
321
|
+
errors.add(:#{name}, msg)
|
322
|
+
end;
|
323
|
+
}
|
324
|
+
elsif max
|
325
|
+
c[:msg] ||= N::Validation::Errors.too_long
|
326
|
+
code = %{
|
327
|
+
if obj.#{name}.nil?
|
328
|
+
errors.add(:#{name}, '#{c[:msg_no_value]}')
|
329
|
+
elsif obj.#{name}.to_s.length > #{max}
|
330
|
+
msg = '#{c[:msg]}'
|
331
|
+
msg = (msg % #{max}) rescue msg
|
332
|
+
errors.add(:#{name}, msg)
|
333
|
+
end;
|
334
|
+
}
|
335
|
+
elsif range
|
336
|
+
code = %{
|
337
|
+
if obj.#{name}.nil?
|
338
|
+
errors.add(:#{name}, '#{c[:msg_no_value]}')
|
339
|
+
elsif obj.#{name}.to_s.length < #{range.first}
|
340
|
+
msg = '#{c[:msg_short]}'
|
341
|
+
msg = (msg % #{range.first}) rescue msg
|
342
|
+
errors.add(:#{name}, msg)
|
343
|
+
elsif obj.#{name}.to_s.length > #{range.last}
|
344
|
+
msg = '#{c[:msg_long]}'
|
345
|
+
msg = (msg % #{range.last}) rescue msg
|
346
|
+
errors.add(:#{name}, msg)
|
347
|
+
end;
|
348
|
+
}
|
349
|
+
elsif length
|
350
|
+
c[:msg] ||= N::Validation::Errors.invalid_length
|
351
|
+
code = %{
|
352
|
+
if obj.#{name}.nil?
|
353
|
+
errors.add(:#{name}, '#{c[:msg_no_value]}')
|
354
|
+
elsif obj.#{name}.to_s.length != #{length}
|
355
|
+
msg = '#{c[:msg]}'
|
356
|
+
msg = (msg % #{length}) rescue msg
|
357
|
+
errors.add(:#{name}, msg)
|
358
|
+
end;
|
359
|
+
}
|
360
|
+
end
|
361
|
+
|
362
|
+
__meta[:validations] << [code, c[:on]]
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
# Validates that the attributes are included in
|
367
|
+
# an enumeration.
|
368
|
+
#
|
369
|
+
# === Example
|
370
|
+
#
|
371
|
+
# validate_inclusion :sex, :in => %w{ Male Female }, :msg => 'huh??'
|
372
|
+
# validate_inclusion :age, :in => 5..99
|
373
|
+
|
374
|
+
def validate_inclusion(*params)
|
375
|
+
c = {
|
376
|
+
:in => nil,
|
377
|
+
:msg => N::Validation::Errors.no_inclusion,
|
378
|
+
:allow_nil => false,
|
379
|
+
:on => :save
|
380
|
+
}
|
381
|
+
c.update(params.pop) if params.last.is_a?(Hash)
|
382
|
+
|
383
|
+
unless c[:in].respond_to?('include?')
|
384
|
+
raise(ArgumentError,
|
385
|
+
'An object that responds to #include? must be supplied as the :in option')
|
386
|
+
end
|
387
|
+
|
388
|
+
for name in params
|
389
|
+
if c[:allow_nil]
|
390
|
+
code = %{
|
391
|
+
unless obj.#{name}.nil? or (#{c[:in].inspect}).include?(obj.#{name})
|
392
|
+
errors.add(:#{name}, '#{c[:msg]}')
|
393
|
+
end;
|
394
|
+
}
|
395
|
+
else
|
396
|
+
code = %{
|
397
|
+
unless (#{c[:in].inspect}).include?(obj.#{name})
|
398
|
+
errors.add(:#{name}, '#{c[:msg]}')
|
399
|
+
end;
|
400
|
+
}
|
401
|
+
end
|
402
|
+
|
403
|
+
__meta[:validations] << [code, c[:on]]
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
# Validates that the attributes have numeric values.
|
408
|
+
#
|
409
|
+
# [+:integer+]
|
410
|
+
# Only accept integers
|
411
|
+
#
|
412
|
+
# [+:msg]
|
413
|
+
# Custom message
|
414
|
+
#
|
415
|
+
# [+:on:]
|
416
|
+
# When to validate
|
417
|
+
#
|
418
|
+
# === Example
|
419
|
+
#
|
420
|
+
# validate_numeric :age, :msg => 'Must be an integer'
|
421
|
+
|
422
|
+
def validate_numeric(*params)
|
423
|
+
c = {
|
424
|
+
:integer => false,
|
425
|
+
:msg => N::Validation::Errors.no_numeric,
|
426
|
+
:on => :save
|
427
|
+
}
|
428
|
+
c.update(params.pop) if params.last.is_a?(Hash)
|
429
|
+
|
430
|
+
for name in params
|
431
|
+
if c[:integer]
|
432
|
+
code = %{
|
433
|
+
unless obj.#{name}.to_s =~ /^[\\+\\-]?\\d+$/
|
434
|
+
errors.add(:#{name}, '#{c[:msg]}')
|
435
|
+
end;
|
436
|
+
}
|
437
|
+
else
|
438
|
+
code = %{
|
439
|
+
begin
|
440
|
+
Kernel.Float(obj.#{name})
|
441
|
+
rescue ArgumentError, TypeError
|
442
|
+
errors.add(:#{name}, '#{c[:msg]}')
|
443
|
+
end;
|
444
|
+
}
|
445
|
+
end
|
446
|
+
|
447
|
+
__meta[:validations] << [code, c[:on]]
|
448
|
+
end
|
449
|
+
end
|
450
|
+
alias_method :validate_numericality, :validate_numeric
|
451
|
+
|
452
|
+
end
|
453
|
+
|
454
|
+
end
|
455
|
+
|
456
|
+
end
|
457
|
+
|
458
|
+
class Module # :nodoc: all
|
459
|
+
include N::Validation::MetaLanguage
|
460
|
+
end
|
461
|
+
|