activesupport-inflector 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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