Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails: ActiveSupport::Inflectorの便利な活用形メソッド群

更新情報

  • 2017/01/11: 初版公開
  • 2023/04/24: Rails 7版に更新

こんにちは、hachi8833です。

Active Support探訪シリーズは、前回のString#pluralizeで扱ったActiveSupport::Inflectorの便利メソッド群を概観します。

今回のメソッド

今回は、初めてcore extension以外のメソッドにお邪魔することになります。

条件

ActiveSupport::Inflector

🔗 Rails 7.0のActiveSupport::Inflectorメソッド一覧

Rails 5.0のActiveSupport::Inflectorには以下の活用形関連メソッドがあります。ActiveSupport::Inflectorのメソッド群はやや雑然としているので、対になっているメソッドなどをグループ化してみました。

多くはStringクラスですが、数値に関連するメソッドもあります。

最初に、各ケースについて以下に簡単にまとめておきます。

スネークケース
active_record」のように小文字のみの単語をアンダースコアで結合(Railsではメソッド名などで使用)
キャメルケース(upper camel case)
ActiveRecord」のように大文字で始まる単語を結合(Railsではクラス名などで使用) -- PascalCaseと呼ばれることもあります
キャメルケース(lower camel case)
activeRecord」のように先頭のみ小文字、残りは大文字で始まる単語を結合

🔗 英単語の単数形⇔複数形変換

#pluralize
(Stringクラス) 英語の単数形を複数形に変換します
#singularize
(Stringクラス) 英語の複数形を単数形に変換します

活用形はすべてを網羅しているわけではないのでご注意ください。

#pluralize
'post'.pluralize             # => "posts"
'octopus'.pluralize          # => "octopi"
'sheep'.pluralize            # => "sheep"
'words'.pluralize            # => "words"
'the blue mailman'.pluralize # => "the blue mailmen"
'CamelOctopus'.pluralize     # => "CamelOctopi"
'apple'.pluralize(1)         # => "apple"
'apple'.pluralize(2)         # => "apples"
'ley'.pluralize(:es)         # => "leyes"
'ley'.pluralize(1, :es)      # => "ley"

# singularize
'posts'.singularize            # => "post"
'octopi'.singularize           # => "octopus"
'sheep'.singularize            # => "sheep"
'word'.singularize             # => "word"
'the blue mailmen'.singularize # => "the blue mailman"
'CamelOctopi'.singularize      # => "CamelOctopus"
'leyes'.singularize(:es)       # => "ley"

🔗 英語の序数

#ordinal
(Integerクラス)数字に対応する英語の序数(ordinal numbers)の接尾語を返す
#ordinalize
(Integerクラス)数字に英語の序数(ordinal numbers)を追加して返す
# ordinal
1.ordinal     # => "st"
2.ordinal     # => "nd"
1002.ordinal  # => "nd"
1003.ordinal  # => "rd"
-11.ordinal   # => "th"
-1001.ordinal # => "st"

# ordinalize
1.ordinalize     # => "1st"
2.ordinalize     # => "2nd"
1002.ordinalize  # => "1002nd"
1003.ordinalize  # => "1003rd"
-11.ordinalize   # => "-11th"
-1001.ordinalize # => "-1001st"

これらのみIntegerクラスを拡張しています。

🔗 アルファベットの大文字小文字変換

#upcase_first
(Stringクラス) 最初の文字のみを大文字に変換
#humanize
(Stringクラス) アンダースコアをスペースにして先頭を大文字にするなど、英文「らしく」整形する(末尾の_idは除去する)
#titleize
(Stringクラス) 小文字の英語フレーズを英語のタイトル「らしく」整形する(Rails内部では使っていない)
# upcase_first
'what a Lovely Day'.upcase_first # => "What a Lovely Day"
'w'.upcase_first                 # => "W"
''.upcase_first                  # => ""

# humanize
'employee_salary'.humanize              # => "Employee salary"
'author_id'.humanize                    # => "Author"
'author_id'.humanize(capitalize: false) # => "author"
'_id'.humanize                          # => "Id"

# titleiize
'man from the boondocks'.titleize # => "Man From The Boondocks"
'x-men: the last stand'.titleize  # => "X Men: The Last Stand"

#titleize内部で呼ばれている#humanizeを見てみると、Rails 5.0のときのオプション引数options = {}Rails 7.0ではcapitalize: true, keep_id_suffix: falseとキーワード引数に変わっています。

追伸: titleizeは英語のタイトルスタイルそのものではない

現実の英文におけるタイトルのスタイルは、「inやtoなどの前置詞は大文字にしない」などルールが複雑で、かつ出版社や新聞社によって異なるので機械的な処理が困難です。

#titleizeに限らず、こうしたメソッドはコーディング支援のためのものであり、実用的な英文に変換するものではないと考えるほうがよいでしょう。

🔗 記号の変換

#dasherize
(Stringクラス) アンダースコア_をダッシュ-に変換
'puni_puni'.dasherize # => "puni-puni"

なお、ここでいうダッシュはhyphen-dash(U+002D)(要するにマイナス/ハイフン/ダッシュのどれにも使われている例のASCII文字)のことです。

🔗 定数名・モジュール名の変換

#constantize
(Stringクラス) 指定の文字列を定数に変換する(元がキャメルケースかつ該当の定数が存在する場合以外はエラー)
#safe_constantize
(Stringクラス) constantizeと同様だが、無効な文字列の場合にはnilを返す
#deconstantize
(Stringクラス) 定数名(文字列)の最も右の要素を除去する
#demodulize
(Stringクラス) モジュール名の最も右の要素を返す
# constantize
'Module'.constantize  # => Module
'Class'.constantize   # => Class
'blargle'.constantize # => NameError: wrong constant name blargle

# safe_constantize
'Module'.safe_constantize  # => Module
'Class'.safe_constantize   # => Class
'blargle'.safe_constantize # => nil

# deconstantize
'Net::HTTP'.deconstantize   # => "Net"
'::Net::HTTP'.deconstantize # => "::Net"
'String'.deconstantize      # => ""
'::String'.deconstantize    # => ""
''.deconstantize            # => ""

# demodulize
'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
'Inflections'.demodulize                                       # => "Inflections"
'::Inflections'.demodulize                                     # => "Inflections"
''.demodulize  

#constantizeすると文字列ではなく定数オブジェクトになるので、#deconstantizeで逆変換できません。

#deconstantize#constantizeの逆操作ではないのがなかなか紛らわしいですね。

さらに、#demodulizeはモジュール名ではなく名前空間の方を削除しています。

🔗 キャメルケース⇔スネークケース変換

#camelize
(Stringクラス) キャメルケースに変換(デフォルトは:lower)、かつスラッシュ/をコロン2つ::に置き換える
#underscore
(Stringクラス) #camelizeの逆操作
# camelize
'active_record'.camelize                # => "ActiveRecord"
'active_record'.camelize(:lower)        # => "activeRecord"
'active_record/errors'.camelize         # => "ActiveRecord::Errors"
'active_record/errors'.camelize(:lower) # => "activeRecord::Errors"

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

#camelize#underscoreが互いに逆操作というのが名前からはちょっとわかりにくいですね。

🔗 クラス名/モジュール名/テーブル名の変換

テーブル名はデータベースのテーブル名を指します。

classify
(Stringクラス) テーブル名(小文字、ハイフン)をクラス名に変換する
tableize
(Stringクラス) キャメルケースのクラス名(や単数形のスネークケース)をテーブル名に変換する
# classify
'ham_and_eggs'.classify # => "HamAndEgg"
'posts'.classify        # => "Post"

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

#tableize#classifyの逆操作にもなっています。いかにも造語っぽいメソッド名はスペルを間違えそうで冷や冷やします。

🔗 パラメータ化

#parameterize
(Stringクラス) 文字列をURLらしく変換する(記号の除去やスペース->ハイフン置き換え)

ただし全角文字は記号と同様に除去されてしまうので、全角文字を残したい場合はBase64#encode64などで別途変換する必要があります。

🔗 活用形のカスタマイズ

Inflector.inflections
(ActiveSupport::Inflector) 登録されていない活用形を追加する

rails generateでコントローラやモデルなどを生成するときの単語が惜しくも活用形に対応していない場合などに、config/initializers/inflections.rbに以下のように単語の活用を追加します。カスタマイズはロケールごとに行えます。

ActiveSupport::Inflector.inflections(:es) do |inflect|
  inflect.irregular 'ley', 'leyes'
end

なお、このメソッドはActiveSupport::Inflectorクラスのシングルインスタンスを生成します。

# File activesupport/lib/active_support/inflector/inflections.rb, line 234
def inflections(locale = :en)
  if block_given?
    yield Inflections.instance(locale)
  else
    Inflections.instance(locale)
  end
end

🔗 近い英文字への変換

Inflector.transliterate
(ActiveSupport::Inflector)ø、ñ、é、ß、лなどの英語にないアルファベットを近い英語に変換する

これはStringクラスではなく、ActiveSupport::Inflectorモジュールです。

ActiveSupport::Inflector.transliterate('Ærøskøbing') # => "AEroskobing"

この変換は以下のようにロケールごとにカスタマイズもできます。

# locales/de.ymlで指定する場合
i18n:
  transliterate:
    rule:
      ü: "ue"
      ö: "oe"
# Rubyコードで設定する場合
I18n.backend.store_translations(:de, i18n: {
  transliterate: {
    rule: {
      'ü' => 'ue',
      'ö' => 'oe'
    }
  }
})

🔗 外部キー名への変換

#foreign_key
(Stringクラス) クラス名を外部キー名に変換する(小文字化、アンダースコア除去、"_id" の追加)
'Message'.foreign_key        # => "message_id"
'Message'.foreign_key(false) # => "messageid"
'Admin::Post'.foreign_key    # => "post_id"

String#foreign_keyのソースをちょっとのぞいてみると、先に紹介したString#underscoreString#demodulizeを組み合わせて実現しています。

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

🔗 参考: Rubyの活用形関連メソッド

以下はRailsではなくRubyのメソッドですが、上のActiveSupport::Inflectorの内部で呼ばれているなど、Railsとも密接に関連しています。各メソッドには!が末尾につく破壊的メソッドもあります。

これらのメソッドはRuby 2.4で大きく拡張され、トルコ語など多くのアルファベット系言語でUnicode仕様に準じた変換に対応したのは記憶に新しいところです。

なお、大文字小文字を無視して比較するString#casecmpA-Z/a-zのみが対象で、まだUnicode仕様の変換に対応していません。

»  "Å".casecmp "Å".swapcase
#> -1

🔗 追記(2018/10/22)

なお、Rubyの#10085ではentrなどで言語を指定する形になっていましたが、現在はオプションなし以外は:ascii:turkic:lithuanian:foldのみとなっています。また、置き換え方法はエンコーディングにも依存します。

参考: String#downcase (Ruby 3.2 リファレンスマニュアル)

対応できる言語を順次シンボルで渡せるようにするということだと思います。大文字小文字の変換はマルチリンガルになると単純にはいかないので苦労が偲ばれます。

関連記事

Rails: ビューのHTMLエスケープは#link_toなどのヘルパーメソッドで解除されることがある


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。