activesupport-inflector 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,72 @@
1
+ # encoding: utf-8
2
+ require 'active_support/multibyte'
3
+
4
+ class String
5
+ if RUBY_VERSION >= "1.9"
6
+ # == Multibyte proxy
7
+ #
8
+ # +mb_chars+ is a multibyte safe proxy for string methods.
9
+ #
10
+ # In Ruby 1.8 and older it creates and returns an instance of the ActiveSupport::Multibyte::Chars class which
11
+ # encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy
12
+ # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string.
13
+ #
14
+ # name = 'Claus Müller'
15
+ # name.reverse # => "rell??M sualC"
16
+ # name.length # => 13
17
+ #
18
+ # name.mb_chars.reverse.to_s # => "rellüM sualC"
19
+ # name.mb_chars.length # => 12
20
+ #
21
+ # In Ruby 1.9 and newer +mb_chars+ returns +self+ because String is (mostly) encoding aware. This means that
22
+ # it becomes easy to run one version of your code on multiple Ruby versions.
23
+ #
24
+ # == Method chaining
25
+ #
26
+ # All the methods on the Chars proxy which normally return a string will return a Chars object. This allows
27
+ # method chaining on the result of any of these methods.
28
+ #
29
+ # name.mb_chars.reverse.length # => 12
30
+ #
31
+ # == Interoperability and configuration
32
+ #
33
+ # The Chars object tries to be as interchangeable with String objects as possible: sorting and comparing between
34
+ # String and Char work like expected. The bang! methods change the internal string representation in the Chars
35
+ # object. Interoperability problems can be resolved easily with a +to_s+ call.
36
+ #
37
+ # For more information about the methods defined on the Chars proxy see ActiveSupport::Multibyte::Chars. For
38
+ # information about how to change the default Multibyte behavior see ActiveSupport::Multibyte.
39
+ def mb_chars
40
+ if ActiveSupport::Multibyte.proxy_class.consumes?(self)
41
+ ActiveSupport::Multibyte.proxy_class.new(self)
42
+ else
43
+ self
44
+ end
45
+ end
46
+
47
+ def is_utf8?
48
+ case encoding
49
+ when Encoding::UTF_8
50
+ valid_encoding?
51
+ when Encoding::ASCII_8BIT, Encoding::US_ASCII
52
+ dup.force_encoding(Encoding::UTF_8).valid_encoding?
53
+ else
54
+ false
55
+ end
56
+ end
57
+ else
58
+ def mb_chars
59
+ if ActiveSupport::Multibyte.proxy_class.wants?(self)
60
+ ActiveSupport::Multibyte.proxy_class.new(self)
61
+ else
62
+ self
63
+ end
64
+ end
65
+
66
+ # Returns true if the string has UTF-8 semantics (a String used for purely byte resources is unlikely to have
67
+ # them), returns false otherwise.
68
+ def is_utf8?
69
+ ActiveSupport::Multibyte::Chars.consumes?(self)
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,9 @@
1
+ begin
2
+ require 'i18n'
3
+ require 'active_support/lazy_load_hooks'
4
+ rescue LoadError => e
5
+ $stderr.puts "The i18n gem is not available. Please add it to your Gemfile and run bundle install"
6
+ raise e
7
+ end
8
+
9
+ I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml"
@@ -0,0 +1,63 @@
1
+ require 'active_support/inflector/inflections'
2
+
3
+ module ActiveSupport
4
+ Inflector.inflections do |inflect|
5
+ inflect.plural(/$/, 's')
6
+ inflect.plural(/s$/i, 's')
7
+ inflect.plural(/(ax|test)is$/i, '\1es')
8
+ inflect.plural(/(octop|vir)us$/i, '\1i')
9
+ inflect.plural(/(octop|vir)i$/i, '\1i')
10
+ inflect.plural(/(alias|status)$/i, '\1es')
11
+ inflect.plural(/(bu)s$/i, '\1ses')
12
+ inflect.plural(/(buffal|tomat)o$/i, '\1oes')
13
+ inflect.plural(/([ti])um$/i, '\1a')
14
+ inflect.plural(/([ti])a$/i, '\1a')
15
+ inflect.plural(/sis$/i, 'ses')
16
+ inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
17
+ inflect.plural(/(hive)$/i, '\1s')
18
+ inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
19
+ inflect.plural(/(x|ch|ss|sh)$/i, '\1es')
20
+ inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices')
21
+ inflect.plural(/(m|l)ouse$/i, '\1ice')
22
+ inflect.plural(/(m|l)ice$/i, '\1ice')
23
+ inflect.plural(/^(ox)$/i, '\1en')
24
+ inflect.plural(/^(oxen)$/i, '\1')
25
+ inflect.plural(/(quiz)$/i, '\1zes')
26
+
27
+ inflect.singular(/s$/i, '')
28
+ inflect.singular(/(n)ews$/i, '\1ews')
29
+ inflect.singular(/([ti])a$/i, '\1um')
30
+ inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '\1\2sis')
31
+ inflect.singular(/(^analy)ses$/i, '\1sis')
32
+ inflect.singular(/([^f])ves$/i, '\1fe')
33
+ inflect.singular(/(hive)s$/i, '\1')
34
+ inflect.singular(/(tive)s$/i, '\1')
35
+ inflect.singular(/([lr])ves$/i, '\1f')
36
+ inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y')
37
+ inflect.singular(/(s)eries$/i, '\1eries')
38
+ inflect.singular(/(m)ovies$/i, '\1ovie')
39
+ inflect.singular(/(x|ch|ss|sh)es$/i, '\1')
40
+ inflect.singular(/(m|l)ice$/i, '\1ouse')
41
+ inflect.singular(/(bus)es$/i, '\1')
42
+ inflect.singular(/(o)es$/i, '\1')
43
+ inflect.singular(/(shoe)s$/i, '\1')
44
+ inflect.singular(/(cris|ax|test)es$/i, '\1is')
45
+ inflect.singular(/(octop|vir)i$/i, '\1us')
46
+ inflect.singular(/(alias|status)es$/i, '\1')
47
+ inflect.singular(/^(ox)en/i, '\1')
48
+ inflect.singular(/(vert|ind)ices$/i, '\1ex')
49
+ inflect.singular(/(matr)ices$/i, '\1ix')
50
+ inflect.singular(/(quiz)zes$/i, '\1')
51
+ inflect.singular(/(database)s$/i, '\1')
52
+
53
+ inflect.irregular('person', 'people')
54
+ inflect.irregular('man', 'men')
55
+ inflect.irregular('child', 'children')
56
+ inflect.irregular('sex', 'sexes')
57
+ inflect.irregular('move', 'moves')
58
+ inflect.irregular('cow', 'kine')
59
+ inflect.irregular('zombie', 'zombies')
60
+
61
+ inflect.uncountable(%w(equipment information rice money species series fish sheep jeans))
62
+ end
63
+ end
@@ -0,0 +1,7 @@
1
+ # in case active_support/inflector is required without the rest of active_support
2
+ require 'active_support/inflector/inflections'
3
+ require 'active_support/inflector/transliterate'
4
+ require 'active_support/inflector/methods'
5
+
6
+ require 'active_support/inflections'
7
+ require 'active_support/core_ext/string/inflections'
@@ -0,0 +1,174 @@
1
+ module ActiveSupport
2
+ module Inflector
3
+ extend self
4
+
5
+ # A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional
6
+ # inflection rules. Examples:
7
+ #
8
+ # ActiveSupport::Inflector.inflections do |inflect|
9
+ # inflect.plural /^(ox)$/i, '\1\2en'
10
+ # inflect.singular /^(ox)en/i, '\1'
11
+ #
12
+ # inflect.irregular 'octopus', 'octopi'
13
+ #
14
+ # inflect.uncountable "equipment"
15
+ # end
16
+ #
17
+ # New rules are added at the top. So in the example above, the irregular rule for octopus will now be the first of the
18
+ # pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may
19
+ # already have been loaded.
20
+ class Inflections
21
+ def self.instance
22
+ @__instance__ ||= new
23
+ end
24
+
25
+ attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
26
+
27
+ def initialize
28
+ @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/
29
+ end
30
+
31
+ # Specifies a new acronym. An acronym must be specified as it will appear in a camelized string. An underscore
32
+ # string that contains the acronym will retain the acronym when passed to `camelize`, `humanize`, or `titleize`.
33
+ # A camelized string that contains the acronym will maintain the acronym when titleized or humanized, and will
34
+ # convert the acronym into a non-delimited single lowercase word when passed to +underscore+.
35
+ #
36
+ # Examples:
37
+ # acronym 'HTML'
38
+ # titleize 'html' #=> 'HTML'
39
+ # camelize 'html' #=> 'HTML'
40
+ # underscore 'MyHTML' #=> 'my_html'
41
+ #
42
+ # The acronym, however, must occur as a delimited unit and not be part of another word for conversions to recognize it:
43
+ #
44
+ # acronym 'HTTP'
45
+ # camelize 'my_http_delimited' #=> 'MyHTTPDelimited'
46
+ # camelize 'https' #=> 'Https', not 'HTTPs'
47
+ # underscore 'HTTPS' #=> 'http_s', not 'https'
48
+ #
49
+ # acronym 'HTTPS'
50
+ # camelize 'https' #=> 'HTTPS'
51
+ # underscore 'HTTPS' #=> 'https'
52
+ #
53
+ # Note: Acronyms that are passed to `pluralize` will no longer be recognized, since the acronym will not occur as
54
+ # a delimited unit in the pluralized result. To work around this, you must specify the pluralized form as an
55
+ # acronym as well:
56
+ #
57
+ # acronym 'API'
58
+ # camelize(pluralize('api')) #=> 'Apis'
59
+ #
60
+ # acronym 'APIs'
61
+ # camelize(pluralize('api')) #=> 'APIs'
62
+ #
63
+ # `acronym` may be used to specify any word that contains an acronym or otherwise needs to maintain a non-standard
64
+ # capitalization. The only restriction is that the word must begin with a capital letter.
65
+ #
66
+ # Examples:
67
+ # acronym 'RESTful'
68
+ # underscore 'RESTful' #=> 'restful'
69
+ # underscore 'RESTfulController' #=> 'restful_controller'
70
+ # titleize 'RESTfulController' #=> 'RESTful Controller'
71
+ # camelize 'restful' #=> 'RESTful'
72
+ # camelize 'restful_controller' #=> 'RESTfulController'
73
+ #
74
+ # acronym 'McDonald'
75
+ # underscore 'McDonald' #=> 'mcdonald'
76
+ # camelize 'mcdonald' #=> 'McDonald'
77
+ def acronym(word)
78
+ @acronyms[word.downcase] = word
79
+ @acronym_regex = /#{@acronyms.values.join("|")}/
80
+ end
81
+
82
+ # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression.
83
+ # The replacement should always be a string that may include references to the matched data from the rule.
84
+ def plural(rule, replacement)
85
+ @uncountables.delete(rule) if rule.is_a?(String)
86
+ @uncountables.delete(replacement)
87
+ @plurals.insert(0, [rule, replacement])
88
+ end
89
+
90
+ # Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression.
91
+ # The replacement should always be a string that may include references to the matched data from the rule.
92
+ def singular(rule, replacement)
93
+ @uncountables.delete(rule) if rule.is_a?(String)
94
+ @uncountables.delete(replacement)
95
+ @singulars.insert(0, [rule, replacement])
96
+ end
97
+
98
+ # Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
99
+ # for strings, not regular expressions. You simply pass the irregular in singular and plural form.
100
+ #
101
+ # Examples:
102
+ # irregular 'octopus', 'octopi'
103
+ # irregular 'person', 'people'
104
+ def irregular(singular, plural)
105
+ @uncountables.delete(singular)
106
+ @uncountables.delete(plural)
107
+ if singular[0,1].upcase == plural[0,1].upcase
108
+ plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1])
109
+ plural(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + plural[1..-1])
110
+ singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1])
111
+ else
112
+ plural(Regexp.new("#{singular[0,1].upcase}(?i)#{singular[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
113
+ plural(Regexp.new("#{singular[0,1].downcase}(?i)#{singular[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
114
+ plural(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
115
+ plural(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
116
+ singular(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), singular[0,1].upcase + singular[1..-1])
117
+ singular(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), singular[0,1].downcase + singular[1..-1])
118
+ end
119
+ end
120
+
121
+ # Add uncountable words that shouldn't be attempted inflected.
122
+ #
123
+ # Examples:
124
+ # uncountable "money"
125
+ # uncountable "money", "information"
126
+ # uncountable %w( money information rice )
127
+ def uncountable(*words)
128
+ (@uncountables << words).flatten!
129
+ end
130
+
131
+ # Specifies a humanized form of a string by a regular expression rule or by a string mapping.
132
+ # When using a regular expression based replacement, the normal humanize formatting is called after the replacement.
133
+ # When a string is used, the human form should be specified as desired (example: 'The name', not 'the_name')
134
+ #
135
+ # Examples:
136
+ # human /_cnt$/i, '\1_count'
137
+ # human "legacy_col_person_name", "Name"
138
+ def human(rule, replacement)
139
+ @humans.insert(0, [rule, replacement])
140
+ end
141
+
142
+ # Clears the loaded inflections within a given scope (default is <tt>:all</tt>).
143
+ # Give the scope as a symbol of the inflection type, the options are: <tt>:plurals</tt>,
144
+ # <tt>:singulars</tt>, <tt>:uncountables</tt>, <tt>:humans</tt>.
145
+ #
146
+ # Examples:
147
+ # clear :all
148
+ # clear :plurals
149
+ def clear(scope = :all)
150
+ case scope
151
+ when :all
152
+ @plurals, @singulars, @uncountables, @humans = [], [], [], []
153
+ else
154
+ instance_variable_set "@#{scope}", []
155
+ end
156
+ end
157
+ end
158
+
159
+ # Yields a singleton instance of Inflector::Inflections so you can specify additional
160
+ # inflector rules.
161
+ #
162
+ # Example:
163
+ # ActiveSupport::Inflector.inflections do |inflect|
164
+ # inflect.uncountable "rails"
165
+ # end
166
+ def inflections
167
+ if block_given?
168
+ yield Inflections.instance
169
+ else
170
+ Inflections.instance
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,321 @@
1
+ require 'active_support/inflector/inflections'
2
+ require 'active_support/inflections'
3
+
4
+ module ActiveSupport
5
+ # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without,
6
+ # and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept
7
+ # in inflections.rb.
8
+ #
9
+ # The Rails core team has stated patches for the inflections library will not be accepted
10
+ # in order to avoid breaking legacy applications which may be relying on errant inflections.
11
+ # If you discover an incorrect inflection and require it for your application, you'll need
12
+ # to correct it yourself (explained below).
13
+ module Inflector
14
+ extend self
15
+
16
+ # Returns the plural form of the word in the string.
17
+ #
18
+ # Examples:
19
+ # "post".pluralize # => "posts"
20
+ # "octopus".pluralize # => "octopi"
21
+ # "sheep".pluralize # => "sheep"
22
+ # "words".pluralize # => "words"
23
+ # "CamelOctopus".pluralize # => "CamelOctopi"
24
+ def pluralize(word)
25
+ apply_inflections(word, inflections.plurals)
26
+ end
27
+
28
+ # The reverse of +pluralize+, returns the singular form of a word in a string.
29
+ #
30
+ # Examples:
31
+ # "posts".singularize # => "post"
32
+ # "octopi".singularize # => "octopus"
33
+ # "sheep".singularize # => "sheep"
34
+ # "word".singularize # => "word"
35
+ # "CamelOctopi".singularize # => "CamelOctopus"
36
+ def singularize(word)
37
+ apply_inflections(word, inflections.singulars)
38
+ end
39
+
40
+ # By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+
41
+ # is set to <tt>:lower</tt> then +camelize+ produces lowerCamelCase.
42
+ #
43
+ # +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
44
+ #
45
+ # Examples:
46
+ # "active_model".camelize # => "ActiveModel"
47
+ # "active_model".camelize(:lower) # => "activeModel"
48
+ # "active_model/errors".camelize # => "ActiveModel::Errors"
49
+ # "active_model/errors".camelize(:lower) # => "activeModel::Errors"
50
+ #
51
+ # As a rule of thumb you can think of +camelize+ as the inverse of +underscore+,
52
+ # though there are cases where that does not hold:
53
+ #
54
+ # "SSLError".underscore.camelize # => "SslError"
55
+ def camelize(term, uppercase_first_letter = true)
56
+ string = term.to_s
57
+ if uppercase_first_letter
58
+ string = string.sub(/^[a-z\d]*/) { inflections.acronyms[$&] || $&.capitalize }
59
+ else
60
+ string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase }
61
+ end
62
+ string.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }.gsub('/', '::')
63
+ end
64
+
65
+ # Makes an underscored, lowercase form from the expression in the string.
66
+ #
67
+ # Changes '::' to '/' to convert namespaces to paths.
68
+ #
69
+ # Examples:
70
+ # "ActiveModel".underscore # => "active_model"
71
+ # "ActiveModel::Errors".underscore # => "active_model/errors"
72
+ #
73
+ # As a rule of thumb you can think of +underscore+ as the inverse of +camelize+,
74
+ # though there are cases where that does not hold:
75
+ #
76
+ # "SSLError".underscore.camelize # => "SslError"
77
+ def underscore(camel_cased_word)
78
+ word = camel_cased_word.to_s.dup
79
+ word.gsub!(/::/, '/')
80
+ word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" }
81
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
82
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
83
+ word.tr!("-", "_")
84
+ word.downcase!
85
+ word
86
+ end
87
+
88
+ # Capitalizes the first word and turns underscores into spaces and strips a
89
+ # trailing "_id", if any. Like +titleize+, this is meant for creating pretty output.
90
+ #
91
+ # Examples:
92
+ # "employee_salary" # => "Employee salary"
93
+ # "author_id" # => "Author"
94
+ def humanize(lower_case_and_underscored_word)
95
+ result = lower_case_and_underscored_word.to_s.dup
96
+ inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
97
+ result.gsub!(/_id$/, "")
98
+ result.gsub!(/_/, ' ')
99
+ result.gsub(/([a-z\d]*)/i) { |match|
100
+ "#{inflections.acronyms[match] || match.downcase}"
101
+ }.gsub(/^\w/) { $&.upcase }
102
+ end
103
+
104
+ # Capitalizes all the words and replaces some characters in the string to create
105
+ # a nicer looking title. +titleize+ is meant for creating pretty output. It is not
106
+ # used in the Rails internals.
107
+ #
108
+ # +titleize+ is also aliased as as +titlecase+.
109
+ #
110
+ # Examples:
111
+ # "man from the boondocks".titleize # => "Man From The Boondocks"
112
+ # "x-men: the last stand".titleize # => "X Men: The Last Stand"
113
+ # "TheManWithoutAPast".titleize # => "The Man Without A Past"
114
+ # "raiders_of_the_lost_ark".titleize # => "Raiders Of The Lost Ark"
115
+ def titleize(word)
116
+ humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize }
117
+ end
118
+
119
+ # Create the name of a table like Rails does for models to table names. This method
120
+ # uses the +pluralize+ method on the last word in the string.
121
+ #
122
+ # Examples
123
+ # "RawScaledScorer".tableize # => "raw_scaled_scorers"
124
+ # "egg_and_ham".tableize # => "egg_and_hams"
125
+ # "fancyCategory".tableize # => "fancy_categories"
126
+ def tableize(class_name)
127
+ pluralize(underscore(class_name))
128
+ end
129
+
130
+ # Create a class name from a plural table name like Rails does for table names to models.
131
+ # Note that this returns a string and not a Class. (To convert to an actual class
132
+ # follow +classify+ with +constantize+.)
133
+ #
134
+ # Examples:
135
+ # "egg_and_hams".classify # => "EggAndHam"
136
+ # "posts".classify # => "Post"
137
+ #
138
+ # Singular names are not handled correctly:
139
+ # "business".classify # => "Busines"
140
+ def classify(table_name)
141
+ # strip out any leading schema name
142
+ camelize(singularize(table_name.to_s.sub(/.*\./, '')))
143
+ end
144
+
145
+ # Replaces underscores with dashes in the string.
146
+ #
147
+ # Example:
148
+ # "puni_puni" # => "puni-puni"
149
+ def dasherize(underscored_word)
150
+ underscored_word.gsub(/_/, '-')
151
+ end
152
+
153
+ # Removes the module part from the expression in the string:
154
+ #
155
+ # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections"
156
+ # "Inflections".demodulize # => "Inflections"
157
+ #
158
+ # See also +deconstantize+.
159
+ def demodulize(path)
160
+ path = path.to_s
161
+ if i = path.rindex('::')
162
+ path[(i+2)..-1]
163
+ else
164
+ path
165
+ end
166
+ end
167
+
168
+ # Removes the rightmost segment from the constant expression in the string:
169
+ #
170
+ # "Net::HTTP".deconstantize # => "Net"
171
+ # "::Net::HTTP".deconstantize # => "::Net"
172
+ # "String".deconstantize # => ""
173
+ # "::String".deconstantize # => ""
174
+ # "".deconstantize # => ""
175
+ #
176
+ # See also +demodulize+.
177
+ def deconstantize(path)
178
+ path.to_s[0...(path.rindex('::') || 0)] # implementation based on the one in facets' Module#spacename
179
+ end
180
+
181
+ # Creates a foreign key name from a class name.
182
+ # +separate_class_name_and_id_with_underscore+ sets whether
183
+ # the method should put '_' between the name and 'id'.
184
+ #
185
+ # Examples:
186
+ # "Message".foreign_key # => "message_id"
187
+ # "Message".foreign_key(false) # => "messageid"
188
+ # "Admin::Post".foreign_key # => "post_id"
189
+ def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
190
+ underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
191
+ end
192
+
193
+ # Ruby 1.9 introduces an inherit argument for Module#const_get and
194
+ # #const_defined? and changes their default behavior.
195
+ if Module.method(:const_get).arity == 1
196
+ # Tries to find a constant with the name specified in the argument string:
197
+ #
198
+ # "Module".constantize # => Module
199
+ # "Test::Unit".constantize # => Test::Unit
200
+ #
201
+ # The name is assumed to be the one of a top-level constant, no matter whether
202
+ # it starts with "::" or not. No lexical context is taken into account:
203
+ #
204
+ # C = 'outside'
205
+ # module M
206
+ # C = 'inside'
207
+ # C # => 'inside'
208
+ # "C".constantize # => 'outside', same as ::C
209
+ # end
210
+ #
211
+ # NameError is raised when the name is not in CamelCase or the constant is
212
+ # unknown.
213
+ def constantize(camel_cased_word)
214
+ names = camel_cased_word.split('::')
215
+ names.shift if names.empty? || names.first.empty?
216
+
217
+ constant = Object
218
+ names.each do |name|
219
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
220
+ end
221
+ constant
222
+ end
223
+ else
224
+ def constantize(camel_cased_word) #:nodoc:
225
+ names = camel_cased_word.split('::')
226
+ names.shift if names.empty? || names.first.empty?
227
+
228
+ constant = Object
229
+ names.each do |name|
230
+ constant = constant.const_defined?(name, false) ? constant.const_get(name) : constant.const_missing(name)
231
+ end
232
+ constant
233
+ end
234
+ end
235
+
236
+ # Tries to find a constant with the name specified in the argument string:
237
+ #
238
+ # "Module".safe_constantize # => Module
239
+ # "Test::Unit".safe_constantize # => Test::Unit
240
+ #
241
+ # The name is assumed to be the one of a top-level constant, no matter whether
242
+ # it starts with "::" or not. No lexical context is taken into account:
243
+ #
244
+ # C = 'outside'
245
+ # module M
246
+ # C = 'inside'
247
+ # C # => 'inside'
248
+ # "C".safe_constantize # => 'outside', same as ::C
249
+ # end
250
+ #
251
+ # nil is returned when the name is not in CamelCase or the constant (or part of it) is
252
+ # unknown.
253
+ #
254
+ # "blargle".safe_constantize # => nil
255
+ # "UnknownModule".safe_constantize # => nil
256
+ # "UnknownModule::Foo::Bar".safe_constantize # => nil
257
+ #
258
+ def safe_constantize(camel_cased_word)
259
+ begin
260
+ constantize(camel_cased_word)
261
+ rescue NameError => e
262
+ raise unless e.message =~ /(uninitialized constant|wrong constant name) #{const_regexp(camel_cased_word)}$/ ||
263
+ e.name.to_s == camel_cased_word.to_s
264
+ rescue ArgumentError => e
265
+ raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/
266
+ end
267
+ end
268
+
269
+ # Turns a number into an ordinal string used to denote the position in an
270
+ # ordered sequence such as 1st, 2nd, 3rd, 4th.
271
+ #
272
+ # Examples:
273
+ # ordinalize(1) # => "1st"
274
+ # ordinalize(2) # => "2nd"
275
+ # ordinalize(1002) # => "1002nd"
276
+ # ordinalize(1003) # => "1003rd"
277
+ # ordinalize(-11) # => "-11th"
278
+ # ordinalize(-1021) # => "-1021st"
279
+ def ordinalize(number)
280
+ if (11..13).include?(number.to_i.abs % 100)
281
+ "#{number}th"
282
+ else
283
+ case number.to_i.abs % 10
284
+ when 1; "#{number}st"
285
+ when 2; "#{number}nd"
286
+ when 3; "#{number}rd"
287
+ else "#{number}th"
288
+ end
289
+ end
290
+ end
291
+
292
+ private
293
+
294
+ # Mount a regular expression that will match part by part of the constant.
295
+ # For instance, Foo::Bar::Baz will generate Foo(::Bar(::Baz)?)?
296
+ def const_regexp(camel_cased_word) #:nodoc:
297
+ parts = camel_cased_word.split("::")
298
+ last = parts.pop
299
+
300
+ parts.reverse.inject(last) do |acc, part|
301
+ part.empty? ? acc : "#{part}(::#{acc})?"
302
+ end
303
+ end
304
+
305
+ # Applies inflection rules for +singularize+ and +pluralize+.
306
+ #
307
+ # Examples:
308
+ # apply_inflections("post", inflections.plurals) # => "posts"
309
+ # apply_inflections("posts", inflections.singulars) # => "post"
310
+ def apply_inflections(word, rules)
311
+ result = word.to_s.dup
312
+
313
+ if word.empty? || inflections.uncountables.any? { |inflection| result =~ /\b#{inflection}\Z/i }
314
+ result
315
+ else
316
+ rules.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
317
+ result
318
+ end
319
+ end
320
+ end
321
+ end