aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support/inflector
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport/lib/active_support/inflector')
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb70
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb99
2 files changed, 110 insertions, 59 deletions
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index 9cf4b2b2ba..486838bd15 100644
--- a/activesupport/lib/active_support/inflector/inflections.rb
+++ b/activesupport/lib/active_support/inflector/inflections.rb
@@ -52,21 +52,21 @@ module ActiveSupport
# into a non-delimited single lowercase word when passed to +underscore+.
#
# acronym 'HTML'
- # titleize 'html' #=> 'HTML'
- # camelize 'html' #=> 'HTML'
- # underscore 'MyHTML' #=> 'my_html'
+ # titleize 'html' # => 'HTML'
+ # camelize 'html' # => 'HTML'
+ # underscore 'MyHTML' # => 'my_html'
#
# The acronym, however, must occur as a delimited unit and not be part of
# another word for conversions to recognize it:
#
# acronym 'HTTP'
- # camelize 'my_http_delimited' #=> 'MyHTTPDelimited'
- # camelize 'https' #=> 'Https', not 'HTTPs'
- # underscore 'HTTPS' #=> 'http_s', not 'https'
+ # camelize 'my_http_delimited' # => 'MyHTTPDelimited'
+ # camelize 'https' # => 'Https', not 'HTTPs'
+ # underscore 'HTTPS' # => 'http_s', not 'https'
#
# acronym 'HTTPS'
- # camelize 'https' #=> 'HTTPS'
- # underscore 'HTTPS' #=> 'https'
+ # camelize 'https' # => 'HTTPS'
+ # underscore 'HTTPS' # => 'https'
#
# Note: Acronyms that are passed to +pluralize+ will no longer be
# recognized, since the acronym will not occur as a delimited unit in the
@@ -74,25 +74,25 @@ module ActiveSupport
# form as an acronym as well:
#
# acronym 'API'
- # camelize(pluralize('api')) #=> 'Apis'
+ # camelize(pluralize('api')) # => 'Apis'
#
# acronym 'APIs'
- # camelize(pluralize('api')) #=> 'APIs'
+ # camelize(pluralize('api')) # => 'APIs'
#
# +acronym+ may be used to specify any word that contains an acronym or
# otherwise needs to maintain a non-standard capitalization. The only
# restriction is that the word must begin with a capital letter.
#
# acronym 'RESTful'
- # underscore 'RESTful' #=> 'restful'
- # underscore 'RESTfulController' #=> 'restful_controller'
- # titleize 'RESTfulController' #=> 'RESTful Controller'
- # camelize 'restful' #=> 'RESTful'
- # camelize 'restful_controller' #=> 'RESTfulController'
+ # underscore 'RESTful' # => 'restful'
+ # underscore 'RESTfulController' # => 'restful_controller'
+ # titleize 'RESTfulController' # => 'RESTful Controller'
+ # camelize 'restful' # => 'RESTful'
+ # camelize 'restful_controller' # => 'RESTfulController'
#
# acronym 'McDonald'
- # underscore 'McDonald' #=> 'mcdonald'
- # camelize 'mcdonald' #=> 'McDonald'
+ # underscore 'McDonald' # => 'mcdonald'
+ # camelize 'mcdonald' # => 'McDonald'
def acronym(word)
@acronyms[word.downcase] = word
@acronym_regex = /#{@acronyms.values.join("|")}/
@@ -128,27 +128,39 @@ module ActiveSupport
def irregular(singular, plural)
@uncountables.delete(singular)
@uncountables.delete(plural)
- if singular[0,1].upcase == plural[0,1].upcase
- plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1])
- plural(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + plural[1..-1])
- singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1])
+
+ s0 = singular[0]
+ srest = singular[1..-1]
+
+ p0 = plural[0]
+ prest = plural[1..-1]
+
+ if s0.upcase == p0.upcase
+ plural(/(#{s0})#{srest}$/i, '\1' + prest)
+ plural(/(#{p0})#{prest}$/i, '\1' + prest)
+
+ singular(/(#{s0})#{srest}$/i, '\1' + srest)
+ singular(/(#{p0})#{prest}$/i, '\1' + srest)
else
- plural(Regexp.new("#{singular[0,1].upcase}(?i)#{singular[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
- plural(Regexp.new("#{singular[0,1].downcase}(?i)#{singular[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
- plural(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
- plural(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
- singular(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), singular[0,1].upcase + singular[1..-1])
- singular(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), singular[0,1].downcase + singular[1..-1])
+ plural(/#{s0.upcase}(?i)#{srest}$/, p0.upcase + prest)
+ plural(/#{s0.downcase}(?i)#{srest}$/, p0.downcase + prest)
+ plural(/#{p0.upcase}(?i)#{prest}$/, p0.upcase + prest)
+ plural(/#{p0.downcase}(?i)#{prest}$/, p0.downcase + prest)
+
+ singular(/#{s0.upcase}(?i)#{srest}$/, s0.upcase + srest)
+ singular(/#{s0.downcase}(?i)#{srest}$/, s0.downcase + srest)
+ singular(/#{p0.upcase}(?i)#{prest}$/, s0.upcase + srest)
+ singular(/#{p0.downcase}(?i)#{prest}$/, s0.downcase + srest)
end
end
- # Add uncountable words that shouldn't be attempted inflected.
+ # Specifies words that are uncountable and should not be inflected.
#
# uncountable 'money'
# uncountable 'money', 'information'
# uncountable %w( money information rice )
def uncountable(*words)
- (@uncountables << words).flatten!
+ @uncountables += words.flatten.map(&:downcase)
end
# Specifies a humanized form of a string by a regular expression rule or
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 1eb2b4212b..74b3a7c2a9 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -1,6 +1,5 @@
# encoding: utf-8
-require 'active_support/inflector/inflections'
require 'active_support/inflections'
module ActiveSupport
@@ -37,7 +36,7 @@ module ActiveSupport
# string.
#
# If passed an optional +locale+ parameter, the word will be
- # pluralized using rules defined for that language. By default,
+ # singularized using rules defined for that language. By default,
# this parameter is set to <tt>:en</tt>.
#
# 'posts'.singularize # => "post"
@@ -73,7 +72,9 @@ module ActiveSupport
else
string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase }
end
- string.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }.gsub('/', '::')
+ string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }
+ string.gsub!(/\//, '::')
+ string
end
# Makes an underscored, lowercase form from the expression in the string.
@@ -88,9 +89,9 @@ module ActiveSupport
#
# 'SSLError'.underscore.camelize # => "SslError"
def underscore(camel_cased_word)
- word = camel_cased_word.to_s.dup
- word.gsub!('::', '/')
- word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" }
+ return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
+ word = camel_cased_word.to_s.gsub(/::/, '/')
+ word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'}#{$2.downcase}" }
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
word.tr!("-", "_")
@@ -98,20 +99,47 @@ module ActiveSupport
word
end
- # Capitalizes the first word and turns underscores into spaces and strips a
- # trailing "_id", if any. Like +titleize+, this is meant for creating pretty
- # output.
+ # Tweaks an attribute name for display to end users.
+ #
+ # Specifically, +humanize+ performs these transformations:
+ #
+ # * Applies human inflection rules to the argument.
+ # * Deletes leading underscores, if any.
+ # * Removes a "_id" suffix if present.
+ # * Replaces underscores with spaces, if any.
+ # * Downcases all words except acronyms.
+ # * Capitalizes the first word.
+ #
+ # The capitalization of the first word can be turned off by setting the
+ # +:capitalize+ option to false (default is true).
+ #
+ # humanize('employee_salary') # => "Employee salary"
+ # humanize('author_id') # => "Author"
+ # humanize('author_id', capitalize: false) # => "author"
+ # humanize('_id') # => "Id"
#
- # 'employee_salary'.humanize # => "Employee salary"
- # 'author_id'.humanize # => "Author"
- def humanize(lower_case_and_underscored_word)
+ # If "SSL" was defined to be an acronym:
+ #
+ # humanize('ssl_error') # => "SSL error"
+ #
+ def humanize(lower_case_and_underscored_word, options = {})
result = lower_case_and_underscored_word.to_s.dup
+
inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
- result.gsub!(/_id$/, "")
+
+ result.sub!(/\A_+/, '')
+ result.sub!(/_id\z/, '')
result.tr!('_', ' ')
- result.gsub(/([a-z\d]*)/i) { |match|
+
+ result.gsub!(/([a-z\d]*)/i) do |match|
"#{inflections.acronyms[match] || match.downcase}"
- }.gsub(/^\w/) { $&.upcase }
+ end
+
+ if options.fetch(:capitalize, true)
+ result.sub!(/\A\w/) { |match| match.upcase }
+ end
+
+ result
end
# Capitalizes all the words and replaces some characters in the string to
@@ -147,7 +175,7 @@ module ActiveSupport
#
# Singular names are not handled correctly:
#
- # 'business'.classify # => "Busines"
+ # 'calculus'.classify # => "Calculu"
def classify(table_name)
# strip out any leading schema name
camelize(singularize(table_name.to_s.sub(/.*\./, '')))
@@ -164,6 +192,8 @@ module ActiveSupport
#
# 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
# 'Inflections'.demodulize # => "Inflections"
+ # '::Inflections'.demodulize # => "Inflections"
+ # ''.demodulize # => ""
#
# See also +deconstantize+.
def demodulize(path)
@@ -185,7 +215,7 @@ module ActiveSupport
#
# See also +demodulize+.
def deconstantize(path)
- path.to_s[0...(path.rindex('::') || 0)] # implementation based on the one in facets' Module#spacename
+ path.to_s[0, path.rindex('::') || 0] # implementation based on the one in facets' Module#spacename
end
# Creates a foreign key name from a class name.
@@ -219,7 +249,12 @@ module ActiveSupport
# unknown.
def constantize(camel_cased_word)
names = camel_cased_word.split('::')
- names.shift if names.empty? || names.first.empty?
+
+ # Trigger a built-in NameError exception including the ill-formed constant in the message.
+ Object.const_get(camel_cased_word) if names.empty?
+
+ # Remove the first blank element in case of '::ClassName' notation.
+ names.shift if names.size > 1 && names.first.empty?
names.inject(Object) do |constant, name|
if constant == Object
@@ -229,8 +264,8 @@ module ActiveSupport
next candidate if constant.const_defined?(name, false)
next candidate unless Object.const_defined?(name)
- # Go down the ancestors to check it it's owned
- # directly before we reach Object or the end of ancestors.
+ # Go down the ancestors to check if it is owned directly. The check
+ # stops when we reach Object or the end of ancestors tree.
constant = constant.ancestors.inject do |const, ancestor|
break const if ancestor == Object
break ancestor if ancestor.const_defined?(name, false)
@@ -266,14 +301,12 @@ module ActiveSupport
# 'UnknownModule'.safe_constantize # => nil
# 'UnknownModule::Foo::Bar'.safe_constantize # => nil
def safe_constantize(camel_cased_word)
- begin
- constantize(camel_cased_word)
- rescue NameError => e
- raise unless e.message =~ /(uninitialized constant|wrong constant name) #{const_regexp(camel_cased_word)}$/ ||
- e.name.to_s == camel_cased_word.to_s
- rescue ArgumentError => e
- raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/
- end
+ constantize(camel_cased_word)
+ rescue NameError => e
+ raise if e.name && !(camel_cased_word.to_s.split("::").include?(e.name.to_s) ||
+ e.name.to_s == camel_cased_word.to_s)
+ rescue ArgumentError => e
+ raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/
end
# Returns the suffix that should be added to a number to denote the position
@@ -315,10 +348,16 @@ module ActiveSupport
private
- # Mount a regular expression that will match part by part of the constant.
- # For instance, Foo::Bar::Baz will generate Foo(::Bar(::Baz)?)?
+ # Mounts a regular expression, returned as a string to ease interpolation,
+ # that will match part by part the given constant.
+ #
+ # const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?"
+ # const_regexp("::") # => "::"
def const_regexp(camel_cased_word) #:nodoc:
parts = camel_cased_word.split("::")
+
+ return Regexp.escape(camel_cased_word) if parts.blank?
+
last = parts.pop
parts.reverse.inject(last) do |acc, part|