跳至内容 跳至搜索

Active Support Inflector

Inflector 将单词从单数转换为复数,将类名转换为表名,将模块化类名转换为无模块化类名,将类名转换为外键。复数化、单数化和不可数单词的默认词形变化保存在 inflections.rb 中。

Rails 核心团队已声明,为了避免破坏可能依赖于错误词形变化的旧应用程序,将不接受词形变化库的补丁。如果您发现不正确的词形变化并且需要将其用于您的应用程序,或希望为除英语以外的其他语言定义规则,请自行更正或添加它们(如下所述)。

命名空间
方法
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"

根据经验,您可以将 camelize 视为 underscore 的逆运算,尽管在某些情况下不成立

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。(要转换为实际类,请将 classifyconstantize 一起使用。)

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

当名称不是骆驼拼写法或常量未知时,会引发 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 的单例实例,以便您可以指定其他屈折规则。如果传递了一个可选的语言环境,则可以指定其他语言的规则。如果未指定,则默认为:en。仅提供英语规则。

ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.uncountable 'rails'
end
# File activesupport/lib/active_support/inflector/inflections.rb, line 265
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?
    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

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”等内容。默认情况下,此参数为 false。

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,因此可以为某个语言环境设置自定义近似值。例如,这对于将德语的“ü”和“ö”音译为“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) }
  }
})

现在可以为每个语言环境设置不同的音译

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"

根据经验,你可以将underscore视为camelize的逆运算,尽管在某些情况下不成立

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