glue 0.41.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +6 -0
- data/Manifest.txt +6 -0
- data/README.txt +130 -0
- data/Rakefile +16 -0
- data/lib/glue.rb +49 -72
- data/test/test_glue.rb +218 -0
- metadata +84 -100
- data/doc/AUTHORS +0 -13
- data/doc/CHANGELOG.1 +0 -354
- data/doc/LICENSE +0 -32
- data/doc/RELEASES +0 -207
- data/lib/glue/attribute.rb +0 -113
- data/lib/glue/attributeutils.rb +0 -117
- data/lib/glue/autoreload.rb +0 -60
- data/lib/glue/builder.rb +0 -57
- data/lib/glue/builder/xml.rb +0 -103
- data/lib/glue/cache.rb +0 -22
- data/lib/glue/cache/drb.rb +0 -51
- data/lib/glue/cache/file.rb +0 -78
- data/lib/glue/cache/memcached.rb +0 -68
- data/lib/glue/cache/memory.rb +0 -79
- data/lib/glue/cache/og.rb +0 -61
- data/lib/glue/configuration.rb +0 -305
- data/lib/glue/fixture.rb +0 -154
- data/lib/glue/html.rb +0 -12
- data/lib/glue/localization.rb +0 -129
- data/lib/glue/logger.rb +0 -208
- data/lib/glue/mail.rb +0 -160
- data/lib/glue/mailer.rb +0 -55
- data/lib/glue/mailer/incoming.rb +0 -41
- data/lib/glue/mailer/outgoing.rb +0 -119
- data/lib/glue/settings.rb +0 -3
- data/lib/glue/uri.rb +0 -190
- data/lib/glue/validation.rb +0 -447
- data/lib/html/document.rb +0 -63
- data/lib/html/node.rb +0 -480
- data/lib/html/tokenizer.rb +0 -103
- data/lib/html/version.rb +0 -11
- data/test/fixture/article.csv +0 -3
- data/test/fixture/article.yml +0 -13
- data/test/fixture/user.yml +0 -12
- data/test/glue/builder/tc_xml.rb +0 -57
- data/test/glue/tc_aspects.rb +0 -99
- data/test/glue/tc_attribute.rb +0 -112
- data/test/glue/tc_attribute_mixins.rb +0 -86
- data/test/glue/tc_builder.rb +0 -30
- data/test/glue/tc_configuration.rb +0 -135
- data/test/glue/tc_fixture.rb +0 -98
- data/test/glue/tc_localization.rb +0 -49
- data/test/glue/tc_logger.rb +0 -43
- data/test/glue/tc_mail.rb +0 -99
- data/test/glue/tc_stores.rb +0 -16
- data/test/glue/tc_uri.rb +0 -97
- data/test/glue/tc_validation.rb +0 -217
- data/test/public/dummy_mailer/registration.xhtml +0 -5
data/lib/glue/mailer.rb
DELETED
@@ -1,55 +0,0 @@
|
|
1
|
-
require 'glue/mail'
|
2
|
-
require 'glue/mailer/incoming'
|
3
|
-
require 'glue/mailer/outgoing'
|
4
|
-
require 'glue/configuration'
|
5
|
-
|
6
|
-
require 'socket'
|
7
|
-
|
8
|
-
module Glue
|
9
|
-
|
10
|
-
# Handles incoming and outgoing emails. Can be called from
|
11
|
-
# a Controller or a standalone script (target of the MTA).
|
12
|
-
|
13
|
-
class Mailer < Mail
|
14
|
-
include IncomingMailer
|
15
|
-
include OutgoingMailer
|
16
|
-
|
17
|
-
# The outgoing mail server configuration.
|
18
|
-
|
19
|
-
setting :server, :default => {
|
20
|
-
:address => 'localhost',
|
21
|
-
:port => 25,
|
22
|
-
:domain => Socket.gethostname,
|
23
|
-
:username => nil,
|
24
|
-
:password => nil,
|
25
|
-
:authentication => nil
|
26
|
-
}, :doc => 'The outgoing server configuration'
|
27
|
-
|
28
|
-
# The delivery method. The following options are
|
29
|
-
# supported:
|
30
|
-
#
|
31
|
-
# * :smtp
|
32
|
-
# * :sendmail
|
33
|
-
# * :test
|
34
|
-
|
35
|
-
setting :delivery_method, :default => :smtp, :doc => 'The delivery method'
|
36
|
-
|
37
|
-
# Disable deliveries, useful for testing.
|
38
|
-
|
39
|
-
setting :disable_deliveries, :default => false, :doc => 'Dissable deliveries?'
|
40
|
-
|
41
|
-
# The default template root.
|
42
|
-
|
43
|
-
setting :template_root, :default => 'template/mail', :doc => 'The default template root'
|
44
|
-
|
45
|
-
# The default from address
|
46
|
-
|
47
|
-
setting :from, :default => 'bot@nitroproject.org', :doc => 'The default from address'
|
48
|
-
|
49
|
-
# An array to store the delivered mails, useful
|
50
|
-
# for testing.
|
51
|
-
|
52
|
-
cattr_accessor :deliveries; @@deliveries = []
|
53
|
-
end
|
54
|
-
|
55
|
-
end
|
data/lib/glue/mailer/incoming.rb
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
module Glue
|
2
|
-
|
3
|
-
# Add support for incoming mail handling.
|
4
|
-
#
|
5
|
-
# You need to setup your MTA to support incoming email
|
6
|
-
# handling. Here is an example for Postfix. Edit these files:
|
7
|
-
#
|
8
|
-
# /etc/postfix/master.cf:
|
9
|
-
# mailman unix - n n - - pipe
|
10
|
-
# flags= user=nobody argv=/path/to/ruby /path/to/app/script/runner.rb Mailer.receive(STDIN)
|
11
|
-
#
|
12
|
-
# /etc/postfix/main.cf:
|
13
|
-
# transport_maps = hash:/etc/postfix/transport
|
14
|
-
# virtual_mailbox_domains = lists.yourdomain.com
|
15
|
-
#
|
16
|
-
# /etc/postfix/transport:
|
17
|
-
# lists.yourdomain.com mailman:
|
18
|
-
#
|
19
|
-
# Then run:
|
20
|
-
#
|
21
|
-
# sudo postmap transport
|
22
|
-
# sudo postfix stop
|
23
|
-
# sudo postfix start
|
24
|
-
|
25
|
-
module IncomingMailer
|
26
|
-
on_included %{ base.extend ClassMethods }
|
27
|
-
|
28
|
-
# You can overide this class for specialized handling.
|
29
|
-
|
30
|
-
def receive(mail)
|
31
|
-
end
|
32
|
-
|
33
|
-
module ClassMethods
|
34
|
-
def receive(encoded)
|
35
|
-
mail = Glue::Mail.new_from_encoded(encoded)
|
36
|
-
self.new.receive(mail)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
end
|
data/lib/glue/mailer/outgoing.rb
DELETED
@@ -1,119 +0,0 @@
|
|
1
|
-
module Glue
|
2
|
-
|
3
|
-
# Add support for outgoing mail handling.
|
4
|
-
|
5
|
-
module OutgoingMailer
|
6
|
-
# The root directory where the templates reside.
|
7
|
-
|
8
|
-
attr_accessor :template_root
|
9
|
-
|
10
|
-
on_included %{ base.extend ClassMethods }
|
11
|
-
|
12
|
-
def initialize(from = nil, to = nil, subject = nil, body = Nitro::FileTemplate.new)
|
13
|
-
super
|
14
|
-
@charset = Mailer.default_charset.dup
|
15
|
-
@encode_subject = Mailer.encode_subject
|
16
|
-
@template_root = Mailer.template_root
|
17
|
-
end
|
18
|
-
|
19
|
-
module ClassMethods
|
20
|
-
def method_missing(method_symbol, *params) #:nodoc:
|
21
|
-
case method_symbol.id2name
|
22
|
-
when /^create_([_a-z]*)/
|
23
|
-
create_from_method($1, *params)
|
24
|
-
when /^deliver_([_a-z]*)/
|
25
|
-
begin
|
26
|
-
deliver(send("create_" + $1, *params))
|
27
|
-
rescue Object => e
|
28
|
-
raise e # FIXME
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def mail(from, to, subject, body, timestamp = nil, headers = {}, encode = Mail.encode_subject, charset = Mail.default_charset) #:nodoc:
|
34
|
-
deliver(create(from, to, subject, body, timestamp, headers, charset))
|
35
|
-
end
|
36
|
-
|
37
|
-
def create(from, to, subject, body, timestamp = nil, headers = {}, encode = Mail.encode_subject, charset = Mail.default_charset) #:nodoc:
|
38
|
-
m = Mail.new
|
39
|
-
m.to, m.subject, m.body, m.from = to, ( encode ? quoted_printable(subject, charset) : subject ), body, from
|
40
|
-
# m.date = timestamp.respond_to?("to_time") ? timestamp.to_time : (timestamp || Time.now)
|
41
|
-
# m.set_content_type "text", "plain", { "charset" => charset }
|
42
|
-
headers.each do |k, v|
|
43
|
-
m[k] = v
|
44
|
-
end
|
45
|
-
return m
|
46
|
-
end
|
47
|
-
|
48
|
-
def deliver(mail) #:nodoc:
|
49
|
-
# gmosx, FIXME: for some STUPID reason, delivery_method
|
50
|
-
# returns nil, investigate.
|
51
|
-
Mailer.delivery_method = :smtp
|
52
|
-
unless Mailer.disable_deliveries
|
53
|
-
send("perform_delivery_#{Mailer.delivery_method}", mail)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def quoted_printable(text, charset) #:nodoc:
|
58
|
-
text = text.gsub( /[^a-z ]/i ) { "=%02x" % $&[0] }.gsub( / /, "_" )
|
59
|
-
"=?#{charset}?Q?#{text}?="
|
60
|
-
end
|
61
|
-
|
62
|
-
private
|
63
|
-
|
64
|
-
def create_from_method(method_name, *params)
|
65
|
-
mailer = new
|
66
|
-
|
67
|
-
mailer.send(method_name, *params)
|
68
|
-
|
69
|
-
unless mailer.body.is_a? String
|
70
|
-
mailer.body = render_body(method_name, mailer)
|
71
|
-
end
|
72
|
-
|
73
|
-
mail = create(
|
74
|
-
mailer.from, mailer.to, mailer.subject,
|
75
|
-
mailer.body, mailer.sent_on,
|
76
|
-
mailer.headers, mailer.charset
|
77
|
-
)
|
78
|
-
|
79
|
-
mail.cc = mailer.cc if mailer.cc
|
80
|
-
mail.bcc = mailer.bcc if mailer.bcc
|
81
|
-
|
82
|
-
return mail
|
83
|
-
end
|
84
|
-
|
85
|
-
# Render the body by expanding the template
|
86
|
-
|
87
|
-
def render_body(method_name, mailer)
|
88
|
-
mailer.body.template_filename = "#{mailer.template_root}/#{method_name.to_s}.xhtml"
|
89
|
-
return mailer.body.process
|
90
|
-
end
|
91
|
-
|
92
|
-
# Deliver emails using SMTP.
|
93
|
-
|
94
|
-
def perform_delivery_smtp(mail) # :nodoc:
|
95
|
-
c = Mailer.server
|
96
|
-
Net::SMTP.start(c[:address], c[:port], c[:domain], c[:username], c[:password], c[:authentication]) do |smtp|
|
97
|
-
smtp.send_message(mail.encoded, mail.from, *[mail.to, mail.cc, mail.bcc].compact)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
# Deliver emails using sendmail.
|
102
|
-
|
103
|
-
def perform_delivery_sendmail(mail) # :nodoc:
|
104
|
-
IO.popen('/usr/sbin/sendmail -i -t', 'w+') do |sm|
|
105
|
-
sm.print(mail.encoded)
|
106
|
-
sm.flush
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
# Used for testing, does not actually send the
|
111
|
-
# mail.
|
112
|
-
|
113
|
-
def perform_delivery_test(mail) # :nodoc:
|
114
|
-
deliveries << mail
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
end
|
data/lib/glue/settings.rb
DELETED
data/lib/glue/uri.rb
DELETED
@@ -1,190 +0,0 @@
|
|
1
|
-
require 'uri'
|
2
|
-
require 'cgi'
|
3
|
-
|
4
|
-
require 'facet/string/blank'
|
5
|
-
|
6
|
-
module Glue
|
7
|
-
|
8
|
-
# URI utilities collection.
|
9
|
-
#
|
10
|
-
# === Design
|
11
|
-
#
|
12
|
-
# Implement as a module to avoid class polution. You can still
|
13
|
-
# use Ruby's advanced features to include the module in your
|
14
|
-
# class. Passing the object to act upon allows to check for nil,
|
15
|
-
# which isn't possible if you use self.
|
16
|
-
#
|
17
|
-
# The uris passed as parameters are typically strings.
|
18
|
-
#--
|
19
|
-
# gmosx, TODO: deprecate this.
|
20
|
-
#++
|
21
|
-
|
22
|
-
module UriUtils
|
23
|
-
|
24
|
-
# Decode the uri components.
|
25
|
-
|
26
|
-
def self.decode(uri)
|
27
|
-
# gmosx: hmm is this needed?
|
28
|
-
# guard against invalid filenames for example pictures with
|
29
|
-
# spaces uploaded by users
|
30
|
-
escaped_uri = uri.gsub(/ /, "+")
|
31
|
-
|
32
|
-
if md = URI::REGEXP::REL_URI.match(escaped_uri)
|
33
|
-
|
34
|
-
path = "#{md[5]}#{md[6]}"
|
35
|
-
type = File.extname(path)
|
36
|
-
query_string = md[7]
|
37
|
-
|
38
|
-
# real_path = "#{$root_dir}/#{path}"
|
39
|
-
|
40
|
-
parameters = Glue::UriUtils.query_string_to_hash(query_string)
|
41
|
-
path.gsub!(/\+/, " ")
|
42
|
-
|
43
|
-
return [path, type, parameters, query_string]
|
44
|
-
|
45
|
-
end # match
|
46
|
-
|
47
|
-
# this is usefull for uncovering bugs!
|
48
|
-
raise ArgumentError.new("the parameter '#{uri}' is not a valid uri")
|
49
|
-
end
|
50
|
-
|
51
|
-
# Extend the basic query string parser provided by the cgi module.
|
52
|
-
# converts single valued params (the most common case) to
|
53
|
-
# objects instead of arrays
|
54
|
-
#
|
55
|
-
# Input:
|
56
|
-
# the query string
|
57
|
-
#
|
58
|
-
# Output:
|
59
|
-
# hash of parameters, contains arrays for multivalued parameters
|
60
|
-
# (multiselect, checkboxes , etc)
|
61
|
-
# If no query string is provided (nil or "") returns an empty hash.
|
62
|
-
|
63
|
-
def self.query_string_to_hash(query_string)
|
64
|
-
return {} unless query_string
|
65
|
-
|
66
|
-
query_parameters = CGI::parse(query_string)
|
67
|
-
|
68
|
-
query_parameters.each { |key, val|
|
69
|
-
# replace the array with an object
|
70
|
-
query_parameters[key] = val[0] if 1 == val.length
|
71
|
-
}
|
72
|
-
|
73
|
-
# set default value to nil! cgi sets this to []
|
74
|
-
query_parameters.default = nil
|
75
|
-
|
76
|
-
return query_parameters
|
77
|
-
end
|
78
|
-
|
79
|
-
# Given a hash with parameter/value pairs construct a
|
80
|
-
# standard query string. This method only encodes simple
|
81
|
-
# types (Numeric, String) to avoid query string polution
|
82
|
-
# with marshal, etc.
|
83
|
-
#
|
84
|
-
# gmosx, FIXME: only numeric and strings are passed to
|
85
|
-
# the latest code, so update old code and optimize this!
|
86
|
-
#
|
87
|
-
# Input:
|
88
|
-
# the parameter hash
|
89
|
-
#
|
90
|
-
# Output:
|
91
|
-
# the query string
|
92
|
-
|
93
|
-
def self.hash_to_query_string(parameters)
|
94
|
-
return nil unless parameters
|
95
|
-
pairs = []
|
96
|
-
parameters.each { |param, value|
|
97
|
-
# only encode simple classes !
|
98
|
-
|
99
|
-
if value.is_a?(Numeric) or value.is_a?(String)
|
100
|
-
pairs << "#{param}=#{CGI.escape(value.to_s)}"
|
101
|
-
end
|
102
|
-
}
|
103
|
-
return pairs.join(";")
|
104
|
-
end
|
105
|
-
|
106
|
-
# This method returns the query string of a uri
|
107
|
-
#
|
108
|
-
# Input:
|
109
|
-
# the uri
|
110
|
-
#
|
111
|
-
# Output:
|
112
|
-
# the query string.
|
113
|
-
# returns nil if no query string
|
114
|
-
|
115
|
-
def self.get_query_string(uri)
|
116
|
-
return nil unless uri
|
117
|
-
# gmosx: INVESTIGATE ruby's URI seems to differently handle
|
118
|
-
# abs and rel uris.
|
119
|
-
if md = URI::REGEXP::ABS_URI.match(uri)
|
120
|
-
return md[8]
|
121
|
-
elsif md = URI::REGEXP::REL_URI.match(uri)
|
122
|
-
return md[7]
|
123
|
-
end
|
124
|
-
return nil
|
125
|
-
end
|
126
|
-
|
127
|
-
# Removes the query string from a uri
|
128
|
-
#
|
129
|
-
# Input:
|
130
|
-
# the uri
|
131
|
-
#
|
132
|
-
# Output:
|
133
|
-
# the chomped uri.
|
134
|
-
|
135
|
-
def self.chomp_query_string(uri)
|
136
|
-
return nil unless uri
|
137
|
-
query_string = self.get_query_string(uri)
|
138
|
-
return uri.dup.chomp("?#{query_string}")
|
139
|
-
end
|
140
|
-
|
141
|
-
# Get a uri and a hash of parameters. Inject the hash values
|
142
|
-
# as parameters in the query sting path. Returns the full
|
143
|
-
# uri.
|
144
|
-
#
|
145
|
-
# Input:
|
146
|
-
# the uri to filter (String)
|
147
|
-
# hash of parameters to update
|
148
|
-
#
|
149
|
-
# Output:
|
150
|
-
# the full updated query string
|
151
|
-
#
|
152
|
-
# === TODO:
|
153
|
-
# optimize this a litle bit.
|
154
|
-
|
155
|
-
def self.update_query_string(uri, parameters)
|
156
|
-
query_string = self.get_query_string(uri)
|
157
|
-
rest = uri.dup.gsub(/\?#{query_string}/, "")
|
158
|
-
|
159
|
-
hash = self.query_string_to_hash(query_string)
|
160
|
-
hash.update(parameters)
|
161
|
-
query_string = self.hash_to_query_string(hash)
|
162
|
-
|
163
|
-
unless query_string.blank?
|
164
|
-
return "#{rest}?#{query_string}"
|
165
|
-
else
|
166
|
-
return rest
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
# TODO: find a better name.
|
171
|
-
# Gets the request uri, injects extra parameters in the query string
|
172
|
-
# and returns a new uri. The request object is not modified.
|
173
|
-
# There is always a qs string so an extra test is skipped.
|
174
|
-
|
175
|
-
def self.update_request_uri(request, parameters)
|
176
|
-
hash = request.parameters.dup()
|
177
|
-
hash.update(parameters)
|
178
|
-
|
179
|
-
# use this in hash_to_querystring.
|
180
|
-
query_string = hash.collect { |k, v|
|
181
|
-
"#{k}=#{v}"
|
182
|
-
}.join(";")
|
183
|
-
|
184
|
-
return "#{request.translated_uri}?#{query_string}"
|
185
|
-
end
|
186
|
-
|
187
|
-
end
|
188
|
-
|
189
|
-
end
|
190
|
-
|
data/lib/glue/validation.rb
DELETED
@@ -1,447 +0,0 @@
|
|
1
|
-
require 'facets/core/class/cattr'
|
2
|
-
require 'facets/core/module/on_included'
|
3
|
-
require 'facets/more/inheritor'
|
4
|
-
|
5
|
-
require 'glue/configuration'
|
6
|
-
|
7
|
-
module Glue
|
8
|
-
|
9
|
-
# Implements a meta-language for validating managed
|
10
|
-
# objects. Typically used in Validator objects but can be
|
11
|
-
# included in managed objects too.
|
12
|
-
#
|
13
|
-
# Additional og-related validation macros can be found
|
14
|
-
# in lib/og/validation.
|
15
|
-
#
|
16
|
-
# The following validation macros are available:
|
17
|
-
#
|
18
|
-
# * validate_value
|
19
|
-
# * validate_confirmation
|
20
|
-
# * validate_format
|
21
|
-
# * validate_length
|
22
|
-
# * validate_inclusion
|
23
|
-
#
|
24
|
-
# Og/Database specific validation methods are added in the
|
25
|
-
# file og/validation.rb
|
26
|
-
#
|
27
|
-
# === Example
|
28
|
-
#
|
29
|
-
# class User
|
30
|
-
# attr_accessor :name, String
|
31
|
-
# attr_accessor :level, Fixnum
|
32
|
-
#
|
33
|
-
# validate_length :name, :range => 2..6
|
34
|
-
# validate_unique :name, :msg => :name_allready_exists
|
35
|
-
# validate_format :name, :format => /[a-z]*/, :msg => 'invalid format', :on => :create
|
36
|
-
# end
|
37
|
-
#
|
38
|
-
# class CustomUserValidator
|
39
|
-
# include Validation
|
40
|
-
# validate_length :name, :range => 2..6, :msg_short => :name_too_short, :msg_long => :name_too_long
|
41
|
-
# end
|
42
|
-
#
|
43
|
-
# user = @request.fill(User.new)
|
44
|
-
# user.level = 15
|
45
|
-
#
|
46
|
-
# unless user.valid?
|
47
|
-
# user.save
|
48
|
-
# else
|
49
|
-
# p user.errors[:name]
|
50
|
-
# end
|
51
|
-
#
|
52
|
-
# unless user.save
|
53
|
-
# p user.errors.on(:name)
|
54
|
-
# end
|
55
|
-
#
|
56
|
-
# unless errors = CustomUserValidator.errors(user)
|
57
|
-
# user.save
|
58
|
-
# else
|
59
|
-
# p errors[:name]
|
60
|
-
# end
|
61
|
-
#--
|
62
|
-
# TODO: all validation methods should imply validate_value.
|
63
|
-
#++
|
64
|
-
|
65
|
-
module Validation
|
66
|
-
|
67
|
-
# The postfix attached to confirmation attributes.
|
68
|
-
|
69
|
-
setting :confirmation_postfix, :default => '_confirmation', :doc => 'The postfix attached to confirmation attributes.'
|
70
|
-
|
71
|
-
# Encapsulates a list of validation errors.
|
72
|
-
|
73
|
-
class Errors
|
74
|
-
attr_accessor :errors
|
75
|
-
|
76
|
-
setting :no_value, :default => 'No value provided'
|
77
|
-
setting :no_confirmation, :default => 'Invalid confirmation'
|
78
|
-
setting :invalid_format, :default => 'Invalid format'
|
79
|
-
setting :too_short, :default => 'Too short, must be more than %d characters long'
|
80
|
-
setting :too_long, :default => 'Too long, must be less than %d characters long'
|
81
|
-
setting :invalid_length, :default => 'Must be %d characters long'
|
82
|
-
setting :no_inclusion, :default => 'The value is invalid'
|
83
|
-
setting :no_numeric, :default => 'The value must be numeric'
|
84
|
-
setting :not_unique, :default => 'The value is already used'
|
85
|
-
|
86
|
-
def initialize
|
87
|
-
@errors = {}
|
88
|
-
end
|
89
|
-
|
90
|
-
def add(attr, message)
|
91
|
-
(@errors[attr] ||= []) << message
|
92
|
-
end
|
93
|
-
|
94
|
-
def on(attr)
|
95
|
-
@errors[attr]
|
96
|
-
end
|
97
|
-
alias_method :[], :on
|
98
|
-
|
99
|
-
# Yields each attribute and associated message.
|
100
|
-
|
101
|
-
def each
|
102
|
-
@errors.each_key do |attr|
|
103
|
-
@errors[attr].each { |msg| yield attr, msg }
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def size
|
108
|
-
@errors.size
|
109
|
-
end
|
110
|
-
alias_method :count, :size
|
111
|
-
|
112
|
-
def empty?
|
113
|
-
@errors.empty?
|
114
|
-
end
|
115
|
-
|
116
|
-
def clear
|
117
|
-
@errors.clear
|
118
|
-
end
|
119
|
-
|
120
|
-
def to_a
|
121
|
-
@errors.inject([]) { |a, kv| a << kv }
|
122
|
-
end
|
123
|
-
|
124
|
-
def join(glue)
|
125
|
-
@errors.to_a.join(glue)
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
# A Key is used to uniquely identify a validation rule.
|
130
|
-
|
131
|
-
class Key
|
132
|
-
attr_reader :validation
|
133
|
-
attr_reader :field
|
134
|
-
|
135
|
-
def initialize(validation, field)
|
136
|
-
@validation, @field = validation.to_s, field.to_s
|
137
|
-
end
|
138
|
-
|
139
|
-
def hash
|
140
|
-
"#{@validation}-#{@field}".hash
|
141
|
-
end
|
142
|
-
|
143
|
-
def ==(other)
|
144
|
-
self.validation == other.validation and self.field == other.field
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
# If the validate method returns true, this
|
149
|
-
# attributes holds the errors found.
|
150
|
-
|
151
|
-
attr_accessor :errors
|
152
|
-
|
153
|
-
# Call the #validate method for this object.
|
154
|
-
# If validation errors are found, sets the
|
155
|
-
# @errors attribute to the Errors object and
|
156
|
-
# returns true.
|
157
|
-
|
158
|
-
def valid?
|
159
|
-
validate
|
160
|
-
@errors.empty?
|
161
|
-
end
|
162
|
-
|
163
|
-
# Evaluate the class and see if it is valid.
|
164
|
-
# Can accept any parameter for 'on' event,
|
165
|
-
# and defaults to :save
|
166
|
-
|
167
|
-
def validate(on = :save)
|
168
|
-
@errors = Errors.new
|
169
|
-
|
170
|
-
return if self.class.validations.length == 0
|
171
|
-
|
172
|
-
for event, block in self.class.validations
|
173
|
-
block.call(self) if event == on.to_sym
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
on_included %{
|
178
|
-
base.module_eval do
|
179
|
-
inheritor(:validations, [], :+) unless @validations
|
180
|
-
# THINK: investigate carefuly!
|
181
|
-
def self.included(cbase)
|
182
|
-
super
|
183
|
-
cbase.send :include, Glue::Validation
|
184
|
-
end
|
185
|
-
end
|
186
|
-
base.extend(ClassMethods)
|
187
|
-
}
|
188
|
-
|
189
|
-
# Implements the Validation meta-language.
|
190
|
-
|
191
|
-
module ClassMethods
|
192
|
-
# Validates that the attributes have a values, ie they are
|
193
|
-
# neither nil or empty.
|
194
|
-
#
|
195
|
-
# === Example
|
196
|
-
#
|
197
|
-
# validate_value :param, :msg => 'No confirmation'
|
198
|
-
|
199
|
-
def validate_value(*params)
|
200
|
-
c = parse_config(params, :msg => Glue::Validation::Errors.no_value, :on => :save)
|
201
|
-
|
202
|
-
params.each do |field|
|
203
|
-
define_validation(:value, field, c[:on]) do |obj|
|
204
|
-
value = obj.send(field)
|
205
|
-
obj.errors.add(field, c[:msg]) if value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
206
|
-
end
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
# Validates the confirmation of +String+ attributes.
|
211
|
-
#
|
212
|
-
# === Example
|
213
|
-
#
|
214
|
-
# validate_confirmation :password, :msg => 'No confirmation'
|
215
|
-
|
216
|
-
def validate_confirmation(*params)
|
217
|
-
c = parse_config(params,
|
218
|
-
:msg => Glue::Validation::Errors.no_confirmation,
|
219
|
-
:postfix => Glue::Validation.confirmation_postfix,
|
220
|
-
:on => :save
|
221
|
-
)
|
222
|
-
|
223
|
-
params.each do |field|
|
224
|
-
confirm_name = field.to_s + c[:postfix]
|
225
|
-
attr_accessor confirm_name.to_sym
|
226
|
-
|
227
|
-
define_validation(:confirmation, field, c[:on]) do |obj|
|
228
|
-
value = obj.send(field)
|
229
|
-
obj.errors.add(field, c[:msg]) if value.nil? or obj.send(confirm_name) != value
|
230
|
-
end
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
# Validates the format of +String+ attributes.
|
235
|
-
# WARNING: regexp options are ignored.
|
236
|
-
#
|
237
|
-
# === Example
|
238
|
-
#
|
239
|
-
# validate_format :name, :format => /$A*/, :msg => 'My error', :on => :create
|
240
|
-
#
|
241
|
-
#--
|
242
|
-
# FIXME: how to get the Regexp options?
|
243
|
-
#++
|
244
|
-
|
245
|
-
def validate_format(*params)
|
246
|
-
c = parse_config(params,
|
247
|
-
:format => nil,
|
248
|
-
:msg_no_value => Glue::Validation::Errors.no_value,
|
249
|
-
:msg => Glue::Validation::Errors.invalid_format,
|
250
|
-
:on => :save
|
251
|
-
)
|
252
|
-
|
253
|
-
unless c[:format].is_a?(Regexp)
|
254
|
-
raise ArgumentError, "A regular expression must be supplied as the :format option for validate_format."
|
255
|
-
end
|
256
|
-
|
257
|
-
params.each do |field|
|
258
|
-
define_validation(:format, field, c[:on]) do |obj|
|
259
|
-
value = obj.send(field)
|
260
|
-
obj.errors.add(field, c[:msg]) if value.nil? or not value.to_s.match(c[:format].source)
|
261
|
-
end
|
262
|
-
end
|
263
|
-
end
|
264
|
-
|
265
|
-
# Validates the length of +String+ attributes.
|
266
|
-
#
|
267
|
-
# === Example
|
268
|
-
#
|
269
|
-
# validate_length :name, :max => 30, :msg => 'Too long'
|
270
|
-
# validate_length :name, :min => 2, :msg => 'Too sort'
|
271
|
-
# validate_length :name, :range => 2..30
|
272
|
-
# validate_length :name, :length => 15, :msg => 'Name should be %d chars long'
|
273
|
-
|
274
|
-
LENGTHS = [:min, :max, :range, :length].freeze
|
275
|
-
|
276
|
-
def validate_length(*params)
|
277
|
-
c = parse_config(params,
|
278
|
-
# :lengths => {:min => nil, :max => nil, :range => nil, :length => nil},
|
279
|
-
:min => nil, :max => nil, :range => nil, :length => nil,
|
280
|
-
:msg => nil,
|
281
|
-
:msg_no_value => Glue::Validation::Errors.no_value,
|
282
|
-
:msg_short => Glue::Validation::Errors.too_short,
|
283
|
-
:msg_long => Glue::Validation::Errors.too_long,
|
284
|
-
:msg_invalid => Glue::Validation::Errors.invalid_length,
|
285
|
-
:on => :save
|
286
|
-
)
|
287
|
-
|
288
|
-
length_params = c.reject {|k,v| !LENGTHS.include?(k) || v.nil? }
|
289
|
-
valid_count = length_params.reject{|k,v| v.nil?}.length
|
290
|
-
|
291
|
-
if valid_count == 0
|
292
|
-
raise ArgumentError, 'One of :min, :max, :range, or :length must be provided!'
|
293
|
-
elsif valid_count > 1
|
294
|
-
raise ArgumentError, 'You can only use one of :min, :max, :range, or :length options!'
|
295
|
-
end
|
296
|
-
|
297
|
-
operation, required = length_params.keys[0], length_params.values[0]
|
298
|
-
|
299
|
-
params.each do |field|
|
300
|
-
define_validation(:length, field, c[:on]) do |obj|
|
301
|
-
msg = c[:msg]
|
302
|
-
value = obj.send(field)
|
303
|
-
if value.nil?
|
304
|
-
obj.errors.add(field, c[:msg_no_value])
|
305
|
-
else
|
306
|
-
case operation
|
307
|
-
when :min
|
308
|
-
msg ||= c[:msg_short]
|
309
|
-
obj.errors.add(field, msg % required) if value.length < required
|
310
|
-
when :max
|
311
|
-
msg ||= c[:msg_long]
|
312
|
-
obj.errors.add(field, msg % required) if value.length > required
|
313
|
-
when :range
|
314
|
-
min, max = required.first, required.last
|
315
|
-
if value.length < min
|
316
|
-
msg ||= c[:msg_short]
|
317
|
-
obj.errors.add(field, msg % min)
|
318
|
-
end
|
319
|
-
if value.length > max
|
320
|
-
msg ||= c[:msg_long]
|
321
|
-
obj.errors.add(field, msg % min)
|
322
|
-
end
|
323
|
-
when :length
|
324
|
-
msg ||= c[:msg_invalid]
|
325
|
-
obj.errors.add(field, msg % required) if value.length != required
|
326
|
-
end
|
327
|
-
end
|
328
|
-
end
|
329
|
-
end
|
330
|
-
end
|
331
|
-
|
332
|
-
# Validates that the attributes are included in
|
333
|
-
# an enumeration.
|
334
|
-
#
|
335
|
-
# === Example
|
336
|
-
#
|
337
|
-
# validate_inclusion :sex, :in => %w{ Male Female }, :msg => 'huh??'
|
338
|
-
# validate_inclusion :age, :in => 5..99
|
339
|
-
|
340
|
-
def validate_inclusion(*params)
|
341
|
-
c = parse_config(params,
|
342
|
-
:in => nil,
|
343
|
-
:msg => Glue::Validation::Errors.no_inclusion,
|
344
|
-
:allow_nil => false,
|
345
|
-
:on => :save
|
346
|
-
)
|
347
|
-
|
348
|
-
unless c[:in].respond_to?('include?')
|
349
|
-
raise(ArgumentError, 'An object that responds to #include? must be supplied as the :in option')
|
350
|
-
end
|
351
|
-
|
352
|
-
params.each do |field|
|
353
|
-
define_validation(:inclusion, field, c[:on]) do |obj|
|
354
|
-
value = obj.send(field)
|
355
|
-
unless (c[:allow_nil] && value.nil?) or c[:in].include?(value)
|
356
|
-
obj.errors.add(field, c[:msg])
|
357
|
-
end
|
358
|
-
end
|
359
|
-
end
|
360
|
-
end
|
361
|
-
|
362
|
-
# Validates that the attributes have numeric values.
|
363
|
-
#
|
364
|
-
# [+:integer+]
|
365
|
-
# Only accept integers
|
366
|
-
#
|
367
|
-
# [+:msg]
|
368
|
-
# Custom message
|
369
|
-
#
|
370
|
-
# [+:on:]
|
371
|
-
# When to validate
|
372
|
-
#
|
373
|
-
# === Example
|
374
|
-
#
|
375
|
-
# validate_numeric :age, :msg => 'Must be an integer'
|
376
|
-
|
377
|
-
def validate_numeric(*params)
|
378
|
-
c = parse_config(params,
|
379
|
-
:integer => false,
|
380
|
-
:msg => Glue::Validation::Errors.no_numeric,
|
381
|
-
:on => :save
|
382
|
-
)
|
383
|
-
|
384
|
-
params.each do |field|
|
385
|
-
define_validation(:numeric, field, c[:on]) do |obj|
|
386
|
-
value = obj.send(field).to_s
|
387
|
-
if c[:integer]
|
388
|
-
unless value =~ /^[\+\-]*\d+$/
|
389
|
-
obj.errors.add(field, c[:msg])
|
390
|
-
end
|
391
|
-
else
|
392
|
-
begin
|
393
|
-
Float(value)
|
394
|
-
rescue ArgumentError, TypeError
|
395
|
-
obj.errors.add(field, c[:msg])
|
396
|
-
end
|
397
|
-
end
|
398
|
-
end
|
399
|
-
end
|
400
|
-
end
|
401
|
-
alias_method :validate_numericality, :validate_numeric
|
402
|
-
|
403
|
-
protected
|
404
|
-
|
405
|
-
# Parse the configuration for a validation by comparing
|
406
|
-
# the default options with the user-specified options,
|
407
|
-
# and returning the results.
|
408
|
-
|
409
|
-
def parse_config(params, defaults = {})
|
410
|
-
defaults.update(params.pop) if params.last.is_a?(Hash)
|
411
|
-
defaults
|
412
|
-
end
|
413
|
-
|
414
|
-
# Define a validation for this class on the specified event.
|
415
|
-
# Specify the on event for when this validation should be
|
416
|
-
# checked.
|
417
|
-
#
|
418
|
-
# An extra check is performed to avoid multiple validations.
|
419
|
-
#
|
420
|
-
# This example creates a validation for the 'save' event,
|
421
|
-
# and will add an error to the record if the 'name' property
|
422
|
-
# of the validating object is nil.
|
423
|
-
#
|
424
|
-
# === Example
|
425
|
-
#
|
426
|
-
# field = :name
|
427
|
-
#
|
428
|
-
# define_validation(:value, field, :save) do |object|
|
429
|
-
# object.errors.add(field, "You must set a value for #{field}") if object.send(field).nil?
|
430
|
-
# end
|
431
|
-
|
432
|
-
def define_validation(val, field, on = :save, &block)
|
433
|
-
vk = Validation::Key.new(val, field)
|
434
|
-
unless validations.find { |v| v[2] == vk }
|
435
|
-
validations! << [on, block, vk]
|
436
|
-
end
|
437
|
-
end
|
438
|
-
|
439
|
-
end
|
440
|
-
|
441
|
-
end
|
442
|
-
|
443
|
-
end
|
444
|
-
|
445
|
-
class Module # :nodoc: all
|
446
|
-
include Glue::Validation::ClassMethods
|
447
|
-
end
|