glue 0.41.0 → 1.0.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/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
|