glue 0.25.0 → 0.26.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|