LoginSignup
29
15

More than 3 years have passed since last update.

Rails 6.0.0.rc1でER図を出力

Last updated at Posted at 2019-06-16

概要

Rails 6でER図を出力したかったのですが、Rails 6ではリリースノートにありますようにオートローダーはデフォルトでZeitwerkを用いています。それゆえこれまでと同様にrails-erdのREADMEに従って設定するだけではうまくいかなかったので、その解決方法の備忘録です。

問題

ER図を作成しようとするとモデルがロードできずエラーになります。

$ bin/rake erd filetype=png
Loading application environment...
Loading code in search of Active Record models...
Generating Entity-Relationship Diagram for 0 models...
rake aborted!
No entities found; create your models first!

原因

原因究明を開始したは良かったのですが、最初はそもそもオートローダーが何をどうやってるのかさっぱりでしたし、なんならあまり意識もしていませんでした(恥ずかしながらこれまで何も困ったことがなかったので)。正直今もあまり分かってはいないのですが、Rails 5と比較してどこに違いがあるのかを探っていきました。

Rails.application.eager_load!

rails-erdのソースコードを読んでいくとすぐに挙動が違うところを見つけました。それがRails.application.eager_load!です。このメソッドをコールするとオートローダーの設定に関係なく一括でロードしてくれるみたいです。またここで言う設定とは config/environments/xxx.rbconfig.eager_loadのことで、こちらに関してはここの説明がとても分かりやすいです。

rails-erd-1.6.0/lib/rails_erd/tasks.rake
task :load_models do
  say "Loading application environment..."
  Rake::Task[:environment].invoke

  say "Loading code in search of Active Record models..."
  begin
    Rails.application.eager_load!
    ...
end

ではこの Rails.application.eager_load! にて何が違うのかと言いますと、Rails 5ではロードするパスの配列を返していたのですが、Rails 6では返り値がnilになっていました。このメソッドの返り値を使っているようなコードは見当たらなかったので、返り値自体が何かに使われるわけではなさそうですが、何かおかしそうだと思って調べてみました。

railties-6.0.0.rc1/lib/rails/engine.rb
def eager_load!
  # Already done by Zeitwerk::Loader.eager_load_all in the finisher.
  return if Rails.autoloaders.zeitwerk_enabled?

  config.eager_load_paths.each do |load_path|
    # Starts after load_path plus a slash, ends before ".rb".
    relname_range = (load_path.to_s.length + 1)...-3
    Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
      require_dependency file[relname_range]
    end
  end
end

(使いこなせていない)pryeager_load!のソースコードを読むとZeitwerkを使っているとこのRails.application.eager_load!は何もせず終わってしまうことが分かりました。Rails 6としてはこの挙動は正しいのかもしれませんが、このままではRails.application.eager_load!を使って読み込むことを前提としたrails-erdとしては困りそうです。

解決策その1 従来のオートローダーを使う

最も簡単な方法は従来のオートローダーを使うことだと思います。従来のオートローダーを使うこと自体はとても簡単で、以下のように config/application.rbconfig.autoloader = :classic を設定するだけでいけます。

config/application.rb
module RailsTest
  class Application < Rails::Application
    config.load_defaults 6.0
    config.autoloader = :classic
  end
end

しかし、このままではせっかく今回導入されたZeitwerkを使えないので少しもったいない感じがします。

解決策その2 なんとかしてZeitwerk::Loader.eager_load_allを使う

さきほどの lib/rails/engine.rb にて「Already done by Zeitwerk::Loader.eager_load_all」と言ってるのですからこいつをコールしてあげれば良いのでは?という発想です。以下が代わりに作ったRake Taskです。

lib/tasks/test.rake
namespace :test do
  task erd: :environment do
    Zeitwerk::Loader.eager_load_all
    Rake::Task['erd'].invoke
  end
end

実際に使ってみます。

$ bin/rake test:erd
Loading application environment...
Loading code in search of Active Record models...
Generating Entity-Relationship Diagram for 8 models...
Warning: Ignoring invalid model ActionText::RichText (table action_text_rich_texts does not exist)
Warning: Ignoring invalid model ActionMailbox::InboundEmail (table action_mailbox_inbound_emails does not exist)
Done! Saved diagram to erd.png.

ER図が作成されました。しかし、まだこのままではbin/rails g erd:installで作成したauto_generate_diagram.rakeがロードするマイグレーションのフックには影響を与えません。なのでrails-erdがマイグレーション時にコールするメソッドをモンキーパッチで書き換えてあげる必要があります。幸いなことにマイグレーション時にコールされる ERDGraph::Migration.update_modelerdタスクをinvokeするだけの簡単なメソッドなのでこれを変更しました。

lib/tasks/auto_generate_diagram.rake
if Rails.env.development?
  RailsERD.load_tasks

  module ERDGraph
    class Migration
      def self.update_model
        Zeitwerk::Loader.eager_load_all
        Rake::Task['erd'].invoke
      end
    end
  end
end
$ bin/rails db:migrate
Loading application environment...
Loading code in search of Active Record models...
Generating Entity-Relationship Diagram for 8 models...
Warning: Ignoring invalid model ActionText::RichText (table action_text_rich_texts does not exist)
Warning: Ignoring invalid model ActionMailbox::InboundEmail (table action_mailbox_inbound_emails does not exist)
Done! Saved diagram to erd.png.

これでマイグレーションの度に勝手にER図を更新してくれると思います。仮にrails-erdがRails 6をサポートした場合においてもモジュール配下を削除するだけで済むのでそんなに悪くない手法だと思います。

考察

従来のオートローダーを使うのはやはりもったいない感じがするので、今回は「なんとかしてZeitwerk::Loader.eager_load_allを使う」であげたモンキーパッチをあてる手法を採用しました。ライブラリが提供するクラスをモンキーパッチで手を加えるのは可能な限り回避すべきかもしれません。ですが、ERDGraph::Migrationを他で使うことは基本的にないと思ったのと、そもそも影響範囲が比較的狭いと思ったからです(たしかRake Taskの実行時しかロードされなかった気がします)。

感想

Rails.application.eager_load!rails-erd以外にも依存しているライブラリはあると思うのですがどうなんでしょう?今回の調査に間違いがなければRails側かGem側のどちらかがなんとかしないといけないと思います。ただGem側を変更する場合はそれほど難しくはないような気がします。

しかしまぁ、こういうライブラリの実装を読んでいくと、知らない書き方や思いもよらない手法など新しい発見があって面白いですね〜。

29
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
15