datamapper 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. data/CHANGELOG +2 -0
  2. data/MIT-LICENSE +22 -0
  3. data/README +1 -0
  4. data/example.rb +25 -0
  5. data/lib/data_mapper.rb +30 -0
  6. data/lib/data_mapper/adapters/abstract_adapter.rb +229 -0
  7. data/lib/data_mapper/adapters/mysql_adapter.rb +171 -0
  8. data/lib/data_mapper/adapters/sqlite3_adapter.rb +189 -0
  9. data/lib/data_mapper/associations.rb +19 -0
  10. data/lib/data_mapper/associations/belongs_to_association.rb +111 -0
  11. data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +100 -0
  12. data/lib/data_mapper/associations/has_many_association.rb +101 -0
  13. data/lib/data_mapper/associations/has_one_association.rb +107 -0
  14. data/lib/data_mapper/base.rb +160 -0
  15. data/lib/data_mapper/callbacks.rb +47 -0
  16. data/lib/data_mapper/database.rb +134 -0
  17. data/lib/data_mapper/extensions/active_record_impersonation.rb +69 -0
  18. data/lib/data_mapper/extensions/callback_helpers.rb +35 -0
  19. data/lib/data_mapper/identity_map.rb +21 -0
  20. data/lib/data_mapper/loaded_set.rb +45 -0
  21. data/lib/data_mapper/mappings/column.rb +78 -0
  22. data/lib/data_mapper/mappings/schema.rb +28 -0
  23. data/lib/data_mapper/mappings/table.rb +99 -0
  24. data/lib/data_mapper/queries/conditions.rb +141 -0
  25. data/lib/data_mapper/queries/connection.rb +34 -0
  26. data/lib/data_mapper/queries/create_table_statement.rb +38 -0
  27. data/lib/data_mapper/queries/delete_statement.rb +17 -0
  28. data/lib/data_mapper/queries/drop_table_statement.rb +17 -0
  29. data/lib/data_mapper/queries/insert_statement.rb +29 -0
  30. data/lib/data_mapper/queries/reader.rb +42 -0
  31. data/lib/data_mapper/queries/result.rb +19 -0
  32. data/lib/data_mapper/queries/select_statement.rb +103 -0
  33. data/lib/data_mapper/queries/table_exists_statement.rb +17 -0
  34. data/lib/data_mapper/queries/truncate_table_statement.rb +17 -0
  35. data/lib/data_mapper/queries/update_statement.rb +25 -0
  36. data/lib/data_mapper/session.rb +240 -0
  37. data/lib/data_mapper/support/blank_slate.rb +3 -0
  38. data/lib/data_mapper/support/connection_pool.rb +117 -0
  39. data/lib/data_mapper/support/enumerable.rb +27 -0
  40. data/lib/data_mapper/support/inflector.rb +329 -0
  41. data/lib/data_mapper/support/proc.rb +69 -0
  42. data/lib/data_mapper/support/string.rb +23 -0
  43. data/lib/data_mapper/support/symbol.rb +91 -0
  44. data/lib/data_mapper/support/weak_hash.rb +46 -0
  45. data/lib/data_mapper/unit_of_work.rb +38 -0
  46. data/lib/data_mapper/validations/confirmation_validator.rb +55 -0
  47. data/lib/data_mapper/validations/contextual_validations.rb +50 -0
  48. data/lib/data_mapper/validations/format_validator.rb +85 -0
  49. data/lib/data_mapper/validations/formats/email.rb +78 -0
  50. data/lib/data_mapper/validations/generic_validator.rb +27 -0
  51. data/lib/data_mapper/validations/length_validator.rb +75 -0
  52. data/lib/data_mapper/validations/required_field_validator.rb +47 -0
  53. data/lib/data_mapper/validations/unique_validator.rb +65 -0
  54. data/lib/data_mapper/validations/validation_errors.rb +34 -0
  55. data/lib/data_mapper/validations/validation_helper.rb +60 -0
  56. data/performance.rb +156 -0
  57. data/profile_data_mapper.rb +18 -0
  58. data/rakefile.rb +80 -0
  59. data/spec/basic_finder.rb +67 -0
  60. data/spec/belongs_to.rb +47 -0
  61. data/spec/fixtures/animals.yaml +32 -0
  62. data/spec/fixtures/exhibits.yaml +90 -0
  63. data/spec/fixtures/fruit.yaml +6 -0
  64. data/spec/fixtures/people.yaml +15 -0
  65. data/spec/fixtures/zoos.yaml +20 -0
  66. data/spec/has_and_belongs_to_many.rb +25 -0
  67. data/spec/has_many.rb +34 -0
  68. data/spec/legacy.rb +14 -0
  69. data/spec/models/animal.rb +7 -0
  70. data/spec/models/exhibit.rb +6 -0
  71. data/spec/models/fruit.rb +6 -0
  72. data/spec/models/person.rb +7 -0
  73. data/spec/models/post.rb +4 -0
  74. data/spec/models/sales_person.rb +4 -0
  75. data/spec/models/zoo.rb +5 -0
  76. data/spec/new_record.rb +24 -0
  77. data/spec/spec_helper.rb +61 -0
  78. data/spec/sub_select.rb +16 -0
  79. data/spec/symbolic_operators.rb +21 -0
  80. data/spec/validates_confirmation_of.rb +36 -0
  81. data/spec/validates_format_of.rb +61 -0
  82. data/spec/validates_length_of.rb +101 -0
  83. data/spec/validates_uniqueness_of.rb +45 -0
  84. data/spec/validations.rb +63 -0
  85. metadata +134 -0
@@ -0,0 +1,3 @@
1
+ class BlankSlate #:nodoc:
2
+ instance_methods.each { |m| undef_method m unless m =~ /^(__|instance_eval)/ }
3
+ end
@@ -0,0 +1,117 @@
1
+ require 'thread'
2
+
3
+ module DataMapper
4
+ module Support
5
+
6
+ # A ConnectionPool manages access to database connections by keeping
7
+ # multiple connections and giving threads exclusive access to each
8
+ # connection.
9
+ #
10
+ # CREDIT: Sharon Rosner, maintainer of the Sequel (http://sequel.rubyforge.org)
11
+ # project an "ORM framework for Ruby" contributed this class.
12
+ class ConnectionPool
13
+ attr_reader :mutex
14
+
15
+ # The maximum number of connections.
16
+ attr_reader :max_size
17
+
18
+ # The proc used to create a new connection.
19
+ attr_accessor :connection_proc
20
+
21
+ attr_reader :available_connections, :allocated, :created_count
22
+
23
+ # Constructs a new pool with a maximum size. If a block is supplied, it
24
+ # is used to create new connections as they are needed.
25
+ #
26
+ # pool = ConnectionPool.new(10) {MyConnection.new(opts)}
27
+ #
28
+ # The connection creation proc can be changed at any time by assigning a
29
+ # Proc to pool#connection_proc.
30
+ #
31
+ # pool = ConnectionPool.new(10)
32
+ # pool.connection_proc = proc {MyConnection.new(opts)}
33
+ def initialize(max_size = 4, &block)
34
+ @max_size = max_size
35
+ @mutex = Mutex.new
36
+ @connection_proc = block
37
+
38
+ @available_connections = []
39
+ @allocated = {}
40
+ @created_count = 0
41
+ end
42
+
43
+ # Returns the number of created connections.
44
+ def size
45
+ @created_count
46
+ end
47
+
48
+ # Assigns a connection to the current thread, yielding the connection
49
+ # to the supplied block.
50
+ #
51
+ # pool.hold {|conn| conn.execute('DROP TABLE posts;')}
52
+ #
53
+ # Pool#hold is re-entrant, meaning it can be called recursively in
54
+ # the same thread without blocking.
55
+ #
56
+ # If no connection is available, Pool#hold will block until a connection
57
+ # is available.
58
+ def hold
59
+ t = Thread.current
60
+ if (conn = owned_connection(t))
61
+ return yield(conn)
62
+ end
63
+ while !(conn = acquire(t))
64
+ sleep 0.001
65
+ end
66
+ begin
67
+ yield conn
68
+ ensure
69
+ release(t)
70
+ end
71
+ rescue Exception => e
72
+ # if the error is not a StandardError it is converted into RuntimeError.
73
+ raise e.is_a?(StandardError) ? e : e.message
74
+ end
75
+
76
+ private
77
+ # Returns the connection owned by the supplied thread, if any.
78
+ def owned_connection(thread)
79
+ @mutex.synchronize {@allocated[thread]}
80
+ end
81
+
82
+ # Assigns a connection to the supplied thread, if one is available.
83
+ def acquire(thread)
84
+ @mutex.synchronize do
85
+ if conn = available
86
+ @allocated[thread] = conn
87
+ end
88
+ end
89
+ end
90
+
91
+ # Returns an available connection. If no connection is available,
92
+ # tries to create a new connection.
93
+ def available
94
+ @available_connections.pop || make_new
95
+ end
96
+
97
+ # Creates a new connection if the size of the pool is less than the
98
+ # maximum size.
99
+ def make_new
100
+ if @created_count < @max_size
101
+ @created_count += 1
102
+ @connection_proc.call
103
+ end
104
+ end
105
+
106
+ # Releases the connection assigned to the supplied thread.
107
+ def release(thread)
108
+ @mutex.synchronize do
109
+ @available_connections << @allocated[thread]
110
+ @allocated.delete(thread)
111
+ end
112
+ end
113
+
114
+ end # class ConnectionPool
115
+
116
+ end # module Support
117
+ end # module DataMapper
@@ -0,0 +1,27 @@
1
+ module DataMapper
2
+ module Support
3
+ module Enumerable
4
+
5
+ # Group a collection of elements into groups within a
6
+ # Hash. The value returned by the block passed to group_by
7
+ # is the key, and the value is an Array of items matching
8
+ # that key.
9
+ #
10
+ # === Example
11
+ # names = %w{ sam scott amy robert betsy }
12
+ # names.group_by { |name| name.size }
13
+ # => { 3 => [ "sam", "amy" ], 5 => [ "scott", "betsy" ], 6 => [ "robert" ]}
14
+ def group_by
15
+ inject(Hash.new { |h,k| h[k] = [] }) do |memo,item|
16
+ memo[yield(item)] << item; memo
17
+ end
18
+ end
19
+
20
+ end # module Enumerable
21
+ end # module Support
22
+ end # module DataMapper
23
+
24
+ # Extend Array with DataMapper::Support::Enumerable
25
+ class Array #:nodoc:
26
+ include DataMapper::Support::Enumerable
27
+ end
@@ -0,0 +1,329 @@
1
+ # This file is copied from the ActiveSupport project, which
2
+ # is a part of the Ruby On Rails web-framework (http://rubyonrails.org).
3
+
4
+ require 'singleton'
5
+
6
+ # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without,
7
+ # and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept
8
+ # in inflections.rb.
9
+ module Inflector
10
+ # A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional
11
+ # inflection rules. Examples:
12
+ #
13
+ # Inflector.inflections do |inflect|
14
+ # inflect.plural /^(ox)$/i, '\1\2en'
15
+ # inflect.singular /^(ox)en/i, '\1'
16
+ #
17
+ # inflect.irregular 'octopus', 'octopi'
18
+ #
19
+ # inflect.uncountable "equipment"
20
+ # end
21
+ #
22
+ # New rules are added at the top. So in the example above, the irregular rule for octopus will now be the first of the
23
+ # pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may
24
+ # already have been loaded.
25
+ class Inflections
26
+ include Singleton
27
+
28
+ attr_reader :plurals, :singulars, :uncountables
29
+
30
+ def initialize
31
+ @plurals, @singulars, @uncountables = [], [], []
32
+ end
33
+
34
+ # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression.
35
+ # The replacement should always be a string that may include references to the matched data from the rule.
36
+ def plural(rule, replacement)
37
+ @plurals.insert(0, [rule, replacement])
38
+ end
39
+
40
+ # Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression.
41
+ # The replacement should always be a string that may include references to the matched data from the rule.
42
+ def singular(rule, replacement)
43
+ @singulars.insert(0, [rule, replacement])
44
+ end
45
+
46
+ # Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
47
+ # for strings, not regular expressions. You simply pass the irregular in singular and plural form.
48
+ #
49
+ # Examples:
50
+ # irregular 'octopus', 'octopi'
51
+ # irregular 'person', 'people'
52
+ def irregular(singular, plural)
53
+ plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1])
54
+ singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1])
55
+ end
56
+
57
+ # Add uncountable words that shouldn't be attempted inflected.
58
+ #
59
+ # Examples:
60
+ # uncountable "money"
61
+ # uncountable "money", "information"
62
+ # uncountable %w( money information rice )
63
+ def uncountable(*words)
64
+ (@uncountables << words).flatten!
65
+ end
66
+
67
+ # Clears the loaded inflections within a given scope (default is :all). Give the scope as a symbol of the inflection type,
68
+ # the options are: :plurals, :singulars, :uncountables
69
+ #
70
+ # Examples:
71
+ # clear :all
72
+ # clear :plurals
73
+ def clear(scope = :all)
74
+ case scope
75
+ when :all
76
+ @plurals, @singulars, @uncountables = [], [], []
77
+ else
78
+ instance_variable_set "@#{scope}", []
79
+ end
80
+ end
81
+ end
82
+
83
+ extend self
84
+
85
+ def inflections
86
+ if block_given?
87
+ yield Inflections.instance
88
+ else
89
+ Inflections.instance
90
+ end
91
+ end
92
+
93
+ # Returns the plural form of the word in the string.
94
+ #
95
+ # Examples
96
+ # "post".pluralize #=> "posts"
97
+ # "octopus".pluralize #=> "octopi"
98
+ # "sheep".pluralize #=> "sheep"
99
+ # "words".pluralize #=> "words"
100
+ # "the blue mailman".pluralize #=> "the blue mailmen"
101
+ # "CamelOctopus".pluralize #=> "CamelOctopi"
102
+ def pluralize(word)
103
+ result = word.to_s.dup
104
+
105
+ if inflections.uncountables.include?(result.downcase)
106
+ result
107
+ else
108
+ inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
109
+ result
110
+ end
111
+ end
112
+
113
+ # The reverse of pluralize, returns the singular form of a word in a string.
114
+ #
115
+ # Examples
116
+ # "posts".singularize #=> "post"
117
+ # "octopi".singularize #=> "octopus"
118
+ # "sheep".singluarize #=> "sheep"
119
+ # "word".singluarize #=> "word"
120
+ # "the blue mailmen".singularize #=> "the blue mailman"
121
+ # "CamelOctopi".singularize #=> "CamelOctopus"
122
+ def singularize(word)
123
+ result = word.to_s.dup
124
+
125
+ if inflections.uncountables.include?(result.downcase)
126
+ result
127
+ else
128
+ inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
129
+ result
130
+ end
131
+ end
132
+
133
+ # By default, camelize converts strings to UpperCamelCase. If the argument to camelize
134
+ # is set to ":lower" then camelize produces lowerCamelCase.
135
+ #
136
+ # camelize will also convert '/' to '::' which is useful for converting paths to namespaces
137
+ #
138
+ # Examples
139
+ # "active_record".camelize #=> "ActiveRecord"
140
+ # "active_record".camelize(:lower) #=> "activeRecord"
141
+ # "active_record/errors".camelize #=> "ActiveRecord::Errors"
142
+ # "active_record/errors".camelize(:lower) #=> "activeRecord::Errors"
143
+ def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
144
+ if first_letter_in_uppercase
145
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
146
+ else
147
+ lower_case_and_underscored_word.first + camelize(lower_case_and_underscored_word)[1..-1]
148
+ end
149
+ end
150
+
151
+ # Capitalizes all the words and replaces some characters in the string to create
152
+ # a nicer looking title. Titleize is meant for creating pretty output. It is not
153
+ # used in the Rails internals.
154
+ #
155
+ # titleize is also aliased as as titlecase
156
+ #
157
+ # Examples
158
+ # "man from the boondocks".titleize #=> "Man From The Boondocks"
159
+ # "x-men: the last stand".titleize #=> "X Men: The Last Stand"
160
+ def titleize(word)
161
+ humanize(underscore(word)).gsub(/\b([a-z])/) { $1.capitalize }
162
+ end
163
+
164
+ # The reverse of +camelize+. Makes an underscored form from the expression in the string.
165
+ #
166
+ # Changes '::' to '/' to convert namespaces to paths.
167
+ #
168
+ # Examples
169
+ # "ActiveRecord".underscore #=> "active_record"
170
+ # "ActiveRecord::Errors".underscore #=> active_record/errors
171
+ def underscore(camel_cased_word)
172
+ camel_cased_word.to_s.gsub(/::/, '/').
173
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
174
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
175
+ tr("-", "_").
176
+ downcase
177
+ end
178
+
179
+ # Replaces underscores with dashes in the string.
180
+ #
181
+ # Example
182
+ # "puni_puni" #=> "puni-puni"
183
+ def dasherize(underscored_word)
184
+ underscored_word.gsub(/_/, '-')
185
+ end
186
+
187
+ # Capitalizes the first word and turns underscores into spaces and strips _id.
188
+ # Like titleize, this is meant for creating pretty output.
189
+ #
190
+ # Examples
191
+ # "employee_salary" #=> "Employee salary"
192
+ # "author_id" #=> "Author"
193
+ def humanize(lower_case_and_underscored_word)
194
+ lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
195
+ end
196
+
197
+ # Removes the module part from the expression in the string
198
+ #
199
+ # Examples
200
+ # "ActiveRecord::CoreExtensions::String::Inflections".demodulize #=> "Inflections"
201
+ # "Inflections".demodulize #=> "Inflections"
202
+ def demodulize(class_name_in_module)
203
+ class_name_in_module.to_s.gsub(/^.*::/, '')
204
+ end
205
+
206
+ # Create the name of a table like Rails does for models to table names. This method
207
+ # uses the pluralize method on the last word in the string.
208
+ #
209
+ # Examples
210
+ # "RawScaledScorer".tableize #=> "raw_scaled_scorers"
211
+ # "egg_and_ham".tableize #=> "egg_and_hams"
212
+ # "fancyCategory".tableize #=> "fancy_categories"
213
+ def tableize(class_name)
214
+ pluralize(underscore(class_name))
215
+ end
216
+
217
+ # Create a class name from a table name like Rails does for table names to models.
218
+ # Note that this returns a string and not a Class. (To convert to an actual class
219
+ # follow classify with constantize.)
220
+ #
221
+ # Examples
222
+ # "egg_and_hams".classify #=> "EggAndHam"
223
+ # "post".classify #=> "Post"
224
+ def classify(table_name)
225
+ # strip out any leading schema name
226
+ camelize(singularize(table_name.to_s.sub(/.*\./, '')))
227
+ end
228
+
229
+ # Creates a foreign key name from a class name.
230
+ # +separate_class_name_and_id_with_underscore+ sets whether
231
+ # the method should put '_' between the name and 'id'.
232
+ #
233
+ # Examples
234
+ # "Message".foreign_key #=> "message_id"
235
+ # "Message".foreign_key(false) #=> "messageid"
236
+ # "Admin::Post".foreign_key #=> "post_id"
237
+ def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
238
+ underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
239
+ end
240
+
241
+ # Constantize tries to find a declared constant with the name specified
242
+ # in the string. It raises a NameError when the name is not in CamelCase
243
+ # or is not initialized.
244
+ #
245
+ # Examples
246
+ # "Module".constantize #=> Module
247
+ # "Class".constantize #=> Class
248
+ def constantize(camel_cased_word)
249
+ unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
250
+ raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
251
+ end
252
+
253
+ Object.module_eval("::#{$1}", __FILE__, __LINE__)
254
+ end
255
+
256
+ # Ordinalize turns a number into an ordinal string used to denote the
257
+ # position in an ordered sequence such as 1st, 2nd, 3rd, 4th.
258
+ #
259
+ # Examples
260
+ # ordinalize(1) # => "1st"
261
+ # ordinalize(2) # => "2nd"
262
+ # ordinalize(1002) # => "1002nd"
263
+ # ordinalize(1003) # => "1003rd"
264
+ def ordinalize(number)
265
+ if (11..13).include?(number.to_i % 100)
266
+ "#{number}th"
267
+ else
268
+ case number.to_i % 10
269
+ when 1: "#{number}st"
270
+ when 2: "#{number}nd"
271
+ when 3: "#{number}rd"
272
+ else "#{number}th"
273
+ end
274
+ end
275
+ end
276
+ end
277
+
278
+ Inflector.inflections do |inflect|
279
+ inflect.plural(/$/, 's')
280
+ inflect.plural(/s$/i, 's')
281
+ inflect.plural(/(ax|test)is$/i, '\1es')
282
+ inflect.plural(/(octop|vir)us$/i, '\1i')
283
+ inflect.plural(/(alias|status)$/i, '\1es')
284
+ inflect.plural(/(bu)s$/i, '\1ses')
285
+ inflect.plural(/(buffal|tomat)o$/i, '\1oes')
286
+ inflect.plural(/([ti])um$/i, '\1a')
287
+ inflect.plural(/sis$/i, 'ses')
288
+ inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
289
+ inflect.plural(/(hive)$/i, '\1s')
290
+ inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
291
+ inflect.plural(/(x|ch|ss|sh)$/i, '\1es')
292
+ inflect.plural(/(matr|vert|ind)ix|ex$/i, '\1ices')
293
+ inflect.plural(/([m|l])ouse$/i, '\1ice')
294
+ inflect.plural(/^(ox)$/i, '\1en')
295
+ inflect.plural(/(quiz)$/i, '\1zes')
296
+
297
+ inflect.singular(/s$/i, '')
298
+ inflect.singular(/(n)ews$/i, '\1ews')
299
+ inflect.singular(/([ti])a$/i, '\1um')
300
+ inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '\1\2sis')
301
+ inflect.singular(/(^analy)ses$/i, '\1sis')
302
+ inflect.singular(/([^f])ves$/i, '\1fe')
303
+ inflect.singular(/(hive)s$/i, '\1')
304
+ inflect.singular(/(tive)s$/i, '\1')
305
+ inflect.singular(/([lr])ves$/i, '\1f')
306
+ inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y')
307
+ inflect.singular(/(s)eries$/i, '\1eries')
308
+ inflect.singular(/(m)ovies$/i, '\1ovie')
309
+ inflect.singular(/(x|ch|ss|sh)es$/i, '\1')
310
+ inflect.singular(/([m|l])ice$/i, '\1ouse')
311
+ inflect.singular(/(bus)es$/i, '\1')
312
+ inflect.singular(/(o)es$/i, '\1')
313
+ inflect.singular(/(shoe)s$/i, '\1')
314
+ inflect.singular(/(cris|ax|test)es$/i, '\1is')
315
+ inflect.singular(/(octop|vir)i$/i, '\1us')
316
+ inflect.singular(/(alias|status)es$/i, '\1')
317
+ inflect.singular(/^(ox)en/i, '\1')
318
+ inflect.singular(/(vert|ind)ices$/i, '\1ex')
319
+ inflect.singular(/(matr)ices$/i, '\1ix')
320
+ inflect.singular(/(quiz)zes$/i, '\1')
321
+
322
+ inflect.irregular('person', 'people')
323
+ inflect.irregular('man', 'men')
324
+ inflect.irregular('child', 'children')
325
+ inflect.irregular('sex', 'sexes')
326
+ inflect.irregular('move', 'moves')
327
+
328
+ inflect.uncountable(%w(equipment information rice money species series fish sheep))
329
+ end