Help us understand the problem. What is going on with this article?

【Guard】自動生成されるGuardfileで監視・実行されるファイルは結局どこなのか、ソースコードから読み解いてみた

はじめに

RailsアプリでRSpecを自動で走らせるためにGuardを導入したのですが、その設定ファイルであるGuardfileについて理解を深めたかったのでまとめてみました。

踏み込んでいくうちにソースコードまで読むことになり、メタプログラミングの勉強にもなりました。

この理解でguard-rspecguard-rubocopで自動生成される内容にアレンジが加えられるようになったので、生産性が上がりそうです:point_up:

ベースとして、こちらの記事を参考にさせて頂きました。
rubyのGuardとRSpecをRailsを使わない環境でつかう | 大石制作ブログ

この記事が役に立つ方

  • 自動生成されるGuardfileを自分ではアレンジ出来ない方

この記事のメリット

  • 自動生成されるGuardfileではどういう設定がされているのかが分かる
  • Guardfileを自分用にアレンジ出来るようになる。

環境

  • macOS Mojave バージョン10.14.6
  • シェル:zsh
  • Ruby:2.6.5
  • Rails:5.2.2
  • RSpec:3.9
  • Guard:2.16.1  

【準備】Guardのインストールから起動まで

RSpecを使う場合を例にしますので、gemはguard-rspecにしています。

Gemfile
group :development, :test do
  gem 'guard-rspec', require:false
end

Gemfileに上記gemを記載。

$ bundle install

gemをbundlerでインストールする。

$ bundle exec guard init

guardの設定ファイル(Guardfile)を作成。

$ bundle exec guard

guardを起動。
これでGuardfileに沿って動く状態になります。

次はデフォルトで生成されるGuardfileを見てみます。

デフォルトのGuardfile

生成されたGuardfileは以下のようになっています。

Guardfile
guard :rspec, cmd: "bundle exec rspec" do
  require "guard/rspec/dsl"
  dsl = Guard::RSpec::Dsl.new(self)

  # Feel free to open issues for suggestions and improvements

  # RSpec files
  rspec = dsl.rspec
  watch(rspec.spec_helper) { rspec.spec_dir }
  watch(rspec.spec_support) { rspec.spec_dir }
  watch(rspec.spec_files)

  # Ruby files
  ruby = dsl.ruby
  dsl.watch_spec_files_for(ruby.lib_files)

  # Rails files
  rails = dsl.rails(view_extensions: %w(erb haml slim))
  dsl.watch_spec_files_for(rails.app_files)
  dsl.watch_spec_files_for(rails.views)

  watch(rails.controllers) do |m|
    [
      rspec.spec.call("routing/#{m[1]}_routing"),
      rspec.spec.call("controllers/#{m[1]}_controller"),
      rspec.spec.call("acceptance/#{m[1]}")
    ]
  end

  # Rails config changes
  watch(rails.spec_helper)     { rspec.spec_dir }
  watch(rails.routes)          { "#{rspec.spec_dir}/routing" }
  watch(rails.app_controller)  { "#{rspec.spec_dir}/controllers" }

  # Capybara features specs
  watch(rails.view_dirs)     { |m| rspec.spec.call("features/#{m[1]}") }
  watch(rails.layouts)       { |m| rspec.spec.call("features/#{m[1]}") }

  # Turnip features and steps
  watch(%r{^spec/acceptance/(.+)\.feature$})
  watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
    Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
  end
end

見ると、rspecのテストがあるspec、railsのcontrollerroutesなどのディレクトリ配下のファイルを監視してくれていて、それに対応するテストを走らせてくれていそうです。

ただ、ほとんど変数なので、具体的にどのディレクトリやファイルを指しているのかは分かりません。

ただ、最初から自動でここまでやってくれるのはありがたいんですが、自分でもアレンジ出来るようになりたかったので中身を読み解いてみます。

Guardfileの基本構文

Guardfile
guard :プラグイン名 オプション do
  watch(監視対象) {実行対象}
end

監視対象・実行対象はディレクトリでもファイルでも可です。
{}内は別の書き方をすると、以下と同義です。

Guardfile
guard :プラグイン名 オプション do
  watch(監視対象) do
    実行対象
  end
end

また、以下のようにすると監視対象自身が実行対象になります。

Guardfile
guard :プラグイン名 オプション do
  watch(監視対象)
end

監視対象の中で正規表現キャプチャを使っている場合、ブロック内で変数として代入することが出来ます。(キャプチャについては、以下記事が非常に参考になります)
正規表現で名前付きキャプチャを使う - Qiita

【例】Railsでコントローラーをいじったら自動でテストが走るようにしたい

Railsでコントローラーを変更したとき、対象となるテストを走らせたいという場合を想定すると、Guardfileには以下のように書きます。

Guardfile
guard :rspec, cmd: "bundle exec rspec" do
  watch("%r{app/controllers/(.*?).rb}") {|m| "spec/controllers/#{m[1]}.rb"}
end

もしくは以下のようになります。

Guardfile
guard :rspec, cmd: "bundle exec rspec" do
  watch("%r{app/controllers/(.*?).rb}") do |m|
    "spec/controllers/#{m[1]}.rb"
  end
end

キャプチャされた内容は[0]でなく、[1]から始まることに注意です。

このコードの意味を日本語で書くと、

  • guard-rspecプラグインを使用して
  • app/controllers配下のRubyファイルを監視して
  • 動きがあればコマンドラインでbundl exec rspec spec/controllers配下のRubyファイルを実行する

ということですね。

次は自動生成されたGuardfileを読み解いてみます。

自動生成されたGuardfileを読み解く

自動生成されるGuardfileについては、メタプログラミングで簡潔に書かれていますが、以下ソースコードを見ると中身が分かるので、理解が可能です。
guard-rspec/dsl.rb at master · guard/guard-rspec · GitHub

以下一文を例にします。

watch(rspec.spec_helper) { rspec.spec_dir }

ここでは
監視対象:rspec.spec_helper
実行対象:rspec.spec_dir
です。

それぞれ変数に代入されているので具体的にどこを指すのかは書かれていませんが、ソースコードを見ると以下のように書かれています。

...
def rspec
  @rspec ||= OpenStruct.new(to_s: "spec").tap do |rspec|
    rspec.spec_dir = "spec"
    rspec.spec = ->(m) { Dsl.detect_spec_file_for(rspec, m) }
    rspec.spec_helper = "#{rspec.spec_dir}/spec_helper.rb"
    rspec.spec_files = %r{^#{rspec.spec_dir}/.+_spec\.rb$}
    rspec.spec_support = %r{^#{rspec.spec_dir}/support/(.+)\.rb$}
  end
end
...

式展開していくと、監視対象は、

  rspec.spec_helper = "spec/spec_helper.rb"

実行対象は、

  rspec.spec_dir = "spec"

ということが分かります。

つまり、元の一文

watch(rspec.spec_helper) { rspec.spec_dir }

は、

watch("spec/spec_helper.rb") { "spec" }

に変換することができますね!

おわりに

gemのソースコードをまともに見るのは初めてでしたが、この深堀りの仕方は他にも応用が効くなと感じました。

やっぱりgemとかプラグインを作る方の書くコードはキレイですね:relaxed:

参考にさせて頂いたサイト(いつもありがとうございます)

rubyのGuardとRSpecをRailsを使わない環境でつかう | 大石制作ブログ
正規表現で名前付きキャプチャを使う - Qiita
guard-rspec/dsl.rb at master · guard/guard-rspec · GitHub

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away