跳至内容 跳至搜索

Active Support Inflector

Inflector 负责将单词从单数转换为复数,类名转换为表名,模块化类名转换为非模块化类名,以及类名转换为外键。复数化、单数化和不可数单词的默认规则保存在 inflections.rb 文件中。

Rails 核心团队已声明,为了避免破坏可能依赖于错误规则的旧版应用程序,将不接受对 inflections 库的补丁。如果您发现一个错误的转写规则,并且您的应用程序需要它,或者您想为除英语以外的语言定义规则,请自行更正或添加(如下文所述)。

命名空间
方法
C
D
F
H
I
O
P
S
T
U

常量

ALLOWED_ENCODINGS_FOR_TRANSLITERATE = [Encoding::UTF_8, Encoding::US_ASCII, Encoding::GB18030].freeze
 

实例公共方法

camelize(term, uppercase_first_letter = true)

将字符串转换为 UpperCamelCase。如果 uppercase_first_letter 参数设置为 false,则生成 lowerCamelCase。

还会将 ‘/’ 转换为 ‘::’,这对于将路径转换为命名空间很有用。

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

经验法则上,您可以认为 camelizeunderscore 的反向操作,尽管在某些情况下这并不成立。

camelize(underscore('SSLError'))        # => "SslError"
# File activesupport/lib/active_support/inflector/methods.rb, line 70
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

classify(table_name)

根据 Rails 将表名转换为模型名的方式,从复数表名创建类名。请注意,这会返回一个字符串而不是一个 Class。(要转换为实际的类,请在 classify 后面加上 constantize。)

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

单数名称处理不正确。

classify('calculus')     # => "Calculu"
# File activesupport/lib/active_support/inflector/methods.rb, line 218
def classify(table_name)
  # strip out any leading schema name
  camelize(singularize(table_name.to_s.sub(/.*\./, "")))
end

constantize(camel_cased_word)

尝试查找参数字符串中指定的常量。

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

假定该名称是顶级常量的名称,无论它是否以“::”开头。不考虑词法上下文。

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

当名称不是 CamelCase 或常量未知时,会引发 NameError

# File activesupport/lib/active_support/inflector/methods.rb, line 289
def constantize(camel_cased_word)
  Object.const_get(camel_cased_word)
end

dasherize(underscored_word)

将字符串中的下划线替换为破折号。

dasherize('puni_puni') # => "puni-puni"
# File activesupport/lib/active_support/inflector/methods.rb, line 226
def dasherize(underscored_word)
  underscored_word.tr("_", "-")
end

deconstantize(path)

删除字符串中常量表达式的最右边部分。

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

另请参阅 demodulize

# File activesupport/lib/active_support/inflector/methods.rb, line 256
def deconstantize(path)
  path.to_s[0, path.rindex("::") || 0] # implementation based on the one in facets' Module#spacename
end

demodulize(path)

删除字符串中模块部分。

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

另请参阅 deconstantize

# File activesupport/lib/active_support/inflector/methods.rb, line 238
def demodulize(path)
  path = path.to_s
  if i = path.rindex("::")
    path[(i + 2), path.length]
  else
    path
  end
end

downcase_first(string)

将字符串的第一个字符转换为小写。

downcase_first('If they enjoyed The Matrix') # => "if they enjoyed The Matrix"
downcase_first('I')                          # => "i"
downcase_first('')                           # => ""
# File activesupport/lib/active_support/inflector/methods.rb, line 175
def downcase_first(string)
  string.length > 0 ? string[0].downcase.concat(string[1..-1]) : +""
end

foreign_key(class_name, separate_class_name_and_id_with_underscore = true)

从类名创建外键名。separate_class_name_and_id_with_underscore 指定该方法是否应在名称和“id”之间添加下划线。

foreign_key('Message')        # => "message_id"
foreign_key('Message', false) # => "messageid"
foreign_key('Admin::Post')    # => "post_id"
# File activesupport/lib/active_support/inflector/methods.rb, line 267
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

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

调整属性名,使其更易于用户显示。

具体执行以下转换:

  • 应用人类可读的转写规则到参数。

  • 删除开头的下划线(如果有)。

  • 移除结尾的 “_id” 后缀(如果存在)。

  • 将下划线替换为空格(如果存在)。

  • 将所有单词转换为小写,但缩写词除外。

  • 首字母大写。

通过将 :capitalize 选项设置为 false(默认为 true)可以关闭首字母大写。

通过将可选参数 keep_id_suffix 设置为 true(默认为 false)可以保留并大写结尾的 ‘_id’。

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

如果“SSL”被定义为一个缩写词

humanize('ssl_error') # => "SSL error"
# File activesupport/lib/active_support/inflector/methods.rb, line 135
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

inflections(locale = :en)

会屈服一个单例的 Inflector::Inflections 实例,以便您可以指定额外的转写规则。如果传入可选的 locale 参数,则可以指定其他语言的规则。如果未指定,则默认为 :en。只提供英语的规则。

ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.uncountable 'rails'
end
# File activesupport/lib/active_support/inflector/inflections.rb, line 281
def inflections(locale = :en)
  if block_given?
    yield Inflections.instance(locale)
  else
    Inflections.instance_or_fallback(locale)
  end
end

ordinal(number)

返回应添加到数字后面的后缀,以表示其在有序序列中的位置,例如 1st、2nd、3rd、4th。

ordinal(1)     # => "st"
ordinal(2)     # => "nd"
ordinal(1002)  # => "nd"
ordinal(1003)  # => "rd"
ordinal(-11)   # => "th"
ordinal(-1021) # => "st"
# File activesupport/lib/active_support/inflector/methods.rb, line 334
def ordinal(number)
  I18n.translate("number.nth.ordinals", number: number)
end

ordinalize(number)

将数字转换为序数字符串,用于表示在有序序列中的位置,例如 1st、2nd、3rd、4th。

ordinalize(1)     # => "1st"
ordinalize(2)     # => "2nd"
ordinalize(1002)  # => "1002nd"
ordinalize(1003)  # => "1003rd"
ordinalize(-11)   # => "-11th"
ordinalize(-1021) # => "-1021st"
# File activesupport/lib/active_support/inflector/methods.rb, line 347
def ordinalize(number)
  I18n.translate("number.nth.ordinalized", number: number)
end

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

替换字符串中的特殊字符,以便它可以用作“美化”URL 的一部分。

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

要使用自定义分隔符,请覆盖 separator 参数。

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

要保留字符串中字符的大小写,请使用 preserve_case 参数。

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

它会保留破折号和下划线,除非它们被用作分隔符。

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

如果指定了可选参数 locale,则该单词将根据该语言的规则进行参数化。默认情况下,此参数设置为 nil,它将使用配置的 I18n.locale

# File activesupport/lib/active_support/inflector/transliterate.rb, line 123
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?
    # No more than one of the separator in a row.
    if separator.length == 1
      parameterized_string.squeeze!(separator)
    else
      re_sep = Regexp.escape(separator)
      parameterized_string.gsub!(/#{re_sep}{2,}/, separator)
    end
    # Remove leading/trailing separator.
    parameterized_string.delete_prefix!(separator)
    parameterized_string.delete_suffix!(separator)
  end

  parameterized_string.downcase! unless preserve_case
  parameterized_string
end

pluralize(word, locale = :en)

返回字符串中单词的复数形式。

如果传入可选的 locale 参数,则将使用为该语言定义的规则来复数化单词。默认情况下,此参数设置为 :en

pluralize('post')             # => "posts"
pluralize('octopus')          # => "octopi"
pluralize('sheep')            # => "sheep"
pluralize('words')            # => "words"
pluralize('CamelOctopus')     # => "CamelOctopi"
pluralize('ley', :es)         # => "leyes"
# File activesupport/lib/active_support/inflector/methods.rb, line 33
def pluralize(word, locale = :en)
  apply_inflections(word, inflections(locale).plurals, locale)
end

safe_constantize(camel_cased_word)

尝试查找参数字符串中指定的常量。

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

假定该名称是顶级常量的名称,无论它是否以“::”开头。不考虑词法上下文。

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

当名称不是 CamelCase 或常量(或其一部分)未知时,返回 nil

safe_constantize('blargle')                  # => nil
safe_constantize('UnknownModule')            # => nil
safe_constantize('UnknownModule::Foo::Bar')  # => nil
# File activesupport/lib/active_support/inflector/methods.rb, line 315
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

singularize(word, locale = :en)

pluralize 相反,返回字符串中单词的单数形式。

如果传入可选的 locale 参数,则将使用为该语言定义的规则来单数化单词。默认情况下,此参数设置为 :en

singularize('posts')            # => "post"
singularize('octopi')           # => "octopus"
singularize('sheep')            # => "sheep"
singularize('word')             # => "word"
singularize('CamelOctopi')      # => "CamelOctopus"
singularize('leyes', :es)       # => "ley"
# File activesupport/lib/active_support/inflector/methods.rb, line 50
def singularize(word, locale = :en)
  apply_inflections(word, inflections(locale).singulars, locale)
end

tableize(class_name)

创建表名,就像 Rails 将模型转换为表名一样。此方法使用字符串中最后一个单词的 pluralize 方法。

tableize('RawScaledScorer') # => "raw_scaled_scorers"
tableize('ham_and_egg')     # => "ham_and_eggs"
tableize('fancyCategory')   # => "fancy_categories"
# File activesupport/lib/active_support/inflector/methods.rb, line 204
def tableize(class_name)
  pluralize(underscore(class_name))
end

titleize(word, keep_id_suffix: false)

将字符串中的所有单词首字母大写,并替换某些字符,以创建更美观的标题。titleize 用于创建漂亮的输出。它不用于 Rails 内部。

通过将可选参数 keep_id_suffix 设置为 true,可以保留并大写结尾的 ‘_id’、‘Id’ 等。

titleize('man from the boondocks')                       # => "Man From The Boondocks"
titleize('x-men: the last stand')                        # => "X Men: The Last Stand"
titleize('TheManWithoutAPast')                           # => "The Man Without A Past"
titleize('raiders_of_the_lost_ark')                      # => "Raiders Of The Lost Ark"
titleize('string_ending_with_id', keep_id_suffix: true)  # => "String Ending With Id"
# File activesupport/lib/active_support/inflector/methods.rb, line 192
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

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

将非 ASCII 字符替换为 ASCII 近似字符,如果不存在,则替换为默认值为“?”的替换字符。

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

为西方/拉丁字符(例如,“ø”、“ñ”、“é”、“ß”等)提供了默认的近似。

此方法支持 I18n,因此您可以为特定 locale 设置自定义近似。例如,这对于将德语的“ü”和“ö”转写为“ue”和“oe”,或者为俄语到 ASCII 的转写添加支持非常有用。

为了使您的自定义转写可用,您必须将其设置为 i18n.transliterate.rule i18n 键。

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

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

i18n.transliterate.rule 的值可以是简单的 Hash,它将字符映射到 ASCII 近似值(如上所示),或者,对于更复杂的要求,可以是 Proc。

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

现在您可以为不同的 locale 设置不同的转写规则。

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

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

转写限制为 UTF-8、US-ASCII 和 GB18030 字符串。其他编码将引发 ArgumentError。

# File activesupport/lib/active_support/inflector/transliterate.rb, line 64
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

underscore(camel_cased_word)

将字符串中的表达式转换为下划线分隔的、小写的形式。

将“::”更改为“/”,以将命名空间转换为路径。

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

经验法则上,您可以认为 underscorecamelize 的反向操作,尽管在某些情况下这并不成立。

camelize(underscore('SSLError'))  # => "SslError"
# File activesupport/lib/active_support/inflector/methods.rb, line 99
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

upcase_first(string)

将字符串的第一个字符转换为大写。

upcase_first('what a Lovely Day') # => "What a Lovely Day"
upcase_first('w')                 # => "W"
upcase_first('')                  # => ""
# File activesupport/lib/active_support/inflector/methods.rb, line 166
def upcase_first(string)
  string.length > 0 ? string[0].upcase.concat(string[1..-1]) : +""
end