glue 0.25.0 → 0.26.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/ProjectInfo +1 -1
- data/README +1 -1
- data/doc/RELEASES +5 -0
- data/lib/glue.rb +1 -1
- data/lib/glue/accumulate.rb +21 -0
- data/lib/glue/expirable.rb +5 -0
- data/lib/glue/fixture.rb +4 -0
- data/lib/glue/html.rb +12 -0
- data/lib/glue/mailer.rb +3 -1
- data/lib/glue/markup.rb +4 -2
- data/lib/glue/property.rb +60 -28
- data/lib/glue/template.rb +6 -6
- data/lib/glue/validation.rb +170 -195
- data/test/glue/multi_validations_model.rb +5 -0
- data/test/glue/tc_accumulate.rb +37 -0
- data/test/glue/tc_multi_validations.rb +19 -0
- data/test/glue/tc_validation.rb +4 -2
- data/test/glue/tc_validation2.rb +208 -0
- data/test/glue/tc_validation_loop.rb +36 -0
- metadata +9 -2
data/ProjectInfo
CHANGED
data/README
CHANGED
data/doc/RELEASES
CHANGED
data/lib/glue.rb
CHANGED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'mega/functor'
|
2
|
+
|
3
|
+
module Enumerable
|
4
|
+
|
5
|
+
# Project has_many Group
|
6
|
+
# Group has_many User
|
7
|
+
# projects.groups.accumulate.users
|
8
|
+
#--
|
9
|
+
# gmosx: any idea for a better name?
|
10
|
+
#++
|
11
|
+
|
12
|
+
def accumulate
|
13
|
+
Functor.new do |op, *args|
|
14
|
+
self.inject([]) { |a, x| a << x.send(op, *args) }.flatten
|
15
|
+
end
|
16
|
+
end
|
17
|
+
alias_method :acc, :accumulate
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
# * George Moschovitis <gm@navel.gr>
|
data/lib/glue/expirable.rb
CHANGED
data/lib/glue/fixture.rb
CHANGED
@@ -76,6 +76,10 @@ class Fixture < Hash
|
|
76
76
|
if path = "#{root_dir}/#{@name}.yml" and File.exist?(path)
|
77
77
|
parse_yaml(path)
|
78
78
|
end
|
79
|
+
|
80
|
+
if path = "#{root_dir}/#{@name}.yaml" and File.exist?(path)
|
81
|
+
parse_yaml(path)
|
82
|
+
end
|
79
83
|
|
80
84
|
if path = "#{root_dir}/#{@name}.csv" and File.exist?(path)
|
81
85
|
parse_csv(path)
|
data/lib/glue/html.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
module Glue
|
2
|
+
module Html
|
3
|
+
def self.cleanup(buf)
|
4
|
+
out = buf.dup
|
5
|
+
elements = "input|img|br|hr|link|style|render|include|inject|base|meta"
|
6
|
+
out.gsub! /<textarea ([^>]*)><\/textarea>/, '<textarea \1>#{}</textarea>'
|
7
|
+
out.gsub! /<(#{elements}) ([^>]*)><\/\1>/, '<\1 \2 />'
|
8
|
+
out.gsub! /<(#{elements})><\/\1>/, '<\1 />'
|
9
|
+
out
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/glue/mailer.rb
CHANGED
@@ -3,6 +3,8 @@ require 'glue/mailer/incoming'
|
|
3
3
|
require 'glue/mailer/outgoing'
|
4
4
|
require 'glue/configuration'
|
5
5
|
|
6
|
+
require 'socket'
|
7
|
+
|
6
8
|
module Glue
|
7
9
|
|
8
10
|
# Handles incoming and outgoing emails. Can be called from
|
@@ -17,7 +19,7 @@ class Mailer < Mail
|
|
17
19
|
setting :server, :default => {
|
18
20
|
:address => 'localhost',
|
19
21
|
:port => 25,
|
20
|
-
:domain =>
|
22
|
+
:domain => Socket.gethostname,
|
21
23
|
:username => nil,
|
22
24
|
:password => nil,
|
23
25
|
:authentication => nil
|
data/lib/glue/markup.rb
CHANGED
@@ -2,6 +2,8 @@ require 'singleton'
|
|
2
2
|
require 'redcloth'
|
3
3
|
require 'cgi'
|
4
4
|
|
5
|
+
require 'mega/class_inherit'
|
6
|
+
|
5
7
|
require 'glue/sanitize'
|
6
8
|
|
7
9
|
module Glue
|
@@ -49,7 +51,7 @@ module Markup
|
|
49
51
|
xstr = str.dup
|
50
52
|
xstr.gsub!(/</, '<')
|
51
53
|
xstr.gsub!(/>/, '>')
|
52
|
-
xstr.gsub!(/\
|
54
|
+
xstr.gsub!(/\r/, '<br />')
|
53
55
|
return String.sanitize(xstr)
|
54
56
|
end
|
55
57
|
return nil
|
@@ -92,7 +94,7 @@ module Markup
|
|
92
94
|
CGI.unescape(str.gsub(/_/, ' '))
|
93
95
|
end
|
94
96
|
|
95
|
-
|
97
|
+
class_inherit do
|
96
98
|
# Helper method for manipulating the sanitize transformation.
|
97
99
|
|
98
100
|
def setup_sanitize_transform(&block)
|
data/lib/glue/property.rb
CHANGED
@@ -38,6 +38,7 @@ class Property
|
|
38
38
|
def to_s
|
39
39
|
@hash[:symbol].to_s
|
40
40
|
end
|
41
|
+
alias_method :name, :to_s
|
41
42
|
end
|
42
43
|
|
43
44
|
# Property related utils and helpers.
|
@@ -57,65 +58,70 @@ class Property
|
|
57
58
|
obj = obj.new if obj.is_a?(Class)
|
58
59
|
|
59
60
|
for prop in obj.class.properties.values
|
61
|
+
|
60
62
|
unless options[:all]
|
61
|
-
next if
|
62
|
-
end
|
63
|
+
next if prop.symbol == obj.class.primary_key.symbol or prop.control == :none
|
64
|
+
end
|
65
|
+
|
63
66
|
prop_name = prop.symbol.to_s
|
64
67
|
|
65
68
|
# See if there is an incoming request param for this prop.
|
66
69
|
|
67
70
|
if values.has_key?(prop_name)
|
68
71
|
|
69
|
-
# if incoming file then read contents into a string.
|
70
|
-
|
71
72
|
prop_value = values[prop_name]
|
72
|
-
#TODO remove this? prop_value.read if prop_value.respond_to?(:read)
|
73
|
-
prop_value = prop_value.to_s unless prop_value.is_a?(Hash) or prop_value.is_a?(Array)
|
74
73
|
|
75
|
-
#
|
74
|
+
# to_s must be called on the prop_value incase the request is IOString
|
75
|
+
prop_value = prop_value.to_s unless prop_value.is_a?(Hash) or prop_value.is_a?(Array)
|
76
76
|
|
77
|
+
# If property is a Blob dont overwrite current property's data if "".
|
77
78
|
break if prop.klass == Og::Blob and prop_value.empty?
|
78
79
|
|
79
|
-
#
|
80
|
-
|
80
|
+
# if custom preprocessing is active then try and preprocess
|
81
|
+
prop_value = preprocess_value(obj, prop, prop_value) if options[:preprocess]
|
82
|
+
|
83
|
+
# assign using __force_ methods.
|
81
84
|
obj.send("__force_#{prop_name}", prop_value)
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
+
|
86
|
+
# Set a boolean property to false if it is not in the request.
|
87
|
+
# requires force_boolean == true.
|
85
88
|
|
89
|
+
elsif options[:force_boolean] and (prop.klass == TrueClass or prop.klass == FalseClass)
|
86
90
|
obj.send("__force_#{prop_name}", 0)
|
87
|
-
|
88
91
|
end
|
89
92
|
end
|
90
93
|
|
91
94
|
if options[:assign_relations]
|
92
95
|
for rel in obj.class.relations
|
96
|
+
|
93
97
|
unless options[:all]
|
94
|
-
next if rel.options[:
|
98
|
+
next if rel.options[:control] == :none
|
95
99
|
end
|
96
100
|
|
97
101
|
rel_name = rel.name.to_s
|
98
102
|
|
99
103
|
# Renew the relations from values
|
100
|
-
# TODO, farms: custom callbacks for processing relations?
|
101
104
|
|
102
|
-
if rel.kind_of?
|
105
|
+
if rel.kind_of?(Og::RefersTo)
|
103
106
|
if foreign_oid = values[rel_name]
|
107
|
+
foreign_oid = foreign_oid.to_s
|
104
108
|
foreign_oid = nil if foreign_oid == 'nil' or foreign_oid == 'none'
|
109
|
+
# if custom preprocessing is active then try and preprocess
|
110
|
+
foreign_oid = preprocess_value(obj, rel, foreign_oid) if options[:preprocess]
|
105
111
|
end
|
106
|
-
obj.send("__force_#{rel.foreign_key}", foreign_oid
|
107
|
-
elsif rel.kind_of?
|
108
|
-
# Empty current relationships.
|
109
|
-
|
112
|
+
obj.send("__force_#{rel.foreign_key}", foreign_oid)
|
113
|
+
elsif rel.kind_of?(Og::JoinsMany) || rel.kind_of?(Og::HasMany)
|
110
114
|
collection = obj.send(rel_name)
|
111
115
|
collection.remove_all
|
112
|
-
|
113
|
-
# Set new.
|
114
|
-
|
115
116
|
if values.has_key?(rel_name)
|
116
|
-
values[rel_name]
|
117
|
-
|
118
|
-
|
117
|
+
primary_keys = values[rel_name]
|
118
|
+
|
119
|
+
# if custom preprocessing is active then try and preprocess
|
120
|
+
primary_keys = preprocess_value(obj, rel, primary_keys) if options[:preprocess]
|
121
|
+
|
122
|
+
primary_keys.each do |v|
|
123
|
+
next if v == 'nil' or v == 'none'
|
124
|
+
collection << rel.target_class[v.to_s.to_i]
|
119
125
|
end
|
120
126
|
end
|
121
127
|
end
|
@@ -125,6 +131,31 @@ class Property
|
|
125
131
|
return obj
|
126
132
|
end
|
127
133
|
|
134
|
+
# This method will attempt to process [value] through the
|
135
|
+
# any on_populate preprocessors availible
|
136
|
+
#
|
137
|
+
# first looks if a method was specified on the property
|
138
|
+
#
|
139
|
+
# ie. property :this, String, :on_populate => :change_value
|
140
|
+
# ==> would mean #change_value(val) would get called
|
141
|
+
# set :on_populate to false to exclude it from any preprocessing
|
142
|
+
#
|
143
|
+
# next an #on_populate method will be looked for on any
|
144
|
+
# associated Nitro::Control (if controls is running)
|
145
|
+
#
|
146
|
+
# otherwise [value] is left untouched
|
147
|
+
def preprocess_value(obj, prop_or_rel, value)
|
148
|
+
if prop_or_rel.on_populate && obj.respond_to?(prop_or_rel.on_populate.to_sym)
|
149
|
+
return obj.send(prop_or_rel.on_populate.to_sym, value)
|
150
|
+
elsif Object.const_defined? :Nitro
|
151
|
+
if control = Control.fetch(obj, prop_or_rel, nil) and control.respond_to?(:on_populate)
|
152
|
+
return control.on_populate(value)
|
153
|
+
end
|
154
|
+
else
|
155
|
+
return value
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
128
159
|
def eval_helpers(writer, m, sym, klass)
|
129
160
|
return unless writer
|
130
161
|
|
@@ -139,7 +170,7 @@ class Property
|
|
139
170
|
when Float.name: 'val.to_f'
|
140
171
|
when Time.name: 'val.is_a?(Hash) ? Time.local(val["year"],val["month"],val["day"],val["hour"],val["min"]) : Time.parse(val.to_s)'
|
141
172
|
when Date.name: 'val.is_a?(Hash) ? Time.local(val["year"],val["month"],val["day"]).to_date : Time.parse(val.to_s).to_date'
|
142
|
-
when TrueClass.name, FalseClass.name: 'val == "on" or val == "true" ? true
|
173
|
+
when TrueClass.name, FalseClass.name: 'val == "on" or val == "true" ? true: val.to_i > 0'
|
143
174
|
else 'val'
|
144
175
|
end + %{)
|
145
176
|
end
|
@@ -164,7 +195,7 @@ class Module
|
|
164
195
|
writer = (m != :reader)
|
165
196
|
code = %{
|
166
197
|
def prop#{m} (*args)
|
167
|
-
inheritor(:properties,
|
198
|
+
inheritor(:properties, Dictionary.new, :merge) unless @properties
|
168
199
|
|
169
200
|
args = args.flatten
|
170
201
|
harg = {}
|
@@ -225,3 +256,4 @@ end
|
|
225
256
|
# * George Moschovitis <gm@navel.gr>
|
226
257
|
# * Tom Sawyer <transfire@gmail.com>
|
227
258
|
# * Chris Farmiloe <chris.farmiloe@farmiloe.com>
|
259
|
+
# * Bryan Soto <bryan.a.soto@gmail.com>
|
data/lib/glue/template.rb
CHANGED
@@ -59,12 +59,8 @@ module TemplateMixin
|
|
59
59
|
# xform render/inject instructions <render href="xxx" />
|
60
60
|
# must be transformed before the processinc instructions.
|
61
61
|
|
62
|
-
text.gsub!(/<inject href=["|'](.*?)["|'](.*)(.?)\/>/) do |match|
|
63
|
-
"<?r render '#$
|
64
|
-
end
|
65
|
-
|
66
|
-
text.gsub!(/<render href=["|'](.*?)["|'](.*)(.?)\/>/) do |match|
|
67
|
-
"<?r render '#$1' ?>"
|
62
|
+
text.gsub!(/<(render|inject) href=["|'](.*?)["|'](.*)(.?)\/>/) do |match|
|
63
|
+
"<?r render '#$2' ?>"
|
68
64
|
end
|
69
65
|
|
70
66
|
# Remove <root> elements. typically removed by xslt but lets
|
@@ -155,6 +151,10 @@ class Template
|
|
155
151
|
|
156
152
|
setting :extension, :default => 'xhtml', :doc => 'The default template file extension'
|
157
153
|
|
154
|
+
# Strip xml comments from templates?
|
155
|
+
|
156
|
+
setting :strip_xml_comments, :default => false, :doc => 'Strip xml comments from templates?'
|
157
|
+
|
158
158
|
class << self
|
159
159
|
include TemplateMixin
|
160
160
|
alias_method :compile, :compile_template
|
data/lib/glue/validation.rb
CHANGED
@@ -55,9 +55,8 @@ module Glue
|
|
55
55
|
# else
|
56
56
|
# p errors[:name]
|
57
57
|
# end
|
58
|
-
#
|
59
58
|
#--
|
60
|
-
# TODO: all validation methods should imply validate_value
|
59
|
+
# TODO: all validation methods should imply validate_value.
|
61
60
|
#++
|
62
61
|
|
63
62
|
module Validation
|
@@ -115,6 +114,25 @@ module Validation
|
|
115
114
|
@errors.clear
|
116
115
|
end
|
117
116
|
end
|
117
|
+
|
118
|
+
# A Key is used to uniquely identify a validation rule.
|
119
|
+
|
120
|
+
class Key
|
121
|
+
attr_reader :validation
|
122
|
+
attr_reader :field
|
123
|
+
|
124
|
+
def initialize(validation, field)
|
125
|
+
@validation, @field = validation.to_s, field.to_s
|
126
|
+
end
|
127
|
+
|
128
|
+
def hash
|
129
|
+
"#{@validation}-#{@field}".hash
|
130
|
+
end
|
131
|
+
|
132
|
+
def ==(other)
|
133
|
+
self.validation == other.validation and self.field == other.field
|
134
|
+
end
|
135
|
+
end
|
118
136
|
|
119
137
|
# If the validate method returns true, this
|
120
138
|
# attributes holds the errors found.
|
@@ -127,40 +145,22 @@ module Validation
|
|
127
145
|
# returns true.
|
128
146
|
|
129
147
|
def valid?
|
130
|
-
|
131
|
-
|
132
|
-
return @errors.empty?
|
133
|
-
rescue NoMethodError => e
|
134
|
-
# gmosx: hmm this is potentially dangerous.
|
135
|
-
Glue::Validation.eval_validate(self.class)
|
136
|
-
retry
|
137
|
-
end
|
148
|
+
validate
|
149
|
+
@errors.empty?
|
138
150
|
end
|
139
151
|
|
140
|
-
# Evaluate the
|
141
|
-
#
|
142
|
-
#
|
143
|
-
# WARNING: for the moment only evaluates for
|
144
|
-
# on == :save
|
145
|
-
|
146
|
-
def self.eval_validate(klass)
|
147
|
-
code = %{
|
148
|
-
def self.validate(obj, on = :save)
|
149
|
-
errors = Errors.new
|
150
|
-
}
|
151
|
-
|
152
|
-
for val, on in klass.validations
|
153
|
-
code << %{
|
154
|
-
#{val}
|
155
|
-
}
|
156
|
-
end
|
152
|
+
# Evaluate the class and see if it is valid.
|
153
|
+
# Can accept any parameter for 'on' event,
|
154
|
+
# and defaults to :save
|
157
155
|
|
158
|
-
|
159
|
-
|
160
|
-
end
|
161
|
-
}
|
156
|
+
def validate(on = :save)
|
157
|
+
@errors = Errors.new
|
162
158
|
|
163
|
-
|
159
|
+
return if self.class.validations.length == 0
|
160
|
+
|
161
|
+
for event, block in self.class.validations
|
162
|
+
block.call(self) if event == on.to_sym
|
163
|
+
end
|
164
164
|
end
|
165
165
|
|
166
166
|
on_included %{
|
@@ -178,7 +178,6 @@ module Validation
|
|
178
178
|
# Implements the Validation meta-language.
|
179
179
|
|
180
180
|
module ClassMethods
|
181
|
-
|
182
181
|
# Validates that the attributes have a values, ie they are
|
183
182
|
# neither nil or empty.
|
184
183
|
#
|
@@ -187,23 +186,13 @@ module Validation
|
|
187
186
|
# validate_value :param, :msg => 'No confirmation'
|
188
187
|
|
189
188
|
def validate_value(*params)
|
190
|
-
c =
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
for name in params
|
198
|
-
code = %{
|
199
|
-
if obj.#{name}.nil?
|
200
|
-
errors.add(:#{name}, '#{c[:msg]}')
|
201
|
-
elsif obj.#{name}.respond_to?(:empty?)
|
202
|
-
errors.add(:#{name}, '#{c[:msg]}') if obj.#{name}.empty?
|
203
|
-
end
|
204
|
-
}
|
205
|
-
|
206
|
-
validations! << [code, c[:on]]
|
189
|
+
c = parse_config(params, :msg => Glue::Validation::Errors.no_value, :on => :save)
|
190
|
+
|
191
|
+
params.each do |field|
|
192
|
+
define_validation(:value, field, c[:on]) do |obj|
|
193
|
+
value = obj.send(field)
|
194
|
+
obj.errors.add(field, c[:msg]) if value.nil? || (value.respond_to?(:empty) && value.empty?)
|
195
|
+
end
|
207
196
|
end
|
208
197
|
end
|
209
198
|
|
@@ -214,25 +203,20 @@ module Validation
|
|
214
203
|
# validate_confirmation :password, :msg => 'No confirmation'
|
215
204
|
|
216
205
|
def validate_confirmation(*params)
|
217
|
-
c =
|
206
|
+
c = parse_config(params,
|
218
207
|
:msg => Glue::Validation::Errors.no_confirmation,
|
219
208
|
:postfix => Glue::Validation.confirmation_postfix,
|
220
209
|
:on => :save
|
221
|
-
|
222
|
-
c.update(params.pop) if params.last.is_a?(Hash)
|
210
|
+
)
|
223
211
|
|
212
|
+
params.each do |field|
|
213
|
+
confirm_name = field.to_s + c[:postfix]
|
214
|
+
attr_accessor confirm_name.to_sym
|
224
215
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
code = %{
|
230
|
-
if obj.#{confirm_name}.nil? or (obj.#{confirm_name} != obj.#{name})
|
231
|
-
errors.add(:#{name}, '#{c[:msg]}')
|
232
|
-
end
|
233
|
-
}
|
234
|
-
|
235
|
-
validations! << [code, c[:on]]
|
216
|
+
define_validation(:confirmation, field, c[:on]) do |obj|
|
217
|
+
value = obj.send(field)
|
218
|
+
obj.errors.add(field, c[:msg]) if value.nil? or obj.send(confirm_name) != value
|
219
|
+
end
|
236
220
|
end
|
237
221
|
end
|
238
222
|
|
@@ -246,33 +230,24 @@ module Validation
|
|
246
230
|
#--
|
247
231
|
# FIXME: how to get the Regexp options?
|
248
232
|
#++
|
249
|
-
|
233
|
+
|
250
234
|
def validate_format(*params)
|
251
|
-
c =
|
235
|
+
c = parse_config(params,
|
252
236
|
:format => nil,
|
253
237
|
:msg_no_value => Glue::Validation::Errors.no_value,
|
254
238
|
:msg => Glue::Validation::Errors.invalid_format,
|
255
239
|
:on => :save
|
256
|
-
|
257
|
-
|
258
|
-
|
240
|
+
)
|
241
|
+
|
259
242
|
unless c[:format].is_a?(Regexp)
|
260
|
-
raise
|
261
|
-
'A regular expression must be supplied as the :format option')
|
243
|
+
raise ArgumentError, "A regular expression must be supplied as the :format option for validate_format."
|
262
244
|
end
|
263
245
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
unless obj.#{name}.to_s.match(/#{c[:format].source}/)
|
270
|
-
errors.add(:#{name}, '#{c[:msg]}')
|
271
|
-
end
|
272
|
-
end;
|
273
|
-
}
|
274
|
-
|
275
|
-
validations! << [code, c[:on]]
|
246
|
+
params.each do |field|
|
247
|
+
define_validation(:format, field, c[:on]) do |obj|
|
248
|
+
value = obj.send(field)
|
249
|
+
obj.errors.add(field, c[:msg]) if value.nil? or not value.to_s.match(c[:format].source)
|
250
|
+
end
|
276
251
|
end
|
277
252
|
end
|
278
253
|
|
@@ -285,85 +260,62 @@ module Validation
|
|
285
260
|
# validate_length :name, :range => 2..30
|
286
261
|
# validate_length :name, :length => 15, :msg => 'Name should be %d chars long'
|
287
262
|
|
263
|
+
LENGTHS = [:min, :max, :range, :length].freeze
|
264
|
+
|
288
265
|
def validate_length(*params)
|
289
|
-
c =
|
290
|
-
|
266
|
+
c = parse_config(params,
|
267
|
+
# :lengths => {:min => nil, :max => nil, :range => nil, :length => nil},
|
268
|
+
:min => nil, :max => nil, :range => nil, :length => nil,
|
291
269
|
:msg => nil,
|
292
270
|
:msg_no_value => Glue::Validation::Errors.no_value,
|
293
271
|
:msg_short => Glue::Validation::Errors.too_short,
|
294
272
|
:msg_long => Glue::Validation::Errors.too_long,
|
273
|
+
:msg_invalid => Glue::Validation::Errors.invalid_length,
|
295
274
|
:on => :save
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
if count == 0
|
306
|
-
raise(ArgumentError,
|
307
|
-
'One of :min, :max, :range, :length must be provided!')
|
308
|
-
end
|
309
|
-
|
310
|
-
if count > 1
|
311
|
-
raise(ArgumentError,
|
312
|
-
'The :min, :max, :range, :length options are mutually exclusive!')
|
275
|
+
)
|
276
|
+
|
277
|
+
length_params = c.reject {|k,v| !LENGTHS.include?(k) || v.nil? }
|
278
|
+
valid_count = length_params.reject{|k,v| v.nil?}.length
|
279
|
+
|
280
|
+
if valid_count == 0
|
281
|
+
raise ArgumentError, 'One of :min, :max, :range, or :length must be provided!'
|
282
|
+
elsif valid_count > 1
|
283
|
+
raise ArgumentError, 'You can only use one of :min, :max, :range, or :length options!'
|
313
284
|
end
|
314
285
|
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
msg
|
344
|
-
|
345
|
-
|
346
|
-
elsif obj.#{name}.to_s.length > #{range.last}
|
347
|
-
msg = '#{c[:msg_long]}'
|
348
|
-
msg = (msg % #{range.last}) rescue msg
|
349
|
-
errors.add(:#{name}, msg)
|
350
|
-
end;
|
351
|
-
}
|
352
|
-
elsif length
|
353
|
-
c[:msg] ||= Glue::Validation::Errors.invalid_length
|
354
|
-
code = %{
|
355
|
-
if obj.#{name}.nil?
|
356
|
-
errors.add(:#{name}, '#{c[:msg_no_value]}')
|
357
|
-
elsif obj.#{name}.to_s.length != #{length}
|
358
|
-
msg = '#{c[:msg]}'
|
359
|
-
msg = (msg % #{length}) rescue msg
|
360
|
-
errors.add(:#{name}, msg)
|
361
|
-
end;
|
362
|
-
}
|
286
|
+
operation, required = length_params.keys[0], length_params.values[0]
|
287
|
+
|
288
|
+
params.each do |field|
|
289
|
+
define_validation(:length, field, c[:on]) do |obj|
|
290
|
+
msg = c[:msg]
|
291
|
+
value = obj.send(field)
|
292
|
+
if value.nil?
|
293
|
+
obj.errors.add(field, c[:msg_no_value])
|
294
|
+
else
|
295
|
+
case operation
|
296
|
+
when :min
|
297
|
+
msg ||= c[:msg_short]
|
298
|
+
obj.errors.add(field, msg % required) if value.length < required
|
299
|
+
when :max
|
300
|
+
msg ||= c[:msg_long]
|
301
|
+
obj.errors.add(field, msg % required) if value.length > required
|
302
|
+
when :range
|
303
|
+
min, max = required.first, required.last
|
304
|
+
if value.length < min
|
305
|
+
msg ||= c[:msg_short]
|
306
|
+
obj.errors.add(field, msg % min)
|
307
|
+
end
|
308
|
+
if value.length > max
|
309
|
+
msg ||= c[:msg_long]
|
310
|
+
obj.errors.add(field, msg % min)
|
311
|
+
end
|
312
|
+
when :length
|
313
|
+
msg ||= c[:msg_invalid]
|
314
|
+
obj.errors.add(field, msg % required) if value.length != required
|
315
|
+
end
|
316
|
+
end
|
363
317
|
end
|
364
|
-
|
365
|
-
validations! << [code, c[:on]]
|
366
|
-
end
|
318
|
+
end
|
367
319
|
end
|
368
320
|
|
369
321
|
# Validates that the attributes are included in
|
@@ -375,35 +327,24 @@ module Validation
|
|
375
327
|
# validate_inclusion :age, :in => 5..99
|
376
328
|
|
377
329
|
def validate_inclusion(*params)
|
378
|
-
c =
|
330
|
+
c = parse_config(params,
|
379
331
|
:in => nil,
|
380
332
|
:msg => Glue::Validation::Errors.no_inclusion,
|
381
333
|
:allow_nil => false,
|
382
334
|
:on => :save
|
383
|
-
|
384
|
-
|
385
|
-
|
335
|
+
)
|
336
|
+
|
386
337
|
unless c[:in].respond_to?('include?')
|
387
|
-
raise(ArgumentError,
|
388
|
-
'An object that responds to #include? must be supplied as the :in option')
|
338
|
+
raise(ArgumentError, 'An object that responds to #include? must be supplied as the :in option')
|
389
339
|
end
|
390
340
|
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
}
|
398
|
-
else
|
399
|
-
code = %{
|
400
|
-
unless (#{c[:in].inspect}).include?(obj.#{name})
|
401
|
-
errors.add(:#{name}, '#{c[:msg]}')
|
402
|
-
end;
|
403
|
-
}
|
341
|
+
params.each do |field|
|
342
|
+
define_validation(:inclusion, field, c[:on]) do |obj|
|
343
|
+
value = obj.send(field)
|
344
|
+
unless (c[:allow_nil] && value.nil?) or c[:in].include?(value)
|
345
|
+
obj.errors.add(field, c[:msg])
|
346
|
+
end
|
404
347
|
end
|
405
|
-
|
406
|
-
validations! << [code, c[:on]]
|
407
348
|
end
|
408
349
|
end
|
409
350
|
|
@@ -423,35 +364,67 @@ module Validation
|
|
423
364
|
# validate_numeric :age, :msg => 'Must be an integer'
|
424
365
|
|
425
366
|
def validate_numeric(*params)
|
426
|
-
c =
|
367
|
+
c = parse_config(params,
|
427
368
|
:integer => false,
|
428
369
|
:msg => Glue::Validation::Errors.no_numeric,
|
429
370
|
:on => :save
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
unless
|
437
|
-
errors.add(
|
438
|
-
end
|
439
|
-
|
440
|
-
else
|
441
|
-
code = %{
|
371
|
+
)
|
372
|
+
|
373
|
+
params.each do |field|
|
374
|
+
define_validation(:numeric, field, c[:on]) do |obj|
|
375
|
+
value = obj.send(field).to_s
|
376
|
+
if c[:integer]
|
377
|
+
unless value =~ /^[\+\-]*\d+$/
|
378
|
+
obj.errors.add(field, c[:msg])
|
379
|
+
end
|
380
|
+
else
|
442
381
|
begin
|
443
|
-
|
382
|
+
Float(value)
|
444
383
|
rescue ArgumentError, TypeError
|
445
|
-
errors.add(
|
446
|
-
end
|
447
|
-
|
384
|
+
obj.errors.add(field, c[:msg])
|
385
|
+
end
|
386
|
+
end
|
448
387
|
end
|
449
|
-
|
450
|
-
validations! << [code, c[:on]]
|
451
|
-
end
|
388
|
+
end
|
452
389
|
end
|
453
390
|
alias_method :validate_numericality, :validate_numeric
|
454
391
|
|
392
|
+
protected
|
393
|
+
|
394
|
+
# Parse the configuration for a validation by comparing
|
395
|
+
# the default options with the user-specified options,
|
396
|
+
# and returning the results.
|
397
|
+
|
398
|
+
def parse_config(params, defaults = {})
|
399
|
+
defaults.update(params.pop) if params.last.is_a?(Hash)
|
400
|
+
defaults
|
401
|
+
end
|
402
|
+
|
403
|
+
# Define a validation for this class on the specified event.
|
404
|
+
# Specify the on event for when this validation should be
|
405
|
+
# checked.
|
406
|
+
#
|
407
|
+
# An extra check is performed to avoid multiple validations.
|
408
|
+
#
|
409
|
+
# This example creates a validation for the 'save' event,
|
410
|
+
# and will add an error to the record if the 'name' property
|
411
|
+
# of the validating object is nil.
|
412
|
+
#
|
413
|
+
# === Example
|
414
|
+
#
|
415
|
+
# field = :name
|
416
|
+
#
|
417
|
+
# define_validation(:value, field, :save) do |object|
|
418
|
+
# object.errors.add(field, "You must set a value for #{field}") if object.send(field).nil?
|
419
|
+
# end
|
420
|
+
|
421
|
+
def define_validation(val, field, on = :save, &block)
|
422
|
+
vk = Validation::Key.new(val, field)
|
423
|
+
unless validations.find { |v| v[2] == vk }
|
424
|
+
validations! << [on, block, vk]
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
455
428
|
end
|
456
429
|
|
457
430
|
end
|
@@ -462,4 +435,6 @@ class Module # :nodoc: all
|
|
462
435
|
include Glue::Validation::ClassMethods
|
463
436
|
end
|
464
437
|
|
465
|
-
# * George Moschovitis
|
438
|
+
# * George Moschovitis <gm@navel.gr>
|
439
|
+
# * Brian Bugh <brian@xsi-design.com>
|
440
|
+
# * Bryan Soto <bryan.a.soto@gmail.com>
|
@@ -0,0 +1,37 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), '..', '..', 'lib')
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'glue/accumulate'
|
5
|
+
|
6
|
+
class TC_Accumulate < Test::Unit::TestCase # :nodoc: all
|
7
|
+
|
8
|
+
class Project
|
9
|
+
attr_accessor :groups
|
10
|
+
end
|
11
|
+
|
12
|
+
class Group
|
13
|
+
attr_accessor :users
|
14
|
+
end
|
15
|
+
|
16
|
+
class User
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_all
|
20
|
+
pr = Project.new
|
21
|
+
gr1 = Group.new
|
22
|
+
gr2 = Group.new
|
23
|
+
us1 = User.new
|
24
|
+
us2 = User.new
|
25
|
+
gr1.users = [ us1 ]
|
26
|
+
gr2.users = [ us2 ]
|
27
|
+
pr.groups = [ gr1, gr2 ]
|
28
|
+
|
29
|
+
users = pr.groups.accumulate.users
|
30
|
+
assert_equal 2, users.size
|
31
|
+
assert_equal us1, users[0]
|
32
|
+
assert_equal us2, users[1]
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
# * George Moschovitis <gm@navel.gr>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), '..', '..', 'lib')
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
require 'glue'
|
6
|
+
require 'glue/property'
|
7
|
+
require 'glue/validation'
|
8
|
+
|
9
|
+
class TC_MultiValidation < Test::Unit::TestCase # :nodoc: all
|
10
|
+
|
11
|
+
def test_all
|
12
|
+
file = File.join(File.dirname($0), 'multi_validations_model.rb')
|
13
|
+
load file
|
14
|
+
assert_equal 2, User.validations.size
|
15
|
+
load file
|
16
|
+
assert_equal 2, User.validations.size
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
data/test/glue/tc_validation.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
$:.unshift File.join(File.dirname(__FILE__), '..', '..', 'lib')
|
2
|
+
$:.unshift File.join(File.dirname(__FILE__), '..', '..', '..', 'og', 'lib')
|
2
3
|
|
3
4
|
require 'test/unit'
|
4
5
|
|
@@ -78,7 +79,7 @@ class TC_Validation < Test::Unit::TestCase # :nodoc: all
|
|
78
79
|
include Mixin
|
79
80
|
validate_length :value, :min => 3
|
80
81
|
end
|
81
|
-
|
82
|
+
|
82
83
|
def test_validate_value
|
83
84
|
d = Dummy1.new
|
84
85
|
|
@@ -90,7 +91,7 @@ class TC_Validation < Test::Unit::TestCase # :nodoc: all
|
|
90
91
|
d.str = 'yeah'
|
91
92
|
assert d.valid?
|
92
93
|
end
|
93
|
-
|
94
|
+
|
94
95
|
def test_validate_confirmation
|
95
96
|
d = Dummy2.new
|
96
97
|
|
@@ -212,4 +213,5 @@ class TC_Validation < Test::Unit::TestCase # :nodoc: all
|
|
212
213
|
d.age = 123
|
213
214
|
assert d.valid?
|
214
215
|
end
|
216
|
+
|
215
217
|
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
path = File.expand_path(File.dirname($0))
|
2
|
+
$: << path + '/og/lib'
|
3
|
+
$: << path + '/glue/lib'
|
4
|
+
require 'test/unit'
|
5
|
+
require 'og'
|
6
|
+
|
7
|
+
module UnitTestClasses
|
8
|
+
class NoValidation
|
9
|
+
property :prop, String
|
10
|
+
|
11
|
+
def initialize(prop)
|
12
|
+
@prop = prop
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class UniqueValidation < NoValidation
|
17
|
+
property :slop, String
|
18
|
+
validate_unique :prop, :slop
|
19
|
+
end
|
20
|
+
|
21
|
+
class ValueValidation < NoValidation
|
22
|
+
validate_value :prop
|
23
|
+
end
|
24
|
+
|
25
|
+
class ConfirmValidation < NoValidation
|
26
|
+
validate_confirmation :prop
|
27
|
+
end
|
28
|
+
|
29
|
+
class FormatValidation < NoValidation
|
30
|
+
validate_format :prop, :format => /^\w\d\w-\d\w\d$/
|
31
|
+
end
|
32
|
+
|
33
|
+
class MinValidation < NoValidation
|
34
|
+
validate_length :prop, :min => 4
|
35
|
+
end
|
36
|
+
|
37
|
+
class MaxValidation < NoValidation
|
38
|
+
validate_length :prop, :max => 8
|
39
|
+
end
|
40
|
+
|
41
|
+
class RangeValidation < NoValidation
|
42
|
+
validate_length :prop, :range => 10..20
|
43
|
+
end
|
44
|
+
|
45
|
+
class LengthValidation < NoValidation
|
46
|
+
validate_length :prop, :length => 8
|
47
|
+
end
|
48
|
+
|
49
|
+
class InclusionValidation < NoValidation
|
50
|
+
validate_inclusion :prop, :in => %w{ Male Female }
|
51
|
+
end
|
52
|
+
|
53
|
+
class NumericValidation < NoValidation
|
54
|
+
property :prop, Fixnum
|
55
|
+
validate_numeric :prop, :integer => true
|
56
|
+
|
57
|
+
prop_accessor :prop2, Float
|
58
|
+
validate_numeric :prop2
|
59
|
+
end
|
60
|
+
|
61
|
+
class RelatedClass
|
62
|
+
prop_accessor :test, String
|
63
|
+
belongs_to :relation_validation, RelationValidation
|
64
|
+
end
|
65
|
+
|
66
|
+
class RelationValidation < NoValidation
|
67
|
+
has_many :relations, RelatedClass
|
68
|
+
validate_related :relations
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
config = {
|
73
|
+
:destroy => true,
|
74
|
+
:store => :sqlite,
|
75
|
+
:prop => 'dbtest_additions'
|
76
|
+
}
|
77
|
+
|
78
|
+
db = Og.setup(config)
|
79
|
+
|
80
|
+
require 'breakpoint'
|
81
|
+
module Test::Unit::Assertions
|
82
|
+
# Assert than an Og object is successfully saved and is error free.
|
83
|
+
def assert_saved_without_errors(obj)
|
84
|
+
assert_block(obj.class.to_s + ' was expected to save, but failed.') do
|
85
|
+
obj.save
|
86
|
+
obj.saved? && obj.errors.size == 0
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Assert that an Og object did not save and has errors.
|
91
|
+
def assert_save_failed(obj)
|
92
|
+
assert_block(obj.class.to_s + ' saving was expected to fail, but it succeeded.') do
|
93
|
+
obj.save
|
94
|
+
!obj.saved? && obj.errors.size > 0
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Unit tests for the Og validation system
|
100
|
+
class TestFixes < Test::Unit::TestCase
|
101
|
+
include UnitTestClasses
|
102
|
+
|
103
|
+
def test_no_validation
|
104
|
+
t = NoValidation.new('nochecking')
|
105
|
+
assert_saved_without_errors t
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_validation_unique
|
109
|
+
assert_saved_without_errors UniqueValidation.new('test')
|
110
|
+
assert_save_failed UniqueValidation.new('test')
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_value_validation
|
114
|
+
assert_saved_without_errors ValueValidation.new('value')
|
115
|
+
assert_save_failed ValueValidation.new(nil)
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_confirmation
|
119
|
+
prop = 'confirm'
|
120
|
+
c = ConfirmValidation.new(prop)
|
121
|
+
c.prop_confirmation = prop
|
122
|
+
assert_saved_without_errors c
|
123
|
+
|
124
|
+
assert_save_failed ConfirmValidation.new(prop)
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_format
|
128
|
+
assert_saved_without_errors FormatValidation.new('a1b-2c3')
|
129
|
+
assert_save_failed FormatValidation.new('banana_pudding')
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_length_min
|
133
|
+
assert_saved_without_errors MinValidation.new('12345')
|
134
|
+
assert_save_failed MinValidation.new('1')
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_length_max
|
138
|
+
assert_saved_without_errors MaxValidation.new('12345')
|
139
|
+
assert_save_failed MaxValidation.new('123456789012345')
|
140
|
+
end
|
141
|
+
|
142
|
+
def test_length_range
|
143
|
+
l = RangeValidation.new('12345678901234')
|
144
|
+
assert_saved_without_errors l
|
145
|
+
|
146
|
+
l = RangeValidation.new('12')
|
147
|
+
assert_save_failed l
|
148
|
+
|
149
|
+
l = RangeValidation.new('1234567890123456789012345')
|
150
|
+
assert_save_failed l
|
151
|
+
end
|
152
|
+
|
153
|
+
def test_length_length
|
154
|
+
assert_saved_without_errors LengthValidation.new('12345678')
|
155
|
+
assert_save_failed LengthValidation.new('1')
|
156
|
+
assert_save_failed LengthValidation.new('123456789012345')
|
157
|
+
end
|
158
|
+
|
159
|
+
def test_inclusion
|
160
|
+
assert_saved_without_errors InclusionValidation.new('Male')
|
161
|
+
assert_save_failed InclusionValidation.new('Shemale')
|
162
|
+
end
|
163
|
+
|
164
|
+
def test_numeric
|
165
|
+
# Both valid
|
166
|
+
n = NumericValidation.new(100)
|
167
|
+
n.prop2 = 1.52
|
168
|
+
assert_saved_without_errors n
|
169
|
+
|
170
|
+
# float for required integer
|
171
|
+
n = NumericValidation.new(1.52)
|
172
|
+
n.prop2 = 1.52
|
173
|
+
assert_save_failed n
|
174
|
+
assert_equal 1, n.errors.count
|
175
|
+
|
176
|
+
# string for required integer
|
177
|
+
n = NumericValidation.new('sausage')
|
178
|
+
n.prop2 = 1.52
|
179
|
+
assert_save_failed n
|
180
|
+
assert_equal 1, n.errors.count
|
181
|
+
|
182
|
+
# string for required integer and float
|
183
|
+
n = NumericValidation.new('sausage')
|
184
|
+
n.prop2 = 'cheese'
|
185
|
+
assert_save_failed n
|
186
|
+
assert_equal 2, n.errors.count
|
187
|
+
|
188
|
+
# string for float
|
189
|
+
n = NumericValidation.new(15)
|
190
|
+
n.prop2 = 'cheese'
|
191
|
+
assert_save_failed n
|
192
|
+
assert_equal 1, n.errors.count
|
193
|
+
end
|
194
|
+
|
195
|
+
def test_related
|
196
|
+
r = RelatedClass.new
|
197
|
+
test_prop = 'example string'
|
198
|
+
r.test = test_prop
|
199
|
+
rv = RelationValidation.new('relation')
|
200
|
+
rv.relations << r
|
201
|
+
|
202
|
+
assert_saved_without_errors rv
|
203
|
+
assert_equal test_prop, rv.relations.members[0].test
|
204
|
+
|
205
|
+
# Test bad
|
206
|
+
assert_save_failed RelationValidation.new('relation')
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
|
5
|
+
require 'og'
|
6
|
+
|
7
|
+
class ManagedClass
|
8
|
+
prop_accessor :thing
|
9
|
+
|
10
|
+
validate_unique :thing
|
11
|
+
end
|
12
|
+
|
13
|
+
Og.start
|
14
|
+
|
15
|
+
class TC_Loop < Test::Unit::TestCase
|
16
|
+
def setup
|
17
|
+
@thing_1 = ManagedClass.new
|
18
|
+
@thing_1.thing = 'thing'
|
19
|
+
@thing_1.save
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_loop
|
23
|
+
thing_2 = ManagedClass.new
|
24
|
+
thing_2.thing = @thing_1.thing
|
25
|
+
# assert_raises(NoMethodError) {
|
26
|
+
# thing_2.valid?
|
27
|
+
# assert thing_2.errors
|
28
|
+
# }
|
29
|
+
end
|
30
|
+
|
31
|
+
def teardown
|
32
|
+
ManagedClass.all.each { |i| i.delete }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# * Bryan Soto <bryan.a.soto@gmail.com>
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.10
|
|
3
3
|
specification_version: 1
|
4
4
|
name: glue
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
date: 2005-
|
6
|
+
version: 0.26.0
|
7
|
+
date: 2005-12-21
|
8
8
|
summary: Utility methods and classes for Nitro.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -42,6 +42,7 @@ files:
|
|
42
42
|
- lib/glue
|
43
43
|
- lib/glue.rb
|
44
44
|
- lib/html
|
45
|
+
- lib/glue/accumulate.rb
|
45
46
|
- lib/glue/aspects.rb
|
46
47
|
- lib/glue/attribute.rb
|
47
48
|
- lib/glue/builder
|
@@ -50,6 +51,7 @@ files:
|
|
50
51
|
- lib/glue/expirable.rb
|
51
52
|
- lib/glue/fixture.rb
|
52
53
|
- lib/glue/flexob.rb
|
54
|
+
- lib/glue/html.rb
|
53
55
|
- lib/glue/localization.rb
|
54
56
|
- lib/glue/logger.rb
|
55
57
|
- lib/glue/mail.rb
|
@@ -77,6 +79,8 @@ files:
|
|
77
79
|
- test/fixture/article.yml
|
78
80
|
- test/fixture/user.yml
|
79
81
|
- test/glue/builder
|
82
|
+
- test/glue/multi_validations_model.rb
|
83
|
+
- test/glue/tc_accumulate.rb
|
80
84
|
- test/glue/tc_aspects.rb
|
81
85
|
- test/glue/tc_attribute.rb
|
82
86
|
- test/glue/tc_builder.rb
|
@@ -86,12 +90,15 @@ files:
|
|
86
90
|
- test/glue/tc_localization.rb
|
87
91
|
- test/glue/tc_logger.rb
|
88
92
|
- test/glue/tc_mail.rb
|
93
|
+
- test/glue/tc_multi_validations.rb
|
89
94
|
- test/glue/tc_property.rb
|
90
95
|
- test/glue/tc_property_mixins.rb
|
91
96
|
- test/glue/tc_property_type_checking.rb
|
92
97
|
- test/glue/tc_template.rb
|
93
98
|
- test/glue/tc_uri.rb
|
94
99
|
- test/glue/tc_validation.rb
|
100
|
+
- test/glue/tc_validation2.rb
|
101
|
+
- test/glue/tc_validation_loop.rb
|
95
102
|
- test/glue/builder/tc_xml.rb
|
96
103
|
- test/public/dummy_mailer
|
97
104
|
- test/public/dummy_mailer/registration.xhtml
|