こんにちは、hachi8833です。
Active Support探訪シリーズは、前回のString#pluralizeで扱ったActiveSupport::Inflector
の便利メソッド群を概観します。
今回のメソッド
今回は、初めてcore extension以外のメソッドにお邪魔することになります。
- Rails API: ActiveSupport::Inflectorのメソッド
- ディレクトリ配置: https://github.com/rails/rails/blob/7-0-stable/activesupport/lib/active_support/inflector/methods.rb
条件
- Railsバージョン: 7-0-stable(更新時点では7.0.4.3)
- Rubyバージョン: 3.2.2
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#underscore
とString#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#casecmp
はA-Z/a-z
のみが対象で、まだUnicode仕様の変換に対応していません。
» "Å".casecmp "Å".swapcase
#> -1
🔗 追記(2018/10/22)
なお、Rubyの#10085ではen
やtr
などで言語を指定する形になっていましたが、現在はオプションなし以外は:ascii
、:turkic
、:lithuanian
、:fold
のみとなっています。また、置き換え方法はエンコーディングにも依存します。
参考: String#downcase
(Ruby 3.2 リファレンスマニュアル)
対応できる言語を順次シンボルで渡せるようにするということだと思います。大文字小文字の変換はマルチリンガルになると単純にはいかないので苦労が偲ばれます。
更新情報