kiss 1.1 → 1.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/LICENSE +1 -1
  2. data/Rakefile +2 -1
  3. data/VERSION +1 -1
  4. data/bin/kiss +151 -34
  5. data/data/scaffold.tgz +0 -0
  6. data/lib/kiss.rb +389 -742
  7. data/lib/kiss/accessors/controller.rb +47 -0
  8. data/lib/kiss/accessors/request.rb +106 -0
  9. data/lib/kiss/accessors/template.rb +23 -0
  10. data/lib/kiss/action.rb +502 -132
  11. data/lib/kiss/bench.rb +14 -5
  12. data/lib/kiss/debug.rb +14 -6
  13. data/lib/kiss/exception_report.rb +22 -299
  14. data/lib/kiss/ext/core.rb +700 -0
  15. data/lib/kiss/ext/rack.rb +33 -0
  16. data/lib/kiss/ext/sequel_database.rb +47 -0
  17. data/lib/kiss/ext/sequel_mysql_dataset.rb +23 -0
  18. data/lib/kiss/form.rb +404 -179
  19. data/lib/kiss/form/field.rb +183 -307
  20. data/lib/kiss/form/field_types.rb +239 -0
  21. data/lib/kiss/format.rb +88 -70
  22. data/lib/kiss/html/exception_report.css +222 -0
  23. data/lib/kiss/html/exception_report.html +210 -0
  24. data/lib/kiss/iterator.rb +14 -12
  25. data/lib/kiss/login.rb +8 -8
  26. data/lib/kiss/mailer.rb +68 -66
  27. data/lib/kiss/model.rb +323 -36
  28. data/lib/kiss/rack/bench.rb +16 -8
  29. data/lib/kiss/rack/email_errors.rb +25 -15
  30. data/lib/kiss/rack/errors_ok.rb +2 -2
  31. data/lib/kiss/rack/facebook.rb +6 -6
  32. data/lib/kiss/rack/file_not_found.rb +10 -8
  33. data/lib/kiss/rack/log_exceptions.rb +3 -3
  34. data/lib/kiss/rack/recorder.rb +2 -2
  35. data/lib/kiss/rack/show_debug.rb +2 -2
  36. data/lib/kiss/rack/show_exceptions.rb +2 -2
  37. data/lib/kiss/request.rb +435 -0
  38. data/lib/kiss/sequel_session.rb +15 -14
  39. data/lib/kiss/static_file.rb +20 -13
  40. data/lib/kiss/template.rb +327 -0
  41. metadata +60 -25
  42. data/lib/kiss/controller_accessors.rb +0 -81
  43. data/lib/kiss/hacks.rb +0 -188
  44. data/lib/kiss/sequel_mysql.rb +0 -25
  45. data/lib/kiss/template_methods.rb +0 -167
@@ -1,25 +1,25 @@
1
1
  class Kiss
2
2
  class Login < Hash
3
3
  def initialize(session)
4
- @session = session
5
- @session['login'] ||= {}
4
+ @_session = session
5
+ @_session['login'] ||= {}
6
6
 
7
7
  # check if login expired
8
8
  if expired?
9
9
  # login expired
10
- @session['login'] = {}
10
+ @_session['login'] = {}
11
11
  end
12
12
 
13
- @persist_data = @session['login']
14
- self.merge!(@persist_data)
13
+ @_persist_data = @_session['login']
14
+ self.merge!(@_persist_data)
15
15
  end
16
16
 
17
17
  def expired?
18
- @session['login']['expires_at'] && session['login']['expires_at'] < Time.now
18
+ @_session['login']['expires_at'] && session['login']['expires_at'] < Time.now
19
19
  end
20
20
 
21
21
  def persist(data = {})
22
- @persist_data.merge!(data)
22
+ @_persist_data.merge!(data)
23
23
  self.merge!(data)
24
24
  end
25
25
 
@@ -38,7 +38,7 @@ class Kiss
38
38
  end
39
39
 
40
40
  def clear
41
- @session['login'] = {}
41
+ @_session['login'] = {}
42
42
  super()
43
43
  end
44
44
  end
@@ -1,7 +1,7 @@
1
1
  class Kiss
2
2
  # This class creates, renders, and sends email messages.
3
3
 
4
- # @options
4
+ # @_options
5
5
  # :server, :port, :domain, :account, :password, :auth - for SMTP login
6
6
  # :from/to - used by SMTP to send message (ignored by sendmail)
7
7
  # :message - message headers/body text
@@ -10,91 +10,93 @@ class Kiss
10
10
  # :sendmail_path - filesystem path to sendmail executable
11
11
 
12
12
  class Mailer
13
- include Kiss::TemplateMethods
13
+ @@sendmail_path = "/usr/sbin/sendmail -t"
14
+
15
+ class << self
16
+ def send(options)
17
+ if options[:sendmail] || (options[:engine] == :sendmail) || !options[:server]
18
+ send_via_sendmail(options)
19
+ else
20
+ send_via_smtp(options)
21
+ end
22
+ end
14
23
 
15
- attr_accessor :controller
24
+ # Attempts to send message using /usr/sbin/sendmail.
25
+ # NOTE: sendmail ignores :from and :to options, using
26
+ # From and To headers from the message
27
+ def send_via_sendmail(options)
28
+ puts options[:message]
29
+ IO.popen(options[:sendmail] || @@sendmail_path, "w") do |pipe|
30
+ pipe.puts(options[:message])
31
+ end
32
+ end
16
33
 
17
- def merge_options(options = {})
18
- @options.merge!(options)
19
- @controller = options[:controller] || @controller
20
- @data = options[:data] || @data
34
+ # Attempts to send message using Net::SMTP.
35
+ def send_via_smtp(options)
36
+ require 'net/smtp' unless defined?(Net::SMTP)
37
+
38
+ Net::SMTP.start(
39
+ options[:server] || 'localhost',
40
+ options[:port] || 25,
41
+ options[:domain] || nil,
42
+ options[:account] || options[:username] || nil,
43
+ options[:password] || nil,
44
+ options[:auth] || :plain
45
+ ) do |smtp|
46
+ smtp.sendmail(options[:message], options[:from], options[:to])
47
+ end
48
+ end
21
49
  end
22
50
 
51
+ include Kiss::ControllerAccessors
52
+ include Kiss::RequestAccessors
53
+ include Kiss::DatabaseAccessors
54
+ include Kiss::TemplateMethods
55
+
56
+ _attr_accessor :controller, :request, :options
57
+
23
58
  # Creates new email message object.
24
- def initialize(options = {})
25
- @options = {}
26
- @data = {}
59
+ def initialize(new_options = {})
60
+ @_options = {}
61
+ merge_options(new_options)
62
+ end
63
+
64
+ def merge_options(new_options = {})
65
+ if new_options[:controller]
66
+ @_controller = new_options[:controller]
67
+ @_options = @_controller.mailer_config.merge(@_options)
68
+ end
27
69
 
28
- merge_options(options) if options
70
+ @_options.merge!(new_options)
71
+ @_request = new_options[:request] || @_request
29
72
  end
30
73
 
31
74
  # Renders email template to string, unless message option is
32
75
  # already set to a string value.
33
- def prepare_email_message(options = nil)
34
- merge_options(options) if options
76
+ def prepare_email_message(new_options = {})
77
+ merge_options(new_options)
35
78
 
36
- unless @options[:message].is_a?(String)
37
- if template_name = @options[:template]
38
- @template_dir = controller.email_template_dir
39
- raise 'email_template_dir path not set' unless @template_dir
40
-
41
- data = vars = @options[:data] || @options[:vars]
42
-
43
- path = "#{@template_dir}/#{template_name}"
44
- @options[:message] = erubis(path,binding)
79
+ unless @_options[:message].is_a?(String)
80
+ if template = @_options[:template]
81
+ @@template_class ||= Kiss::Template.get_root_class(controller, controller.email_template_dir)
82
+ @data = @_options[:data] || @_options[:vars]
83
+ @_options[:message] = @@template_class.get_subclass_from_path('/'+template).new(request, self).call
45
84
  else
46
- raise 'email message not defined'
85
+ raise 'no email message or template found to prepare'
47
86
  end
48
87
  end
49
88
 
50
- return @options[:message]
89
+ return @_options[:message]
51
90
  end
52
91
 
53
92
  # Attempts to send message using SMTP, unless :engine option is set to
54
93
  # :sendmail.
55
- def send(options = nil)
56
- merge_options(options) if options
57
-
58
- if @options[:engine] == :sendmail
59
- return sendmail
60
- else
61
- return send_smtp
62
- end
63
- end
64
-
65
- # Attempts to send message using /usr/sbin/sendmail.
66
- # NOTE: sendmail ignores :from and :to options, using
67
- # From and To headers from the message
68
- def sendmail(options = nil)
69
- merge_options(options) if options
70
-
71
- prepare_email_message(options)
72
-
73
- IO.popen(@options[:sendmail_path] || "/usr/sbin/sendmail -t","w") do |pipe|
74
- pipe.puts(@options[:message])
75
- end
76
- end
77
-
78
- # Attempts to send message using Net::SMTP.
79
- def send_smtp(options = nil)
80
- merge_options(options) if options
94
+ def send(new_options = {})
95
+ merge_options(new_options)
96
+ merge_options(@_controller.mailer_override)
81
97
 
82
- prepare_email_message(options)
83
-
84
- require 'net/smtp' unless defined?(Net::SMTP)
85
- # begin
86
- Net::SMTP.start(
87
- @options[:server] || 'localhost',
88
- @options[:port] || 25,
89
- @options[:domain] || nil,
90
- @options[:account] || @options[:username] || nil,
91
- @options[:password] || nil,
92
- @options[:auth] || :plain
93
- ) do |smtp|
94
- smtp.sendmail(@options[:message], @options[:from], @options[:to])
95
- end
96
- # rescue
97
- # end
98
+ prepare_email_message
99
+ self.class.send(options)
98
100
  end
99
101
  end
100
102
  end
@@ -4,26 +4,50 @@ class Kiss
4
4
  # to cache database model classes, unless no model_dir is specified.
5
5
  class Model < Sequel::Model
6
6
  class << self
7
+ dsl_accessor :value_column, :display_column
8
+
7
9
  def set_dataset(source)
8
10
  super(source)
9
11
  end
10
12
 
13
+ def has_many(*args)
14
+ one_to_many(*args)
15
+ end
16
+
17
+ def belongs_to(*args)
18
+ many_to_one(*args)
19
+ end
20
+
21
+ def has_and_belongs_to_many(*args)
22
+ many_to_many(*args)
23
+ end
24
+
25
+ def alias_association(new_name, old_name)
26
+ alias_method new_name, old_name
27
+ alias_method :"#{new_name}_dataset", :"#{old_name}_dataset"
28
+ end
29
+ alias_method :alias_assoc, :alias_association
30
+
11
31
  # This method is called by Sequel::Model's association def methods.
12
32
  # Must return singularized table name for correct association key names.
13
33
  def name
14
- @table.to_s.singularize
34
+ @_table.to_s.singularize
15
35
  end
16
36
 
17
37
  def controller
18
38
  db.kiss_controller
19
39
  end
20
40
 
41
+ def request
42
+ db.kiss_request || controller
43
+ end
44
+
21
45
  def table
22
- @table.to_sym
46
+ @_table.to_sym
23
47
  end
24
48
 
25
49
  def table=(table)
26
- @table = table
50
+ @_table = table
27
51
  end
28
52
 
29
53
  # Name symbol for default foreign key
@@ -40,7 +64,7 @@ class Kiss
40
64
  opts = opts.clone
41
65
 
42
66
  unless opts[:class] || opts[:class_name]
43
- opts[:class_name] = name.to_s.singularize
67
+ opts[:class_name] = name.to_s.pluralize
44
68
  end
45
69
 
46
70
  super(type, name, opts, &block)
@@ -49,53 +73,222 @@ class Kiss
49
73
  end
50
74
 
51
75
  include Kiss::ControllerAccessors
76
+ include Kiss::RequestAccessors
77
+ alias_method :database, :db
78
+ end
79
+
80
+ def deferred_associations
81
+ @deferred_associations ||= {}
52
82
  end
53
83
 
54
- def method_missing(meth)
55
- raise NoMethodError, "undefined method `#{meth}' for database model `#{self.class.name}'"
84
+ def deferred_association_method_calls
85
+ @deferred_association_method_calls ||= []
86
+ end
87
+
88
+ def set_associated_object(opts, o)
89
+ if o && !o.pk
90
+ if (da = deferred_associations[opts[:name]]) != o
91
+ if da
92
+ da.deferred_association_method_calls.delete_if {|c| c[1] == [self, opts.setter_method, da] }
93
+ remove_deferred_reciprocal_object(opts, o)
94
+ end
95
+
96
+ deferred_associations[opts[:name]] = o
97
+ o.deferred_association_method_calls << [false, [self, opts.setter_method, o]]
98
+ add_deferred_reciprocal_object(opts, o)
99
+ end
100
+ o
101
+ else
102
+ deferred_associations.delete(opts[:name])
103
+ super
104
+ end
105
+ end
106
+
107
+ def add_associated_object(opts, o)
108
+ need_missing_opk = opts.need_associated_primary_key? && !o.pk
109
+ if !pk || need_missing_opk
110
+ double_defer = !pk && need_missing_opk
111
+ # TODO: add :uniq check here (and patch this in Sequel as well)
112
+ (deferred_associations[opts[:name]] ||= []).push(o)
113
+
114
+ method_call = [double_defer, [self, opts.add_method, o]]
115
+ deferred_association_method_calls << method_call unless pk
116
+ o.deferred_association_method_calls << method_call if need_missing_opk
117
+
118
+ add_deferred_reciprocal_object(opts, o)
119
+ o
120
+ else
121
+ deferred_associations[opts[:name]].delete(o) if deferred_associations[opts[:name]]
122
+ super
123
+ end
124
+ end
125
+
126
+ # Add/Set the current object to/as the given object's reciprocal association.
127
+ def add_deferred_reciprocal_object(opts, o)
128
+ return unless reciprocal = opts.reciprocal
129
+ if opts.reciprocal_array?
130
+ if array = o.deferred_associations[reciprocal] and !array.include?(self)
131
+ array.push(self)
132
+ end
133
+ else
134
+ o.deferred_associations[reciprocal] = self
135
+ end
136
+ end
137
+
138
+ def load_associated_objects(opts, reload=false)
139
+ if d = deferred_associations[opts[:name]]
140
+ opts.returns_array? ? super + d : d
141
+ else
142
+ super || set_associated_object(opts, opts.associated_class.new)
143
+ end
144
+ end
145
+
146
+ # Remove all associated objects from the given association
147
+ def remove_all_associated_objects(opts)
148
+ ret = super
149
+ if deferred_associations.include?(opts[:name])
150
+ def_ret = deferred_associations[opts[:name]].each do |o|
151
+ method_call = [self, opts.add_method, o]
152
+ deferred_association_method_calls.delete_if {|c| c[1] == method_call }
153
+ o.deferred_association_method_calls.delete_if {|c| c[1] == method_call }
154
+ remove_deferred_reciprocal_object(opts, o)
155
+ end
156
+ deferred_associations[opts[:name]] = []
157
+ (ret || []) + def_ret
158
+ else
159
+ ret
160
+ end
161
+ end
162
+
163
+ # Remove the given associated object from the given association
164
+ def remove_associated_object(opts, o)
165
+ if (array = deferred_associations[opts[:name]]) and array.delete(o)
166
+ method_call = [self, opts.add_method, o]
167
+ deferred_association_method_calls.delete_if {|c| c[1] == method_call }
168
+ o.deferred_association_method_calls.delete_if {|c| c[1] == method_call }
169
+ remove_deferred_reciprocal_object(opts, o)
170
+ else
171
+ super
172
+ end
173
+ o
174
+ end
175
+
176
+ # Remove/unset the current object from/as the given object's reciprocal association.
177
+ def remove_deferred_reciprocal_object(opts, o)
178
+ return unless reciprocal = opts.reciprocal
179
+ if opts.reciprocal_array?
180
+ if array = o.deferred_associations[reciprocal]
181
+ array.delete_if{|x| self === x}
182
+ end
183
+ else
184
+ o.deferred_associations[reciprocal] = nil
185
+ end
186
+ end
187
+
188
+ def after_create
189
+ deferred_association_method_calls.each do |call|
190
+ if call[0]
191
+ # call[0] (double_defer) is true
192
+ # now set it to false, do nothing else
193
+ # (defer again until other side is created)
194
+ call[0] = false
195
+ else
196
+ m = call[1]
197
+ m.shift.send(*m) if m
198
+ end
199
+ end
200
+ @deferred_association_method_calls = []
201
+ end
202
+
203
+ def method_missing(meth, *args, &block)
204
+ if meth.to_s =~ /(\w+)\?\Z/
205
+ column = $1.to_sym
206
+ if respond_to?(column)
207
+ val = self.send(column, *args, &block)
208
+ return !(val.nil? || val.zero?)
209
+ end
210
+ end
211
+ raise NoMethodError, "undefined method `#{meth}' for database model `#{self.class.name.pluralize}'"
56
212
  end
57
213
 
58
214
  def controller
59
215
  self.class.controller
60
216
  end
61
217
 
218
+ def request
219
+ self.class.request
220
+ end
221
+
222
+ def name
223
+ self[:name] || self.class.table.to_s.singularize.titleize
224
+ end
225
+
226
+ # Creates and invokes new Kiss::Mailer instance to send email message via SMTP.
227
+ def new_email(options = {})
228
+ request.new_email({
229
+ :data => self
230
+ }.merge(options))
231
+ end
232
+
233
+ def send_email(options = {})
234
+ new_email(options).send
235
+ end
236
+
237
+ def to_hash
238
+ result = {}
239
+ keys.each do |key|
240
+ result[key] = values[key]
241
+ end
242
+ result
243
+ end
244
+
62
245
  include Kiss::ControllerAccessors
246
+ include Kiss::RequestAccessors
247
+ alias_method :database, :db
63
248
  end
64
249
 
65
250
  class ModelCache
66
- def self.model_dir=(model_dir = nil)
67
- @@model_dir = model_dir && ::File.directory?(model_dir) ? model_dir : nil
68
- end
69
-
70
- def initialize(database = nil)
71
- @db = database
72
- @cache = {}
251
+ def initialize(database, model_dir)
252
+ @_db = database
253
+ @_model_dir = model_dir
254
+ @_cache = {}
255
+
256
+ # TODO: Fix this to use file cache and subclass hierarchy, so models can be
257
+ # reloaded if _model.rb changes.
258
+ @_root_class = Class.new(Kiss::Model)
259
+ if File.exist?(filename = "#{@_model_dir}/_model.rb")
260
+ @_root_class.class_eval(File.read(filename), filename)
261
+ end
73
262
  end
74
263
 
75
- def new_model_class(database,source)
76
- klass = Class.new(Kiss::Model)
264
+ def new_model_class(database, source)
265
+ klass = Class.new(@_root_class)
77
266
  klass.set_dataset(database[source])
78
267
  klass.table = source
268
+ klass.class_eval do
269
+ @_value_column = :id
270
+ @_display_column = :name
271
+ end
79
272
  klass
80
273
  end
81
274
 
82
275
  def [](source)
83
276
  raise 'argument to model cache must be symbol of database table name' unless source.is_a?(Symbol)
84
277
 
85
- database = @db
86
- @@model_dir ? begin
87
- # use file_cache
88
- model_path = "#{@@model_dir}/#{source}.rb"
89
- Kiss.file_cache(model_path) do |src|
90
- klass = new_model_class(database,source)
91
- klass.class_eval(src,model_path) if src
278
+ database = @_db
279
+ @_model_dir ? begin
280
+ # TODO: use request's file_cache
281
+ model_path = "#{@_model_dir}/#{source}.rb"
282
+ db.kiss_controller.file_cache(model_path) do |src|
283
+ klass = new_model_class(database, source)
284
+ klass.class_eval(src, model_path) if src
92
285
  klass
93
286
  end
94
- end : @cache[source] ||= new_model_class(database,source)
287
+ end : @_cache[source] ||= new_model_class(database, source)
95
288
  end
96
289
 
97
290
  def database
98
- @db
291
+ @_db
99
292
  end
100
293
  alias_method :db, :database
101
294
 
@@ -103,16 +296,12 @@ class Kiss
103
296
  Sequel::Model.dataset.literal(*args)
104
297
  end
105
298
  alias_method :quote, :literal
106
-
107
- def mdy_to_ymd(*args)
108
- Kiss.mdy_to_ymd(*args)
109
- end
110
299
  end
111
300
  end
112
301
 
113
302
  Sequel::Model::Associations::AssociationReflection.class_eval do
114
303
  def associated_class
115
- self[:class] ||= self[:model].controller.dbm[self[:class_name].to_s.pluralize.to_sym]
304
+ self[:class] ||= (self[:model].request || self[:model].controller).dbm[self[:class_name].to_s.pluralize.to_sym]
116
305
  end
117
306
  def default_left_key
118
307
  :"#{self[:model].name.singularize.underscore}_id"
@@ -131,7 +320,7 @@ class Date
131
320
  class << self
132
321
  alias_method :old_parse, :parse
133
322
  def parse(*args, &block)
134
- return SequelZeroTime.new(args[0]) if args[0] =~ /0000/
323
+ return SequelZeroTime.new(args[0]) if args[0] =~ /0000-00-00/
135
324
  old_parse(*args, &block)
136
325
  end
137
326
  end
@@ -184,12 +373,110 @@ end
184
373
  class BigDecimal
185
374
  # Formats number with comma-separated thousands.
186
375
  def format_thousands(value = to_f.to_s)
187
- integer, decimal = value.split(/\./,2)
188
- integer.reverse.gsub(/(\d{3})/,'\1,').sub(/\,(-?)$/,'\1').reverse + '.' + decimal
376
+ integer, decimal = value.split(/\./, 2)
377
+ integer.reverse.gsub(/(\d{3})/, '\1,').sub(/\,(-?)$/, '\1').reverse + '.' + decimal
378
+ end
379
+ end
380
+
381
+ class SequelZeroTime < String
382
+ def initialize(value = '0000-00-00 00:00', *args, &block)
383
+ super(value, *args, &block)
189
384
  end
190
385
 
191
- # Formats number to two decimal places.
192
- def format_currency
193
- format_thousands(sprintf("%0.2f",to_f))
386
+ # arithmetic operators
387
+ def +(*args)
388
+ self
389
+ end
390
+ def -(*args)
391
+ self
194
392
  end
195
- end
393
+
394
+ # comparision operators
395
+ def ==(value)
396
+ (value == 0 || value.is_a?(SequelZeroTime)) ? true : false
397
+ end
398
+ def >(value)
399
+ return false
400
+ end
401
+ def >=(value)
402
+ (value == 0 || value.is_a?(SequelZeroTime)) ? true : false
403
+ end
404
+ def <(value)
405
+ return true
406
+ end
407
+ def <=(value)
408
+ return true
409
+ end
410
+
411
+ # format conversion
412
+ def to_f; 0; end
413
+ def to_i; 0; end
414
+ def to_s; ''; end
415
+
416
+ # timezone conversion
417
+ def set_timezone!(zone, utc)
418
+ self
419
+ end
420
+ def from_timezone(zone)
421
+ self
422
+ end
423
+ def to_timezone(zone)
424
+ self
425
+ end
426
+ def to_utc
427
+ self
428
+ end
429
+ def zone
430
+ ''
431
+ end
432
+
433
+ # string representations
434
+ def strftime(*args); ''; end
435
+ def md; ''; end
436
+ def md_full; ''; end
437
+ alias_method :md_long, :md_full
438
+ def mdy; ''; end
439
+ def mdy_full; ''; end
440
+ alias_method :mdy_long, :mdy_full
441
+ def mdy_hm; ''; end
442
+ def mdy_hmz; ''; end
443
+ def mdy_hms; ''; end
444
+ def mdy_hmsz; ''; end
445
+ def ymd_hm; ''; end
446
+ def ymd_hmz; ''; end
447
+ def ymd_hms; ''; end
448
+ def ymd_hmsz; ''; end
449
+ def sql; '0000-00-00 00:00'; end
450
+ def mdy_hm_full; ''; end
451
+ alias_method :mdy_hm_long, :mdy_hm_full
452
+ def mdy_hmz_full; ''; end
453
+ alias_method :mdy_hmz_long, :mdy_hmz_full
454
+ def hm; ''; end
455
+ def hmz; ''; end
456
+ def hms; ''; end
457
+ def hmsz; ''; end
458
+ def hm_mdy; ''; end
459
+ def hmz_mdy; ''; end
460
+ def hms_mdy; ''; end
461
+ def hmsz_mdy; ''; end
462
+ def hm_mdy_full; ''; end
463
+ alias_method :hm_mdy_long, :hm_mdy_full
464
+ def hmz_mdy_full; ''; end
465
+ alias_method :hmz_mdy_long, :hmz_mdy_full
466
+
467
+ def zero?; true; end
468
+
469
+ def in_between?(*args); false; end
470
+ end
471
+
472
+ IrregularInflection = proc do
473
+ def self.irregular(singular, plural)
474
+ singular(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + singular[1..-1])
475
+ plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1])
476
+
477
+ singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1])
478
+ plural(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + plural[1..-1])
479
+ end
480
+ end
481
+ Sequel::Inflections.class_eval &IrregularInflection
482
+ String::Inflections.class_eval &IrregularInflection