module ActiveSupport::Inflector

Experimental RBS support (using type sampling data from the type_fusion project).

# sig/active_support/inflector/inflections.rbs

module ActiveSupport::Inflector
  def inflections: (?Symbol locale) -> ActiveSupport::Inflector::Inflections
  def pluralize: (String word, ?Symbol locale) -> String
end

def apply_inflections(word, rules, locale = :en)

apply_inflections('posts', inflections.singulars, :en) # => "post"
apply_inflections('post', inflections.plurals, :en) # => "posts"

found for that locale.
If passed an optional +locale+ parameter, the uncountables will be

Applies inflection rules for +singularize+ and +pluralize+.
def apply_inflections(word, rules, locale = :en)
  result = word.to_s.dup
  if word.empty? || inflections(locale).uncountables.uncountable?(result)
    result
  else
    rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
    result
  end
end

def camelize(term, uppercase_first_letter = true)

camelize(underscore('SSLError')) # => "SslError"

#underscore, though there are cases where that does not hold:
As a rule of thumb you can think of +camelize+ as the inverse of

camelize('active_model/errors', false) # => "activeModel::Errors"
camelize('active_model/errors') # => "ActiveModel::Errors"
camelize('active_model', false) # => "activeModel"
camelize('active_model') # => "ActiveModel"

paths to namespaces.
Also converts '/' to '::' which is useful for converting

lowerCamelCase.
If the +uppercase_first_letter+ parameter is set to false, then produces
Converts strings to UpperCamelCase.
def camelize(term, uppercase_first_letter = true)
  string = term.to_s
  # String#camelize takes a symbol (:upper or :lower), so here we also support :lower to keep the methods consistent.
  if !uppercase_first_letter || uppercase_first_letter == :lower
    string = string.sub(inflections.acronyms_camelize_regex) { |match| match.downcase! || match }
  elsif string.match?(/\A[a-z\d]*\z/)
    return inflections.acronyms[string]&.dup || string.capitalize
  else
    string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize! || match }
  end
  string.gsub!(/(?:_|(\/))([a-z\d]*)/i) do
    word = $2
    substituted = inflections.acronyms[word] || word.capitalize! || word
    $1 ? "::#{substituted}" : substituted
  end
  string
end

def classify(table_name)

classify('calculus') # => "Calculu"

Singular names are not handled correctly:

classify('posts') # => "Post"
classify('ham_and_eggs') # => "HamAndEgg"

convert to an actual class follow +classify+ with #constantize.)
names to models. Note that this returns a string and not a Class. (To
Creates a class name from a plural table name like \Rails does for table
def classify(table_name)
  # strip out any leading schema name
  camelize(singularize(table_name.to_s.sub(/.*\./, "")))
end

def const_regexp(camel_cased_word)

const_regexp("::") # => "::"
const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?"

that will match part by part the given constant.
Mounts a regular expression, returned as a string to ease interpolation,
def const_regexp(camel_cased_word)
  parts = camel_cased_word.split("::")
  return Regexp.escape(camel_cased_word) if parts.empty?
  last = parts.pop
  parts.reverse!.inject(last) do |acc, part|
    part.empty? ? acc : "#{part}(::#{acc})?"
  end
end

def constantize(camel_cased_word)

unknown.
NameError is raised when the name is not in CamelCase or the constant is

end
constantize('C') # => 'outside', same as ::C
C # => 'inside'
C = 'inside'
module M
C = 'outside'

account:
whether it starts with "::" or not. No lexical context is taken into
The name is assumed to be the one of a top-level constant, no matter

constantize('Foo::Bar') # => Foo::Bar
constantize('Module') # => Module

Tries to find a constant with the name specified in the argument string.
def constantize(camel_cased_word)
  Object.const_get(camel_cased_word)
end

def dasherize(underscored_word)

dasherize('puni_puni') # => "puni-puni"

Replaces underscores with dashes in the string.
def dasherize(underscored_word)
  underscored_word.tr("_", "-")
end

def deconstantize(path)

See also #demodulize.

deconstantize('') # => ""
deconstantize('::String') # => ""
deconstantize('String') # => ""
deconstantize('::Net::HTTP') # => "::Net"
deconstantize('Net::HTTP') # => "Net"

Removes the rightmost segment from the constant expression in the string.
def deconstantize(path)
  path.to_s[0, path.rindex("::") || 0] # implementation based on the one in facets' Module#spacename
end

def demodulize(path)

See also #deconstantize.

demodulize('') # => ""
demodulize('::Inflections') # => "Inflections"
demodulize('Inflections') # => "Inflections"
demodulize('ActiveSupport::Inflector::Inflections') # => "Inflections"

Removes the module part from the expression in the string.
def demodulize(path)
  path = path.to_s
  if i = path.rindex("::")
    path[(i + 2), path.length]
  else
    path
  end
end

def downcase_first(string)

downcase_first('') # => ""
downcase_first('I') # => "i"
downcase_first('If they enjoyed The Matrix') # => "if they enjoyed The Matrix"

Converts the first character in the string to lowercase.
def downcase_first(string)
  string.length > 0 ? string[0].downcase.concat(string[1..-1]) : +""
end

def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)

foreign_key('Admin::Post') # => "post_id"
foreign_key('Message', false) # => "messageid"
foreign_key('Message') # => "message_id"

the method should put '_' between the name and 'id'.
+separate_class_name_and_id_with_underscore+ sets whether
Creates a foreign key name from a class name.
def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
  underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
end

def humanize(lower_case_and_underscored_word, capitalize: true, keep_id_suffix: false)


humanize('ssl_error') # => "SSL error"

If "SSL" was defined to be an acronym:

humanize('author_id', keep_id_suffix: true) # => "Author id"
humanize('_id') # => "Id"
humanize('author_id', capitalize: false) # => "author"
humanize('author_id') # => "Author"
humanize('employee_salary') # => "Employee salary"

optional parameter +keep_id_suffix+ to true (default is false).
The trailing '_id' can be kept and capitalized by setting the

+:capitalize+ option to false (default is true).
The capitalization of the first word can be turned off by setting the
* Capitalizes the first word.
* Downcases all words except acronyms.
* Replaces underscores with spaces, if any.
* Removes an "_id" suffix if present.
* Deletes leading underscores, if any.
* Applies human inflection rules to the argument.

Specifically, performs these transformations:

Tweaks an attribute name for display to end users.
def humanize(lower_case_and_underscored_word, capitalize: true, keep_id_suffix: false)
  result = lower_case_and_underscored_word.to_s.dup
  inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
  result.tr!("_", " ")
  result.lstrip!
  if !keep_id_suffix && lower_case_and_underscored_word&.end_with?("_id")
    result.delete_suffix!(" id")
  end
  result.gsub!(/([a-z\d]+)/i) do |match|
    match.downcase!
    inflections.acronyms[match] || match
  end
  if capitalize
    result.sub!(/\A\w/) do |match|
      match.upcase!
      match
    end
  end
  result
end

def inflections(locale = :en)

Experimental RBS support (using type sampling data from the type_fusion project).

def inflections: (?Symbol locale) -> ActiveSupport::Inflector::Inflections

This signature was generated using 1 sample from 1 application.

end
inflect.uncountable 'rails'
ActiveSupport::Inflector.inflections(:en) do |inflect|

Only rules for English are provided.
languages can be specified. If not specified, defaults to :en.
additional inflector rules. If passed an optional locale, rules for other
Yields a singleton instance of Inflector::Inflections so you can specify
def inflections(locale = :en)
  if block_given?
    yield Inflections.instance(locale)
  else
    Inflections.instance_or_fallback(locale)
  end
end

def ordinal(number)

ordinal(-1021) # => "st"
ordinal(-11) # => "th"
ordinal(1003) # => "rd"
ordinal(1002) # => "nd"
ordinal(2) # => "nd"
ordinal(1) # => "st"

in an ordered sequence such as 1st, 2nd, 3rd, 4th.
Returns the suffix that should be added to a number to denote the position
def ordinal(number)
  I18n.translate("number.nth.ordinals", number: number)
end

def ordinalize(number)

ordinalize(-1021) # => "-1021st"
ordinalize(-11) # => "-11th"
ordinalize(1003) # => "1003rd"
ordinalize(1002) # => "1002nd"
ordinalize(2) # => "2nd"
ordinalize(1) # => "1st"

ordered sequence such as 1st, 2nd, 3rd, 4th.
Turns a number into an ordinal string used to denote the position in an
def ordinalize(number)
  I18n.translate("number.nth.ordinalized", number: number)
end

def parameterize(string, separator: "-", preserve_case: false, locale: nil)

the configured I18n.locale.
By default, this parameter is set to nil and it will use
the word will be parameterized as a word of that language.
If the optional parameter +locale+ is specified,

parameterize("^très_Jolie-- ", separator: ".") # => "tres_jolie--"
parameterize("^très|Jolie-- ", separator: "_") # => "tres_jolie--"
parameterize("^très|Jolie__ ") # => "tres-jolie__"

It preserves dashes and underscores unless they are used as separators:

parameterize("^très|Jolie-- ", preserve_case: true) # => "tres-Jolie"
parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth"

To preserve the case of the characters in a string, use the +preserve_case+ argument.

parameterize("^très|Jolie__ ", separator: '_') # => "tres_jolie"
parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth"

To use a custom separator, override the +separator+ argument.

parameterize("^très|Jolie-- ") # => "tres-jolie"
parameterize("Donald E. Knuth") # => "donald-e-knuth"

a 'pretty' URL.
Replaces special characters in a string so that it may be used as part of
def parameterize(string, separator: "-", preserve_case: false, locale: nil)
  # Replace accented chars with their ASCII equivalents.
  parameterized_string = transliterate(string, locale: locale)
  # Turn unwanted chars into the separator.
  parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator)
  unless separator.nil? || separator.empty?
    if separator == "-"
      re_duplicate_separator        = /-{2,}/
      re_leading_trailing_separator = /^-|-$/i
    else
      re_sep = Regexp.escape(separator)
      re_duplicate_separator        = /#{re_sep}{2,}/
      re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i
    end
    # No more than one of the separator in a row.
    parameterized_string.gsub!(re_duplicate_separator, separator)
    # Remove leading/trailing separator.
    parameterized_string.gsub!(re_leading_trailing_separator, "")
  end
  parameterized_string.downcase! unless preserve_case
  parameterized_string
end

def pluralize(word, locale = :en)

Experimental RBS support (using type sampling data from the type_fusion project).

def pluralize: (String word, ?Symbol locale) -> String

This signature was generated using 1 sample from 1 application.

pluralize('ley', :es) # => "leyes"
pluralize('CamelOctopus') # => "CamelOctopi"
pluralize('words') # => "words"
pluralize('sheep') # => "sheep"
pluralize('octopus') # => "octopi"
pluralize('post') # => "posts"

this parameter is set to :en.
pluralized using rules defined for that language. By default,
If passed an optional +locale+ parameter, the word will be

Returns the plural form of the word in the string.
def pluralize(word, locale = :en)
  apply_inflections(word, inflections(locale).plurals, locale)
end

def safe_constantize(camel_cased_word)

safe_constantize('UnknownModule::Foo::Bar') # => nil
safe_constantize('UnknownModule') # => nil
safe_constantize('blargle') # => nil

part of it) is unknown.
+nil+ is returned when the name is not in CamelCase or the constant (or

end
safe_constantize('C') # => 'outside', same as ::C
C # => 'inside'
C = 'inside'
module M
C = 'outside'

account:
whether it starts with "::" or not. No lexical context is taken into
The name is assumed to be the one of a top-level constant, no matter

safe_constantize('Foo::Bar') # => Foo::Bar
safe_constantize('Module') # => Module

Tries to find a constant with the name specified in the argument string.
def safe_constantize(camel_cased_word)
  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 LoadError => e
  message = e.respond_to?(:original_message) ? e.original_message : e.message
  raise unless /Unable to autoload constant #{const_regexp(camel_cased_word)}/.match?(message)
end

def singularize(word, locale = :en)

singularize('leyes', :es) # => "ley"
singularize('CamelOctopi') # => "CamelOctopus"
singularize('word') # => "word"
singularize('sheep') # => "sheep"
singularize('octopi') # => "octopus"
singularize('posts') # => "post"

this parameter is set to :en.
singularized using rules defined for that language. By default,
If passed an optional +locale+ parameter, the word will be

string.
The reverse of #pluralize, returns the singular form of a word in a
def singularize(word, locale = :en)
  apply_inflections(word, inflections(locale).singulars, locale)
end

def tableize(class_name)

tableize('fancyCategory') # => "fancy_categories"
tableize('ham_and_egg') # => "ham_and_eggs"
tableize('RawScaledScorer') # => "raw_scaled_scorers"

This method uses the #pluralize method on the last word in the string.
Creates the name of a table like \Rails does for models to table names.
def tableize(class_name)
  pluralize(underscore(class_name))
end

def titleize(word, keep_id_suffix: false)

titleize('string_ending_with_id', keep_id_suffix: true) # => "String Ending With Id"
titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark"
titleize('TheManWithoutAPast') # => "The Man Without A Past"
titleize('x-men: the last stand') # => "X Men: The Last Stand"
titleize('man from the boondocks') # => "Man From The Boondocks"

By default, this parameter is false.
optional parameter +keep_id_suffix+ to true.
The trailing '_id','Id'.. can be kept and capitalized by setting the

output. It is not used in the \Rails internals.
create a nicer looking title. +titleize+ is meant for creating pretty
Capitalizes all the words and replaces some characters in the string to
def titleize(word, keep_id_suffix: false)
  humanize(underscore(word), keep_id_suffix: keep_id_suffix).gsub(/\b(?<!\w['’`()])[a-z]/) do |match|
    match.capitalize
  end
end

def transliterate(string, replacement = "?", locale: nil)

Other encodings will raise an ArgumentError.
Transliteration is restricted to UTF-8, US-ASCII, and GB18030 strings.

# => "Juergen"
transliterate('Jürgen', locale: :de)

# => "Jurgen"
transliterate('Jürgen', locale: :en)

Now you can have different transliterations for each locale:

})
}
rule: ->(string) { MyTransliterator.transliterate(string) }
transliterate: {
I18n.backend.store_translations(:de, i18n: {

complex requirements, a Proc:
maps characters to ASCII approximations as shown above, or, for more
The value for i18n.transliterate.rule can be a simple Hash that

})
}
}
'ö' => 'oe'
'ü' => 'ue',
rule: {
transliterate: {
I18n.backend.store_translations(:de, i18n: {
# Or set them using Ruby

ö: "oe"
ü: "ue"
rule:
transliterate:
i18n:
# Store the transliterations in locales/de.yml

them as the i18n.transliterate.rule i18n key:
In order to make your custom transliterations available, you must set

to ASCII.
and "ö" to "ue" and "oe", or to add support for transliterating Russian
locale. This can be useful, for example, to transliterate German's "ü"
This method is I18n aware, so you can set up custom approximations for a

e.g, "ø", "ñ", "é", "ß", etc.
Default approximations are provided for Western/Latin characters,

# => "AEroskobing"
transliterate('Ærøskøbing')

exists, a replacement character which defaults to "?".
Replaces non-ASCII characters with an ASCII approximation, or if none
def transliterate(string, replacement = "?", locale: nil)
  raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String)
  raise ArgumentError, "Cannot transliterate strings with #{string.encoding} encoding" unless ALLOWED_ENCODINGS_FOR_TRANSLITERATE.include?(string.encoding)
  return string.dup if string.ascii_only?
  string = string.dup if string.frozen?
  input_encoding = string.encoding
  # US-ASCII is a subset of UTF-8 so we'll force encoding as UTF-8 if
  # US-ASCII is given. This way we can let tidy_bytes handle the string
  # in the same way as we do for UTF-8
  string.force_encoding(Encoding::UTF_8) if string.encoding == Encoding::US_ASCII
  # GB18030 is Unicode compatible but is not a direct mapping so needs to be
  # transcoded. Using invalid/undef :replace will result in loss of data in
  # the event of invalid characters, but since tidy_bytes will replace
  # invalid/undef with a "?" we're safe to do the same beforehand
  string.encode!(Encoding::UTF_8, invalid: :replace, undef: :replace) if string.encoding == Encoding::GB18030
  transliterated = I18n.transliterate(
    ActiveSupport::Multibyte::Unicode.tidy_bytes(string).unicode_normalize(:nfc),
    replacement: replacement,
    locale: locale
  )
  # Restore the string encoding of the input if it was not UTF-8.
  # Apply invalid/undef :replace as tidy_bytes does
  transliterated.encode!(input_encoding, invalid: :replace, undef: :replace) if input_encoding != transliterated.encoding
  transliterated
end

def underscore(camel_cased_word)

camelize(underscore('SSLError')) # => "SslError"

#camelize, though there are cases where that does not hold:
As a rule of thumb you can think of +underscore+ as the inverse of

underscore('ActiveModel::Errors') # => "active_model/errors"
underscore('ActiveModel') # => "active_model"

Changes '::' to '/' to convert namespaces to paths.

Makes an underscored, lowercase form from the expression in the string.
def underscore(camel_cased_word)
  return camel_cased_word.to_s.dup unless /[A-Z-]|::/.match?(camel_cased_word)
  word = camel_cased_word.to_s.gsub("::", "/")
  word.gsub!(inflections.acronyms_underscore_regex) { "#{$1 && '_' }#{$2.downcase}" }
  word.gsub!(/(?<=[A-Z])(?=[A-Z][a-z])|(?<=[a-z\d])(?=[A-Z])/, "_")
  word.tr!("-", "_")
  word.downcase!
  word
end

def upcase_first(string)

upcase_first('') # => ""
upcase_first('w') # => "W"
upcase_first('what a Lovely Day') # => "What a Lovely Day"

Converts the first character in the string to uppercase.
def upcase_first(string)
  string.length > 0 ? string[0].upcase.concat(string[1..-1]) : +""
end