YAMLに正規表現を書く方法(Rails・!ruby/regexpタグ)
YAMLファイルに正規表現を書くには、 !ruby/regexp タグを使います。これを知るまで少し詰まったので、使い方と動作確認の手順をまとめます。
結論
regexp: !ruby/regexp /^[a-zA-Z\s]+$/
!ruby/regexp に続けて正規表現を書くだけです。Railsで読み込むとRubyのRegexpオブジェクトとして扱えます。
なぜYAMLに正規表現を書きたかったか
複数の国・企業向けに同じRailsアプリを展開していたことがあります。コードは共通のまま、環境ごとに読み込むYAMLファイルを切り替えることで細かな設定を調整する方式でした。文字列やフラグの値はすぐYAMLに書けましたが、URLバリデーション用の正規表現を環境ごとに変えたい場面で書き方が分からなくなりました。
YAMLは文字列・整数・真偽値・日付などを自動判別してくれますが、正規表現は対象外です。/^$/と書いてもただの文字列として読み込まれてしまいます。
# これはただの文字列になる
pattern: /^[a-zA-Z]+$/
Settings.pattern.class
# => String
バリデーションで使おうとして =~ にかけると、String と String の比較になるので期待どおりには動きません。
!ruby/regexpタグの仕組み
YAMLはデータのシリアライズ形式として、型情報を「タグ」で表現できます。タグは ! で始まり、デシリアライズ時にどのRubyクラスとして復元するかを指定します。
YAML仕様では !!str、!!int、!!bool などのコアタグが定義されていますが、Ruby固有のクラスには !ruby/ プレフィックスを持つタグが割り当てられています。
- !str 123 # 文字列
- !ruby/sym foo # Ruby の Symbol
- !ruby/regexp /^[a-z]+$/ # Ruby の Regexp
- !ruby/object:MyClass # 任意のRubyオブジェクト
正規表現の場合は!ruby/regexpに続けて/パターン/を書きます。フラグも使えます。
regexp_with_flag: !ruby/regexp /^[a-z]+$/i
i(大文字小文字無視)、m(複数行)、x(コメント許可)など、Rubyが対応するフラグをそのまま使えます。
Railsでの読み込み例
YAML.load_fileで直接読む場合
path = # YAMLファイルへのパス
config = YAML.load_file(path).symbolize_keys
config gemのSettings経由で読む場合
自分が使っていたプロジェクトでは、config gemを使ってSettingsオブジェクトに設定を集約していました。
GitHub - rubyconfig/config: Easiest way to add multi-environment yaml settings to Rails, Sinatra, Padrino and other Ruby projects.
Easiest way to add multi-environment yaml settings to Rails, Sinatra, Padrino and other Ruby projects. - rubyconfig/config
path = # YAMLファイルへのパス
Settings.add_source!(path)
Settings.reload!
YAMLファイルは次のように書きます。
reg:
sample_regexp: !ruby/regexp /^[a-zA-Z\s]+$/
動作確認
型指定あり・なしを並べて確認しました。
regexp: !ruby/regexp /^$/
string: /^$/
Railsコンソールで値とクラスを確認するとこうなります。
[1] pry(main)> Settings.regexp
=> /^$/
[2] pry(main)> Settings.regexp.class
=> Regexp
[3] pry(main)> Settings.string
=> "/^$/"
[4] pry(main)> Settings.string.class
=> String
!ruby/regexpをつけた方はRegexp、つけない方はStringとして読み込まれているのが分かります。正規表現マッチで使うにはRegexpでないといけないので、タグの指定が必要です。
実際にバリデーションで使うとこうなります。
# Regexp オブジェクトとして使える
pattern = Settings.reg.sample_regexp
"hello world" =~ pattern # => 0(マッチ)
"hello123" =~ pattern # => nil(マッチしない)
# String のままだと意図しない動作になる
pattern_str = Settings.string
"hello" =~ pattern_str # String#=~ は右辺が Regexp であることを期待する
YAML.load と YAML.safe_load の違いに注意
!ruby/regexp を含むYAMLを読み込む際に YAML.safe_load を使うとエラーになります。
content = "regexp: !ruby/regexp /^[a-z]+$/"
# これはエラーになる
YAML.safe_load(content)
# => Psych::DisallowedClass: Tried to load unspecified class: Regexp
# こちらは動く
YAML.load(content)
# => {"regexp"=>/^[a-z]+$/}
YAML.safe_load はデフォルトで String、Integer、Float、TrueClass、FalseClass、NilClass、Array、Hash のみを許可しており、任意のRubyオブジェクトの復元を防ぐセキュリティ上の制限があります。
ユーザーが入力したYAMLや信頼できないソースのYAMLを読む場合は safe_load が推奨されます。一方、自分たちで管理するコード内の設定ファイルを読む場合は、permitted_classes オプションで許可クラスを追加するか load を使うことになります。
# Ruby 3.1以降は permitted_classes で明示的に許可する書き方が推奨
YAML.safe_load(content, permitted_classes: [Regexp])
Ruby 3.1 / Psych 4.0 以降では YAML.load 自体もデフォルトで safe_load 相当の挙動になっています(任意クラスのデシリアライズを禁止)。permitted_classes キーワード引数はこのバージョンから安定して使える形式です。それ以前(Psych 3.x)では引数名が allowed_classes または位置引数で渡す形式のため、使用しているRubyのバージョンに合わせて公式ドキュメントを確認してください。
library psych (Ruby 4.0 リファレンスマニュアル)
yaml のバックエンドライブラリです。libyaml ベースで作成されており、YAML バージョン 1.1 を扱う事ができます。
環境ごとの設定ファイルで使うパターン
実際に複数環境で使った構成です。config gemの config/settings.yml(共通設定)と config/settings/production.yml(本番設定)で正規表現を環境ごとに上書きするパターンです。
# config/settings.yml(開発・デフォルト)
validation:
url_pattern: !ruby/regexp /^https?:\/\/.+/
# config/settings/production.yml(本番は厳しめのルール)
validation:
url_pattern: !ruby/regexp /^https:\/\/[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]+)+.*/
Railsコードでは Settings.validation.url_pattern だけ呼べばよく、環境切り替えは設定ファイル側で完結します。
参考

Rails 定数を管理するsettings.yml・環境ごとの定数管理の方法 - Qiita
はじめに 先日初めて、config下のsettings.ymlに定数を設定し、同時に環境ごとの定数も設定しました。 呼び出し方や使い方など、備忘録として纏めます。 なぜsettingsに定数を設定するか 管理を楽にして、変更があった際に対応しやすくするためでしょう。 例...

RailsでYAMLに正規表現をつっこむ - Qiita
!ruby/regexpをいれるといいというのが書いてあったので、メモ。 config/reg.yml reg: a: !ruby/regexp /^[a-zA-Z|\s]+/ s: !ruby/rege...
プログラマーのための YAML 入門 (初級編)
記事の更新をメールで受け取る
質問・リクエストを送る
記事についての質問や、取り上げてほしいテーマがあればお気軽にどうぞ。いただいた質問はブログ記事として回答し、Q&Aページで公開することがあります。