kiss 0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +19 -0
- data/Rakefile +33 -0
- data/VERSION +1 -0
- data/lib/kiss/action.rb +204 -0
- data/lib/kiss/controller_accessors.rb +101 -0
- data/lib/kiss/exception_report.rb +359 -0
- data/lib/kiss/form/field.rb +296 -0
- data/lib/kiss/form.rb +414 -0
- data/lib/kiss/format.rb +80 -0
- data/lib/kiss/hacks.rb +140 -0
- data/lib/kiss/iterator.rb +56 -0
- data/lib/kiss/mailer.rb +92 -0
- data/lib/kiss/model.rb +114 -0
- data/lib/kiss/rack/bench.rb +131 -0
- data/lib/kiss/rack/email_errors.rb +64 -0
- data/lib/kiss/rack/facebook.rb +28 -0
- data/lib/kiss/rack/file_not_found.rb +42 -0
- data/lib/kiss/rack/log_exceptions.rb +23 -0
- data/lib/kiss/rack/show_debug.rb +82 -0
- data/lib/kiss/rack/show_exceptions.rb +27 -0
- data/lib/kiss/sequel_mysql.rb +23 -0
- data/lib/kiss/sequel_session.rb +166 -0
- data/lib/kiss/template_methods.rb +125 -0
- data/lib/kiss.rb +725 -0
- metadata +108 -0
data/lib/kiss/form.rb
ADDED
@@ -0,0 +1,414 @@
|
|
1
|
+
class Kiss
|
2
|
+
class Form
|
3
|
+
module AttributesSetter
|
4
|
+
def set_attributes(attrs_original,required = [])
|
5
|
+
unless attrs_original.is_a?(Hash) then
|
6
|
+
raise "first parameter must be a hash of attributes: #{attrs_original}"
|
7
|
+
end
|
8
|
+
|
9
|
+
attrs = attrs_original.clone
|
10
|
+
|
11
|
+
required.each do |key|
|
12
|
+
raise "missing required parameter '#{key}'" unless attrs[key]
|
13
|
+
send("#{key}=", attrs[key])
|
14
|
+
attrs.delete(key)
|
15
|
+
end
|
16
|
+
|
17
|
+
attrs.each_pair do |key,value|
|
18
|
+
send("#{key}=", value)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
include Kiss::Form::AttributesSetter
|
23
|
+
|
24
|
+
require 'kiss/form/field';
|
25
|
+
|
26
|
+
attr_accessor(
|
27
|
+
:name,:params,:submitted,:action,:controller,:id,:url,:request,
|
28
|
+
:object,:fields,:fields_hash,:method,:enctype,:errors,:submit,:cancel,:style,:class,
|
29
|
+
:has_field_errors,:error_class,:field_error_class,:mark_required,:object
|
30
|
+
)
|
31
|
+
|
32
|
+
|
33
|
+
@@field_types = {
|
34
|
+
:text => TextField,
|
35
|
+
:hidden => HiddenField,
|
36
|
+
:textarea => TextAreaField,
|
37
|
+
:password => PasswordField,
|
38
|
+
#:multitext => MultiTextField,
|
39
|
+
:boolean => BooleanField,
|
40
|
+
:file => FileField,
|
41
|
+
:select => SelectField,
|
42
|
+
:radio => RadioField,
|
43
|
+
:checkbox => CheckboxField,
|
44
|
+
:multiselect => MultiSelectField,
|
45
|
+
:submit => SubmitField,
|
46
|
+
}
|
47
|
+
|
48
|
+
|
49
|
+
# Creates a new form object with specified attributes.
|
50
|
+
def initialize(attrs)
|
51
|
+
set_attributes(attrs,[:name,:action])
|
52
|
+
|
53
|
+
raise "Missing required option 'name'" unless @name && (@name != '')
|
54
|
+
|
55
|
+
@method ||= 'post'
|
56
|
+
|
57
|
+
# move field definitions to different var
|
58
|
+
# want to use @fields for array of Field objects
|
59
|
+
@fields_specs = @fields || []
|
60
|
+
|
61
|
+
@fields = []
|
62
|
+
@fields_hash = {}
|
63
|
+
@default_values ||= {}
|
64
|
+
@params = {}
|
65
|
+
|
66
|
+
@fields_specs.each do |field|
|
67
|
+
# create field here
|
68
|
+
add_field(field)
|
69
|
+
end
|
70
|
+
|
71
|
+
@errors = []
|
72
|
+
@field_errors = {}
|
73
|
+
end
|
74
|
+
|
75
|
+
# Adds a section break, which causes form's table to close and new table to open when form is rendered.
|
76
|
+
def add_section_break
|
77
|
+
@fields << :section_break
|
78
|
+
end
|
79
|
+
|
80
|
+
# Creates and adds a field to the form, according to specified attributes.
|
81
|
+
def add_field(attrs)
|
82
|
+
raise "added second field named '#{attrs[:name]}'; field names must be unique" if @fields_hash[attrs[:name]]
|
83
|
+
|
84
|
+
attrs[:type] ||= :text
|
85
|
+
type = attrs[:type].to_sym
|
86
|
+
unless @@field_types.has_key?(type)
|
87
|
+
type = attrs[:type] = :text
|
88
|
+
end
|
89
|
+
|
90
|
+
@has_required_fields ||= attrs[:required]
|
91
|
+
|
92
|
+
field_class = @@field_types[type]
|
93
|
+
field = field_class.new(attrs)
|
94
|
+
|
95
|
+
field_name = field.name.to_s
|
96
|
+
field.form = self
|
97
|
+
|
98
|
+
unless type == :submit
|
99
|
+
@fields << field
|
100
|
+
@fields_hash[field_name] = field
|
101
|
+
end
|
102
|
+
|
103
|
+
@enctype = 'multipart/form-data' if field.type == :file
|
104
|
+
|
105
|
+
# if param submitted, set value of field to match
|
106
|
+
if (param = @params[field_name])
|
107
|
+
field.value = param
|
108
|
+
elsif @object && !attrs.has_key?(:value)
|
109
|
+
value = @object[field_name.to_sym] || @object[field_name]
|
110
|
+
|
111
|
+
case field.format
|
112
|
+
when :date:
|
113
|
+
field.value = value ? value.strftime("%m/%d/%Y") : ''
|
114
|
+
when :datetime:
|
115
|
+
field.value = value ? value.strftime("%m/%d/%Y %I:%M%p") : ''
|
116
|
+
else
|
117
|
+
field.value = value.to_s
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
field
|
122
|
+
end
|
123
|
+
|
124
|
+
# Creates and adds set of submit buttons to the form, per specified attributes.
|
125
|
+
def add_submit(attrs)
|
126
|
+
@submit = add_field({
|
127
|
+
:type => :submit,
|
128
|
+
:name => 'submit',
|
129
|
+
:save => false
|
130
|
+
}.merge(attrs) )
|
131
|
+
end
|
132
|
+
|
133
|
+
# Gets hash of form values ready to be saved to Sequel::Model object.
|
134
|
+
def sequel_values
|
135
|
+
@sequel_values ||= begin
|
136
|
+
hash = {}
|
137
|
+
@fields.each {|field| hash[field.name] = field.sequel_value unless field == :section_break }
|
138
|
+
hash
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Gets hash of form values.
|
143
|
+
def values
|
144
|
+
@values ||= begin
|
145
|
+
hash = {}
|
146
|
+
@fields.each {|field| hash[field.name] = field.value unless field == :section_break}
|
147
|
+
hash
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Add error message to be rendered with form.
|
152
|
+
# If multiple args given, first arg is field name, and second arg (error message)
|
153
|
+
# will render next to specified field.
|
154
|
+
def add_error(*args)
|
155
|
+
if args.size > 2
|
156
|
+
raise 'too many args'
|
157
|
+
elsif args.size == 0
|
158
|
+
raise 'at least one arg required'
|
159
|
+
end
|
160
|
+
|
161
|
+
# args.size == 1 or 2
|
162
|
+
if args.size == 1
|
163
|
+
if args[0].is_a?(String)
|
164
|
+
@errors << args[0]
|
165
|
+
return
|
166
|
+
else
|
167
|
+
field_name = args[0].field_name.to_s
|
168
|
+
message = args[0].message
|
169
|
+
end
|
170
|
+
else # args.size == 2
|
171
|
+
# args == [field_name, message]
|
172
|
+
field_name = args[0].to_s
|
173
|
+
message = args[1]
|
174
|
+
end
|
175
|
+
return @fields_hash[field_name].add_error(message)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Validates form values against fields' format and required attributes,
|
179
|
+
# and returns values unless form has errors.
|
180
|
+
def validate
|
181
|
+
return nil unless submitted
|
182
|
+
|
183
|
+
@fields.each {|field| field.validate unless field == :section_break }
|
184
|
+
|
185
|
+
has_errors ? nil : values
|
186
|
+
end
|
187
|
+
|
188
|
+
# Saves form values to Sequel::Model object.
|
189
|
+
def save(object = @object)
|
190
|
+
@fields.each do |field|
|
191
|
+
next if field == :section_break
|
192
|
+
# ignore fields whose name starts with underscore
|
193
|
+
next if field.name =~ /\A\_/
|
194
|
+
# don't save 'ignore' fields to the database
|
195
|
+
next if field.ignore || !field.save
|
196
|
+
|
197
|
+
object[field.name.to_sym] = field.sequel_value
|
198
|
+
end
|
199
|
+
|
200
|
+
object.save
|
201
|
+
end
|
202
|
+
|
203
|
+
# Returns true if form has errors.
|
204
|
+
def has_errors
|
205
|
+
(@errors.size > 0 || @has_field_errors)
|
206
|
+
end
|
207
|
+
|
208
|
+
# Checks whether form was submitted and accepted by user and, if so,
|
209
|
+
# whether form validates.
|
210
|
+
# If form validates, saves form values to form's Sequel::Model object
|
211
|
+
# and returns true. Otherwise, returns false.
|
212
|
+
def process(object = @object)
|
213
|
+
return false unless submitted
|
214
|
+
|
215
|
+
if accepted
|
216
|
+
validate
|
217
|
+
return false if has_errors
|
218
|
+
|
219
|
+
save(object)
|
220
|
+
end
|
221
|
+
|
222
|
+
return true
|
223
|
+
end
|
224
|
+
|
225
|
+
# Returns true if user submitted form with non-cancel submit button
|
226
|
+
# (non-nil submit param, not equal to cancel submit button value).
|
227
|
+
def accepted
|
228
|
+
raise 'form missing submit field' unless @submit
|
229
|
+
return params[@submit.name] != @submit.cancel
|
230
|
+
end
|
231
|
+
|
232
|
+
# Renders error HTML block for top of form, and returns as string.
|
233
|
+
def errors_html
|
234
|
+
return nil unless has_errors
|
235
|
+
|
236
|
+
@errors << "Please remedy the errors shown below." if @has_field_errors
|
237
|
+
|
238
|
+
if @errors.size == 1
|
239
|
+
content = @errors[0]
|
240
|
+
else
|
241
|
+
content = "<ul>" + @errors.map {|e| "<li>#{e}</li>"}.join + "</ul>"
|
242
|
+
end
|
243
|
+
|
244
|
+
@errors.pop if @has_field_errors
|
245
|
+
|
246
|
+
plural = @errors.size > 1 || @has_field_errors ? 's' : nil
|
247
|
+
%Q(<table border=0 cellspacing=0><tr class="kiss_error"><th>Error#{plural}</th></td><td class="kiss_required"></td><td>#{content}</td></tr></table>)
|
248
|
+
end
|
249
|
+
|
250
|
+
# Renders current action using form's HTML as action render content.
|
251
|
+
def render(options = {})
|
252
|
+
@request.render options.merge(:content => html)
|
253
|
+
end
|
254
|
+
|
255
|
+
# Renders beginning of form (form open tag and form/field/error styles).
|
256
|
+
def html_open
|
257
|
+
@error_class ||= 'kiss_form_error_message'
|
258
|
+
@field_error_class ||= @error_class
|
259
|
+
|
260
|
+
# form tag
|
261
|
+
form_attrs = ['method','enctype','class','style'].map do |attr|
|
262
|
+
"#{attr}=\"#{send attr}\""
|
263
|
+
end.join(' ')
|
264
|
+
form_tag = %Q(<form action="#{@action}" #{form_attrs}><input type=hidden name="form" value="#{@name}">)
|
265
|
+
|
266
|
+
# style tag
|
267
|
+
styles = []
|
268
|
+
styles.push( <<-EOT
|
269
|
+
.kiss_form table {
|
270
|
+
margin-bottom: 6px;
|
271
|
+
}
|
272
|
+
.kiss_form td {
|
273
|
+
padding: 2px 4px;
|
274
|
+
}
|
275
|
+
.kiss_form tr.kiss_error {
|
276
|
+
background-color: #ff8;
|
277
|
+
}
|
278
|
+
.kiss_form tr.kiss_error th {
|
279
|
+
vertical-align: top;
|
280
|
+
padding: 2px 4px 3px 4px;
|
281
|
+
text-align: right;
|
282
|
+
background-color: #fc7;
|
283
|
+
color: #900;
|
284
|
+
border: 1px solid #f96;
|
285
|
+
border-right: none;
|
286
|
+
}
|
287
|
+
.kiss_form tr.kiss_error td {
|
288
|
+
vertical-align: top;
|
289
|
+
padding: 2px 6px 3px 6px;
|
290
|
+
border: 1px solid #f96;
|
291
|
+
border-left: none;
|
292
|
+
color: #000;
|
293
|
+
}
|
294
|
+
.kiss_form tr.kiss_error ul {
|
295
|
+
padding-left: 16px;
|
296
|
+
margin: 0;
|
297
|
+
}
|
298
|
+
.kiss_form tr.kiss_error td.kiss_required {
|
299
|
+
padding: 0;
|
300
|
+
border-right: none;
|
301
|
+
}
|
302
|
+
.kiss_form .kiss_help {
|
303
|
+
padding: 0px 3px 8px 6px;
|
304
|
+
font-size: 90%;
|
305
|
+
color: #555;
|
306
|
+
}
|
307
|
+
.kiss_form td.kiss_required {
|
308
|
+
padding: 2px 0px;
|
309
|
+
color: #c00;
|
310
|
+
}
|
311
|
+
.kiss_form td.kiss_label {
|
312
|
+
text-align: right;
|
313
|
+
white-space: nowrap;
|
314
|
+
}
|
315
|
+
.kiss_form tr.kiss_prompt td {
|
316
|
+
padding: 8px 3px 2px 4px;
|
317
|
+
}
|
318
|
+
.kiss_form tr.kiss_submit td.kiss_submit {
|
319
|
+
padding: 6px 3px;
|
320
|
+
}
|
321
|
+
.kiss_form input[type="text"],.kiss_form textarea {
|
322
|
+
width: 250px;
|
323
|
+
}
|
324
|
+
EOT
|
325
|
+
)
|
326
|
+
styles.push( <<-EOT
|
327
|
+
.kiss_form_error_message {
|
328
|
+
padding: 1px 4px 2px 4px;
|
329
|
+
border: 1px solid #f96;
|
330
|
+
background-color: #ff8;
|
331
|
+
color: #900;
|
332
|
+
font-size: 85%;
|
333
|
+
display: block;
|
334
|
+
float: left;
|
335
|
+
margin-top: 1px;
|
336
|
+
|
337
|
+
}
|
338
|
+
.kiss_form_error_message div {
|
339
|
+
color: #c00;
|
340
|
+
font-weight: bold;
|
341
|
+
}
|
342
|
+
.kiss_form_error_message ul {
|
343
|
+
padding-left: 16px;
|
344
|
+
margin: 0;
|
345
|
+
}
|
346
|
+
EOT
|
347
|
+
) if @error_class == 'kiss_form_error_message'
|
348
|
+
style_tag = styles.size == 0 ? '' : "<style>" + styles.join('') + "</style>"
|
349
|
+
|
350
|
+
# combine
|
351
|
+
return %Q(#{form_tag}#{style_tag}<div class="kiss_form">#{errors_html})
|
352
|
+
end
|
353
|
+
|
354
|
+
# Renders end of form (form close tag).
|
355
|
+
def html_close
|
356
|
+
'</div></form>'
|
357
|
+
end
|
358
|
+
|
359
|
+
# Renders form fields HTML.
|
360
|
+
def fields_html
|
361
|
+
@fields.map do |field|
|
362
|
+
field == :section_break ? begin
|
363
|
+
table_html_close + table_html_open
|
364
|
+
end : field_html(field)
|
365
|
+
end.join
|
366
|
+
end
|
367
|
+
|
368
|
+
# Renders open of form table.
|
369
|
+
def table_html_open
|
370
|
+
%Q(<table border=0 cellspacing=0>) +
|
371
|
+
(@has_required_fields && @mark_required ? %Q( <tr><td></td><td class="kiss_required">*</td><td colspan=2 class="kiss_help">Required field.</td></tr> ) : '')
|
372
|
+
end
|
373
|
+
|
374
|
+
# Renders close of form table.
|
375
|
+
def table_html_close
|
376
|
+
'</table>'
|
377
|
+
end
|
378
|
+
|
379
|
+
# Renders form submit buttons.
|
380
|
+
def submit_html
|
381
|
+
field_html(@submit)
|
382
|
+
end
|
383
|
+
|
384
|
+
# Renders complete form HTML.
|
385
|
+
def html
|
386
|
+
return [
|
387
|
+
html_open,
|
388
|
+
table_html_open,
|
389
|
+
fields_html,
|
390
|
+
submit_html,
|
391
|
+
table_html_close,
|
392
|
+
html_close
|
393
|
+
].flatten.join
|
394
|
+
end
|
395
|
+
|
396
|
+
# Renders HTML for specified form field.
|
397
|
+
def field_html(field)
|
398
|
+
type = field.type
|
399
|
+
prompt = field.prompt.to_s
|
400
|
+
label = field.label.to_s
|
401
|
+
|
402
|
+
[
|
403
|
+
(prompt != '' ? %Q(<tr class="kiss_prompt"><td colspan=2></td><td colspan=2>#{prompt}</td></tr>) : ''),
|
404
|
+
"",
|
405
|
+
%Q(<tr class="kiss_#{type}"><td class="kiss_label">),
|
406
|
+
label != '' && prompt == '' ? label + ':' : '',
|
407
|
+
'</td><td class="kiss_required">',
|
408
|
+
field.required ? @mark_required : '',
|
409
|
+
%Q(</td><td class="kiss_#{type}">),
|
410
|
+
field.html, "</td></tr>"
|
411
|
+
].join
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|
data/lib/kiss/format.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
class Kiss
|
2
|
+
# This class leftover from a previous version of Kiss and is not currently used.
|
3
|
+
# In fact, this file is not required from any other part of Kiss.
|
4
|
+
#
|
5
|
+
# But we're keeping it around in case it's useful for the anticipated rewrite of
|
6
|
+
# Kiss.validate_format.
|
7
|
+
class Format
|
8
|
+
def self.regexp
|
9
|
+
@@regexp
|
10
|
+
end
|
11
|
+
def self.error
|
12
|
+
@@error
|
13
|
+
end
|
14
|
+
|
15
|
+
class Integer < Kiss::Format
|
16
|
+
@regexp = /\A\-?\d+\Z/
|
17
|
+
@error = 'must be an integer'
|
18
|
+
end
|
19
|
+
|
20
|
+
class PositiveInteger < Kiss::Format
|
21
|
+
@regexp = /\A\d*[1-9]\d*\Z/
|
22
|
+
@error = 'must be a positive integer'
|
23
|
+
end
|
24
|
+
|
25
|
+
class UnsignedInteger < Kiss::Format
|
26
|
+
@regexp = /\A\d+\Z/
|
27
|
+
@error = 'must be a positive integer or zero'
|
28
|
+
end
|
29
|
+
|
30
|
+
class NegativeInteger < Kiss::Format
|
31
|
+
@regexp = /\A\-\d*[1-9]\d*\Z/
|
32
|
+
@error = 'must be a negative integer'
|
33
|
+
end
|
34
|
+
|
35
|
+
class AlphaNum
|
36
|
+
@regexp = /\A[a-z0-9]\Z/i
|
37
|
+
@error = 'allows only letters and numbers'
|
38
|
+
end
|
39
|
+
|
40
|
+
class Word
|
41
|
+
@regexp = /\A\w+\Z/
|
42
|
+
@error = 'allows only letters, numbers, and _'
|
43
|
+
end
|
44
|
+
|
45
|
+
class EmailAddress
|
46
|
+
@regexp = /\A[A-Z0-9._%+-]+\@([A-Z0-9-]+\.)+[A-Z]{2,4}\Z/i
|
47
|
+
@error = 'must be a valid email address'
|
48
|
+
end
|
49
|
+
|
50
|
+
class Date
|
51
|
+
@regexp = /\A\d+\D\d+(\D\d+)?\Z/
|
52
|
+
@error = 'must be a valid date'
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
@@classes = {
|
57
|
+
:integer => Kiss::Format::Integer,
|
58
|
+
|
59
|
+
:integer_positive => Kiss::Format::PositiveInteger,
|
60
|
+
:positive_integer => Kiss::Format::PositiveInteger,
|
61
|
+
:id => Kiss::Format::PositiveInteger,
|
62
|
+
|
63
|
+
:integer_unsigned => Kiss::Format::UnsignedInteger,
|
64
|
+
:unsigned_integer => Kiss::Format::UnsignedInteger,
|
65
|
+
:id_or_zero => Kiss::Format::UnsignedInteger,
|
66
|
+
:id_zero => Kiss::Format::UnsignedInteger,
|
67
|
+
|
68
|
+
:integer_negative => Kiss::Format::NegativeInteger,
|
69
|
+
:negative_integer => Kiss::Format::NegativeInteger,
|
70
|
+
|
71
|
+
:word => Kiss::Format::Word,
|
72
|
+
:alphanum => Kiss::Format::AlphaNum,
|
73
|
+
:email_address => Kiss::Format::EmailAddress,
|
74
|
+
:date => Kiss::Format::Date
|
75
|
+
}
|
76
|
+
def self.from_symbol(symbol)
|
77
|
+
return @@classes[symbol] || (raise "no format for #{symbol}")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/kiss/hacks.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
# This is a collection of hacks to enable various Ruby lnaguage enhancements
|
2
|
+
# and other features of Kiss.
|
3
|
+
|
4
|
+
# Placeholder; overloaded by Kiss Rack builder option ShowDebug.
|
5
|
+
def debug(*args); end
|
6
|
+
# Placeholder; overloaded by Kiss Rack builder option Bench.
|
7
|
+
def bench(*args); end
|
8
|
+
# Placeholder; to be overloaded by Kiss Rack builder option MultiBench
|
9
|
+
# (not yet implemented).
|
10
|
+
def multibench(*args); end
|
11
|
+
|
12
|
+
class Kiss
|
13
|
+
# Used when Kiss#file_cache called from Kiss::Action#file_cache.
|
14
|
+
# Caught by Kiss's Rack builder option FileNotFound.
|
15
|
+
class TemplateFileNotFound < LoadError; end
|
16
|
+
class FileNotFound < LoadError; end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
# Methods to enable hash.attr == hash[attr] syntax,
|
21
|
+
# including hash.id and hash.type
|
22
|
+
class Hash
|
23
|
+
def method_missing(meth,*args)
|
24
|
+
if /=$/=~(meth=meth.id2name) then
|
25
|
+
self[meth[0...-1]] = (args.length<2 ? args[0] : args)
|
26
|
+
else
|
27
|
+
self[meth] || self[meth.to_sym]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
def id
|
31
|
+
self['id'] || self [:id]
|
32
|
+
end
|
33
|
+
def type
|
34
|
+
self['type'] || self [:type] || Hash
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Enables FixNum conversion to time duration values (in seconds).
|
39
|
+
class Fixnum
|
40
|
+
# 2.weeks = 14.days
|
41
|
+
def weeks
|
42
|
+
self * 7.days
|
43
|
+
end
|
44
|
+
# 1.days = 24.hours
|
45
|
+
def days
|
46
|
+
self * 24.hours
|
47
|
+
end
|
48
|
+
# 1.hours = 60.minutes = 3600 (seconds)
|
49
|
+
def hours
|
50
|
+
self * 60.minutes
|
51
|
+
end
|
52
|
+
# 1.minutes = 60 (seconds)
|
53
|
+
# 5.minutes = 300 (seconds)
|
54
|
+
def minutes
|
55
|
+
self * 60
|
56
|
+
end
|
57
|
+
# 5.minutes.ago = Time.now - 300
|
58
|
+
def ago
|
59
|
+
Time.now - self
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Date
|
64
|
+
# Returns string representing date in m/d/yyyy format
|
65
|
+
def mdy
|
66
|
+
sprintf('%d/%d/%04d',month,mday,year)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class NilClass
|
71
|
+
def mdy
|
72
|
+
''
|
73
|
+
end
|
74
|
+
def size
|
75
|
+
0
|
76
|
+
end
|
77
|
+
def length
|
78
|
+
0
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Corrections to sequel_core/core_sql.rb
|
83
|
+
class String
|
84
|
+
def to_sequel_time
|
85
|
+
self == '0000-00-00 00:00:00' ? nil : begin
|
86
|
+
Time.parse(self)
|
87
|
+
rescue Exception => e
|
88
|
+
raise Sequel::Error::InvalidValue, "Invalid time value '#{self}' (#{e.message})"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Converts a string into a Date object.
|
93
|
+
def to_date
|
94
|
+
begin
|
95
|
+
Date.parse(self)
|
96
|
+
rescue Exception => e
|
97
|
+
if (self == '0000-00-00')
|
98
|
+
nil
|
99
|
+
else
|
100
|
+
raise Sequel::Error::InvalidValue, "Invalid date value '#{self}' (#{e.message})"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def to_const
|
106
|
+
begin
|
107
|
+
parts = self.split(/::/)
|
108
|
+
klass = Kernel
|
109
|
+
while (next_part = parts.shift)
|
110
|
+
klass = klass.const_get(next_part)
|
111
|
+
end
|
112
|
+
|
113
|
+
klass
|
114
|
+
rescue
|
115
|
+
raise "Constant '#{self}' not defined"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class Symbol
|
121
|
+
def to_const
|
122
|
+
self.to_s.to_const
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
module Rack
|
127
|
+
class Request
|
128
|
+
def server
|
129
|
+
url = scheme + "://"
|
130
|
+
url << host
|
131
|
+
|
132
|
+
if scheme == "https" && port != 443 ||
|
133
|
+
scheme == "http" && port != 80
|
134
|
+
url << ":#{port}"
|
135
|
+
end
|
136
|
+
|
137
|
+
url
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class Kiss
|
2
|
+
# Enables `loop' features for certain template loops (while, for...in).
|
3
|
+
# Similar to features of Perl's Template::Iterator by Andy Wardley.
|
4
|
+
class Iterator
|
5
|
+
attr_reader :index, :collection
|
6
|
+
|
7
|
+
# Creates a new loop iterator.
|
8
|
+
def initialize(collection = nil)
|
9
|
+
@collection = collection
|
10
|
+
@index = -1
|
11
|
+
end
|
12
|
+
|
13
|
+
# Used by template erubis pre-processing voodoo to advance
|
14
|
+
# the iterator index. Not intended for any other use.
|
15
|
+
def increment
|
16
|
+
@index = @index + 1
|
17
|
+
end
|
18
|
+
|
19
|
+
# Return current iteration number, indexed from one instead of zero.
|
20
|
+
def count
|
21
|
+
@index + 1
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns true if this is loop's first iteration.
|
25
|
+
def first
|
26
|
+
@index == 0
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns true if this is the last iteration of a
|
30
|
+
# loop over a collection.
|
31
|
+
def last
|
32
|
+
count == size
|
33
|
+
end
|
34
|
+
|
35
|
+
# Return previous item in loop-iterated collection.
|
36
|
+
def prev
|
37
|
+
@collection ? @collection[index-1] : nil
|
38
|
+
end
|
39
|
+
|
40
|
+
# Return next item in loop-iterated collection.
|
41
|
+
def next
|
42
|
+
@collection ? @collection[index+1] : nil
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns index of loop's last iteration.
|
46
|
+
def max
|
47
|
+
@collection ? @collection.size-1 : nil
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns number of iterations in loop over a collection
|
51
|
+
# (size of the iterated collection).
|
52
|
+
def size
|
53
|
+
@collection ? @collection.size : nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|