Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

root/trunk/activesupport/lib/active_support/inflector.rb

Revision 9093, 10.1 kB (checked in by pratik, 3 months ago)

Improve documentation.

Line 
1 require 'singleton'
2
3 # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without,
4 # and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept
5 # in inflections.rb.
6 module Inflector
7   # A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional
8   # inflection rules. Examples:
9   #
10   #   Inflector.inflections do |inflect|
11   #     inflect.plural /^(ox)$/i, '\1\2en'
12   #     inflect.singular /^(ox)en/i, '\1'
13   #
14   #     inflect.irregular 'octopus', 'octopi'
15   #
16   #     inflect.uncountable "equipment"
17   #   end
18   #
19   # New rules are added at the top. So in the example above, the irregular rule for octopus will now be the first of the
20   # pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may
21   # already have been loaded.
22   class Inflections
23     include Singleton
24
25     attr_reader :plurals, :singulars, :uncountables
26
27     def initialize
28       @plurals, @singulars, @uncountables = [], [], []
29     end
30
31     # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression.
32     # The replacement should always be a string that may include references to the matched data from the rule.
33     def plural(rule, replacement)
34       @plurals.insert(0, [rule, replacement])
35     end
36
37     # Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression.
38     # The replacement should always be a string that may include references to the matched data from the rule.
39     def singular(rule, replacement)
40       @singulars.insert(0, [rule, replacement])
41     end
42
43     # Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
44     # for strings, not regular expressions. You simply pass the irregular in singular and plural form.
45     #
46     # Examples:
47     #   irregular 'octopus', 'octopi'
48     #   irregular 'person', 'people'
49     def irregular(singular, plural)
50       if singular[0,1].upcase == plural[0,1].upcase
51         plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1])
52         singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1])
53       else
54         plural(Regexp.new("#{singular[0,1].upcase}(?i)#{singular[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
55         plural(Regexp.new("#{singular[0,1].downcase}(?i)#{singular[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
56         singular(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), singular[0,1].upcase + singular[1..-1])
57         singular(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), singular[0,1].downcase + singular[1..-1])       
58       end
59     end
60
61     # Add uncountable words that shouldn't be attempted inflected.
62     #
63     # Examples:
64     #   uncountable "money"
65     #   uncountable "money", "information"
66     #   uncountable %w( money information rice )
67     def uncountable(*words)
68       (@uncountables << words).flatten!
69     end
70
71     # Clears the loaded inflections within a given scope (default is :all). Give the scope as a symbol of the inflection type,
72     # the options are: :plurals, :singulars, :uncountables
73     #
74     # Examples:
75     #   clear :all
76     #   clear :plurals
77     def clear(scope = :all)
78       case scope
79         when :all
80           @plurals, @singulars, @uncountables = [], [], []
81         else
82           instance_variable_set "@#{scope}", []
83       end
84     end
85   end
86
87   extend self
88
89   def inflections
90     if block_given?
91       yield Inflections.instance
92     else
93       Inflections.instance
94     end
95   end
96
97   # Returns the plural form of the word in the string.
98   #
99   # Examples
100   #   "post".pluralize #=> "posts"
101   #   "octopus".pluralize #=> "octopi"
102   #   "sheep".pluralize #=> "sheep"
103   #   "words".pluralize #=> "words"
104   #   "the blue mailman".pluralize #=> "the blue mailmen"
105   #   "CamelOctopus".pluralize #=> "CamelOctopi"
106   def pluralize(word)
107     result = word.to_s.dup
108
109     if word.empty? || inflections.uncountables.include?(result.downcase)
110       result
111     else
112       inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
113       result
114     end
115   end
116
117   # The reverse of pluralize, returns the singular form of a word in a string.
118   #
119   # Examples
120   #   "posts".singularize #=> "post"
121   #   "octopi".singularize #=> "octopus"
122   #   "sheep".singluarize #=> "sheep"
123   #   "word".singluarize #=> "word"
124   #   "the blue mailmen".singularize #=> "the blue mailman"
125   #   "CamelOctopi".singularize #=> "CamelOctopus"
126   def singularize(word)
127     result = word.to_s.dup
128
129     if inflections.uncountables.include?(result.downcase)
130       result
131     else
132       inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
133       result
134     end
135   end
136
137   # By default, camelize converts strings to UpperCamelCase. If the argument to camelize
138   # is set to ":lower" then camelize produces lowerCamelCase.
139   #
140   # camelize will also convert '/' to '::' which is useful for converting paths to namespaces
141   #
142   # Examples
143   #   "active_record".camelize #=> "ActiveRecord"
144   #   "active_record".camelize(:lower) #=> "activeRecord"
145   #   "active_record/errors".camelize #=> "ActiveRecord::Errors"
146   #   "active_record/errors".camelize(:lower) #=> "activeRecord::Errors"
147   def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
148     if first_letter_in_uppercase
149       lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
150     else
151       lower_case_and_underscored_word.first + camelize(lower_case_and_underscored_word)[1..-1]
152     end
153   end
154
155   # Capitalizes all the words and replaces some characters in the string to create
156   # a nicer looking title. Titleize is meant for creating pretty output. It is not
157   # used in the Rails internals.
158   #
159   # titleize is also aliased as as titlecase
160   #
161   # Examples
162   #   "man from the boondocks".titleize #=> "Man From The Boondocks"
163   #   "x-men: the last stand".titleize #=> "X Men: The Last Stand"
164   def titleize(word)
165     humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize }
166   end
167
168   # The reverse of +camelize+. Makes an underscored, lowercase form from the expression in the string.
169   #
170   # Changes '::' to '/' to convert namespaces to paths.
171   #
172   # Examples
173   #   "ActiveRecord".underscore #=> "active_record"
174   #   "ActiveRecord::Errors".underscore #=> active_record/errors
175   def underscore(camel_cased_word)
176     camel_cased_word.to_s.gsub(/::/, '/').
177       gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
178       gsub(/([a-z\d])([A-Z])/,'\1_\2').
179       tr("-", "_").
180       downcase
181   end
182
183   # Replaces underscores with dashes in the string.
184   #
185   # Example
186   #   "puni_puni" #=> "puni-puni"
187   def dasherize(underscored_word)
188     underscored_word.gsub(/_/, '-')
189   end
190
191   # Capitalizes the first word and turns underscores into spaces and strips _id.
192   # Like titleize, this is meant for creating pretty output.
193   #
194   # Examples
195   #   "employee_salary" #=> "Employee salary"
196   #   "author_id" #=> "Author"
197   def humanize(lower_case_and_underscored_word)
198     lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
199   end
200
201   # Removes the module part from the expression in the string
202   #
203   # Examples
204   #   "ActiveRecord::CoreExtensions::String::Inflections".demodulize #=> "Inflections"
205   #   "Inflections".demodulize #=> "Inflections"
206   def demodulize(class_name_in_module)
207     class_name_in_module.to_s.gsub(/^.*::/, '')
208   end
209
210   # Create the name of a table like Rails does for models to table names. This method
211   # uses the pluralize method on the last word in the string.
212   #
213   # Examples
214   #   "RawScaledScorer".tableize #=> "raw_scaled_scorers"
215   #   "egg_and_ham".tableize #=> "egg_and_hams"
216   #   "fancyCategory".tableize #=> "fancy_categories"
217   def tableize(class_name)
218     pluralize(underscore(class_name))
219   end
220
221   # Create a class name from a plural table name like Rails does for table names to models.
222   # Note that this returns a string and not a Class. (To convert to an actual class
223   # follow classify with constantize.)
224   #
225   # Examples
226   #   "egg_and_hams".classify #=> "EggAndHam"
227   #   "posts".classify #=> "Post"
228   #
229   # Singular names are not handled correctly
230   #   "business".classify #=> "Busines"
231   def classify(table_name)
232     # strip out any leading schema name
233     camelize(singularize(table_name.to_s.sub(/.*\./, '')))
234   end
235
236   # Creates a foreign key name from a class name.
237   # +separate_class_name_and_id_with_underscore+ sets whether
238   # the method should put '_' between the name and 'id'.
239   #
240   # Examples
241   #   "Message".foreign_key #=> "message_id"
242   #   "Message".foreign_key(false) #=> "messageid"
243   #   "Admin::Post".foreign_key #=> "post_id"
244   def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
245     underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
246   end
247
248   # Constantize tries to find a declared constant with the name specified
249   # in the string. It raises a NameError when the name is not in CamelCase
250   # or is not initialized.
251   #
252   # Examples
253   #   "Module".constantize #=> Module
254   #   "Class".constantize #=> Class
255   def constantize(camel_cased_word)
256     unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
257       raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
258     end
259
260     Object.module_eval("::#{$1}", __FILE__, __LINE__)
261   end
262
263   # Ordinalize turns a number into an ordinal string used to denote the
264   # position in an ordered sequence such as 1st, 2nd, 3rd, 4th.
265   #
266   # Examples
267   #   ordinalize(1)     # => "1st"
268   #   ordinalize(2)     # => "2nd"
269   #   ordinalize(1002)  # => "1002nd"
270   #   ordinalize(1003)  # => "1003rd"
271   def ordinalize(number)
272     if (11..13).include?(number.to_i % 100)
273       "#{number}th"
274     else
275       case number.to_i % 10
276         when 1; "#{number}st"
277         when 2; "#{number}nd"
278         when 3; "#{number}rd"
279         else    "#{number}th"
280       end
281     end
282   end
283 end
284
285 require File.dirname(__FILE__) + '/inflections'
Note: See TracBrowser for help on using the browser.