この記事は、
この記事は、筆者がRails6.0で lib ディレクトリ以下に置いた、カスタムバリデーションファイルを呼び出すために、四苦八苦した奮闘日記です。
「ええからはよ結論ださんかい!」と、筆者のように前戯もまともにできないようなせっかちなお方のために、先に結論をご用意しました。
ユーザーモデルからemailバリデーションを呼び出す前提となっています。
どうぞコピペください。
【先に結論】lib以下のカスタムバリデーションファイルを呼び出す方法
libのディレクトリ構造
lib
├── tasks
└── validator
└── email_validator.rb
config/application.rb
module Myapp8
class Application < Rails::Application
...
config.autoload_paths += %W(#{config.root}/lib/validator) # 追記
# config.autoload_paths << config.root.join("lib/validator") # もしくはこれでも良い
lib/validator/email_validator.rb
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
# emailの書式を検証するバリデーションサンプル
email = value.downcase
format = /\A\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*\z/
record.errors.add(attribute, :invalid) unless email =~ format
end
end
app/models/user.rb
class User < ApplicationRecord
validates email: true
end
以上がlib以下のカスタムバリデーションファイルを呼び出す方法でした。
それでは、良きバリデーションライフを!
何故、男はRails6.0でカスタムバリデーションが呼び出せなかったのか?
さて、ここからが本題です。
筆者が何故、カスタムバリデーションを呼び出せなかったのか?
それは、「Zeitwerk」を使わなければならんのだ。と言う思い込みが原因でした。
Zeitwerkとは?
Zeitwerk(ツァイトベルク)とは、Rails6.0から新たに組み込まれた「オートロードシステム」のことです。
- Zeitwerkを有効にする
config/application.rb
module Myapp8
class Application < Rails::Application
config.load_defaults "6.0" # 6.0を指定すると自動的に有効になる
- Zeitwerkが有効になっているか確認する
Rails コンソール
$ rails c
> Rails.autoloaders.zeitwerk_enabled?
=> true
- Zeitwerkを使わない場合
config/application.rb
module Myapp8
class Application < Rails::Application
config.load_defaults "6.0"
config.autoloader = :classic # クラシックモードを指定する
オートロードシステムって?
オートロードシステムとは、「命名規則に則ったファイルを自動でrequireしてくれる機能」のことです。
参考
requireって?
requireとは、「外部ファイルを読み込むRubyのメソッド」のことです。
Railsのapplication.rbには初めからrequireが多用されていますね。
これは、外部のRubyファイルを読み込んでいるのです。
config/application.rb
require "rails"
require "active_model/railtie"
require "active_job/railtie"
...
なるほど。
Rails6.0ではZeitwerkを使ってファイルを自動読み込みしていると言うことなんですね。
オートロードが見ているパスを確認する
じゃあ一体どこのファイルを自動読み込みしているのか?
それは、Railsコンソールから確認が取れます。
$ rails c
> puts ActiveSupport::Dependencies.autoload_paths
実行するとファイルパスが大量に出てきました。
/Users/andou/rails/myapp8/app/channels
/Users/andou/rails/myapp8/app/controllers
/Users/andou/rails/myapp8/app/controllers/concerns
...
ほうほう。
Zeitwerkはこのファイルパス配下にある、命名規則に則ったファイルを読み込んでいるんですね。
命名規則ってどんな規則?
命名規則は、クラスとモジュール名をディレクトリ構造によって命名する規則です。
クラス・モジュールの命名規則サンプル
lib/my_gem.rb -> MyGem
lib/my_gem/foo.rb -> MyGem::Foo
lib/my_gem/bar_baz.rb -> MyGem::BarBaz
lib/my_gem/woo/zoo.rb -> MyGem::Woo::Zoo
クラス名もしくはモジュール名にディレクトリ名をつけて命名することで、ディレクトリ構造が深くても自動でファイルが読み込まれるようになります。
デフォルトのオートロードのパス
デフォルトではautoload_paths
に lib のディレクトリパスが存在しません。
つまり、autoload_paths
にlibディレクトリのパスを追加することで、lib以下のファイルが読み込まれるようになります。
ここまでを3行まとめ
- Rails6.0ではZeitwerkを使ってファイルの自動読み込みを行なっている
- Zeitwerkは、autoload_pathsに指定されたパスのファイルを読み込んでいる
- autoload_pathsにlibのパスを追加すればlib以下のファイルが読み込まれる
【犯した罪1】Zeitwerkを使わなければならないと言う思い込み
ここまで調べたところで、もう頭の中は、Zeitwerk(ツァイトベルク)一色。
Zeitwerkを使ってlibディレクトリのパスを追加する方法を探せばいいのね、と言う思い込みが出来上がりました。
下のように、Zeitwerkをカスタマイズすることでlibディレクトリのパスを追加することができるのですが。。。
config/application.rb
loader = Zeitwerk::Loader.new
loader.push_dir(config.root.join("./lib/validator"))
loader.setup
やっとできた!と一息ついたとこで気づいたのです。
Railsガイドに以下のような文言があることに。
RailsアプリケーションのZeitwerkは手動で設定しないでください。
な、な、なんあななあああんだと!
じゃあやっぱりこっちなのか?
config/application.rb
config.autoload_paths += %W(#{config.root}/lib/validator)
いやいや、同じページにこんな文言があるんですけど。
自動読み込みパスの配列は、config/application.rbのconfig.autoload_pathsを書き換えることで拡張可能ではありますが、やめておきましょう。
ぬぐぐぐぐぐぐ。どっちじゃー!
libディレクトリはどうしたらえんじゃー!と久々に発狂しましたね。(脳内でね)
こんな時は自分で結論付けるしかない
結局どっちも動きます。そう、どっちも正解なのです。
こんな時は他の根拠付けをして自分で結論付けるしかありません。
そこで再度検索すると...。
見つけました。Rails6 Beta2時点の情報です。
オートロードパスの設定ポイントとして、引き続きconfig.autoload_pathsが残されます。
アプリの初期化中に手動でActiveSupport::Dependencies.autoload_pathsに設定することもできます。
ここから、正解よりの正解はconfig.autoload_paths
を使う方だなと結論付けました。
まとめ
Rails6.0でオートロードパスを追加するにはconfig.autoload_paths
を使おう!
【犯した罪2】カスタムバリデーションが呼び出せない
筆者は当初、
- オートロードパスにlibのパスを追加し、
- その配下のファイル名を命名規則に則れば、
- lib/validatorディレクトリのファイルは読み込まれるから
- オートロードパスにlib/validatorとわざわざ指定しなくても良いんだ!
と考えていました。
libのディレクトリ構造
lib
├── tasks
└── validator
└── email_validator.rb
config/application.rb(libのパスを追加)
config.autoload_paths += %W(#{config.root}/lib)
lib/validator/email_validator.rb(命名規則の則ったクラス名)
class Validator::EmailValidator < ActiveModel::EachValidator
end
app/models/user.rb
class User < ApplicationRecord
validates email: true
end
でも、これじゃ呼び出せませんでした。とほほ。。。
何故か?
カスタムバリデーションのクラス名に命名のルールがあったためです。
クラス名の規約
カスタムバリデーションのクラス名にはルールがあります。
それは、「検証名Validator」の形式でクラス名を宣言しなければならないことです。
そして、モデルから呼び出すときは、クラス名から「validator」を取り除いた上で、アンダースコア形式に変換して呼び出します。
バリデーションのクラス名 | モデル側の呼び出し |
---|---|
EmailValidator | |
UserEmailValidator | user_email |
上記の理由で「Validator::EmailValidator」と言うクラス名は呼び出すことができなかったのです。
まとめ
lib/validatorディレクトリにカスタムバリデーションファイルを作成する場合。
カスタムバリデーションのクラス名は「検証名Validator」にし、オートロードパスにはlib/validatorまで指定しよう。
lib/validator/email_validator.rb
class EmailValidator < ActiveModel::EachValidator
end
config/application.rb
config.autoload_paths += %W(#{config.root}/lib/validator)
最後に
このようにすごく遠回りした結果、一番上の結論コードにたどり着きました。
実はあのコードは、Rails5.2の設定と全く変わりありません。
前のプロジェクトからコピペしたら1分で済むことを、丸2日格闘していました。
まあこうやって腹の底まで理解できたから良しとしよう!と思う今日この頃です。
以上、Rails6.0で lib ディレクトリ以下に置いたカスタムバリデーションファイルを呼び出すために、四苦八苦した筆者の奮闘日記でした。
(おしまい)