LoginSignup
1
2

More than 5 years have passed since last update.

Rakeタスクの定義を追う

Posted at
1 / 16

はじめに

最近業務でRailsを触り始めました。
Rails tutorialはこなしているものの、仕事の対象は既存の大規模アプリケーションなので、カスタマイズされている部分が多く読み解くのに苦労しています。

そもそもなぜコードを読むのに苦労するのかということについて、おおよそ以下の3つの項目が重なっているためだということがわかったので、Rakeを例にとって説明します。

Rubyを読む際に苦労する点
- 豊富なシンタックスシュガー
- 定義はどこに
- gemの仕様


Rakeタスク

私が触っているアプリケーションにはこのようなタスクが定義されていました。

namespace :hoge do
  desc 'Task without arguments'
  task task1: :environment do
    puts "This is task1"
  end

  desc 'Task with arguments'
  task :task2, ['arg1', 'arg2'] => :environment do |arg1, arg2|
    puts "This is task2 with: #{arg1}, #{arg2}"
  end
end

Rakeタスク

私が触っているアプリケーションにはこのようなタスクが定義されていました。

namespace :hoge do
  desc 'Task without arguments'
  task task1: :environment do # :environment とは
    puts "This is task1"
  end

  desc 'Task with arguments'
  # 唐突に登場する "=>" (Hashの初期化に使用する記号では?
  # 上のタスク定義とフォーマットが異なる。こちらでは :environment が "=>" で指し示されているけれど何が違うのか?
  task :task2, ['arg1', 'arg2'] => :environment do |arg1, arg2|
    puts "This is task2 with: #{arg1}, #{arg2}"
  end
end

🤔


シンタックスシュガーを外す

  • 豊富なRubyシンタックスシュガー → シンタックスシュガーを外した場合を考える
  • 定義はどこに
  • gemの仕様

元々の定義

  task :task1 => :environment do
    ...
  end

  task :task2, ['arg1', 'arg2'] => :environment do |arg1, arg2|
    ...
  end

引数のシンタックスシュガーを外す

  task { :task1 => :environment } do
    ...
  end

  task :task2, { ['arg1', 'arg2'] => :environment } do |arg1, arg2|
    ...
  end

メソッド呼び出しのシンタックスシュガーを外す

  task({ :task1 => :environment }) do
    ...
  end

  task(:task2, { ['arg1', 'arg2'] => :environment }) do |arg1, arg2|
    ...
  end

ここまでくると、 task() メソッドにタスク名その他の引数と、ひとつのブロックを与えて呼び出している、ということがわかります。
(引数ブロックについてはこのポストがわかりやすい)


引数のフォーマットが異なる 🤔

しかし、ブロックに引数がある場合とない場合で、 task() メソッドの引数のフォーマットが異なり、ここはまだよくわかりません。

  # 引数はHashのみ
  # タスク名っぽいsymbolがHashのkeyに入ってる
  task({ :task1 => :environment }) do
    ...
  end

  # タスク名っぽいsymbolとHash、ふたつの値を引数にとっている
  task(:task2, { ['arg1', 'arg2'] => :environment }) do |arg1, arg2|
    ...
  end

定義を辿る

  • 豊富なRubyシンタックスシュガー → シンタックスシュガーを外した場合を考える
  • 定義はどこに → RubyMineのGo To Declarationを使用する
  • gemの仕様

RubyMine

選択した要素を右クリックして Go To -> Declaration で定義に飛んでくれます。
namespaceやModuleをincludeしている部分も解決してくれるので優秀です。

task() の実装を追っていくと、最終的に Rake::TaskManager.define_task() がタスク生成処理を行っているということが分かります。


gemの実装を読む

  • 豊富なRubyシンタックスシュガー → シンタックスシュガーを外した場合を考える
  • 定義はどこに → RubyMineのGo To Declarationを使用する
  • gemの仕様 → コードを読む

Rake::TaskManager.define_task() がタスク生成処理を司っているので、見てみます

    def define_task(task_class, *args, &block) # :nodoc:
      task_name, arg_names, deps = resolve_args(args) # 引数の解釈

      ...

      task_name = task_class.scope_name(@scope, task_name)
      deps = [deps] unless deps.respond_to?(:to_ary)
      deps = deps.map { |d| Rake.from_pathname(d).to_s }
      task = intern(task_class, task_name)  # task_class.new してオブジェクト生成

      # 以下taskオブジェクトのセットアップ
      task.set_arg_names(arg_names) unless arg_names.empty?
      ...
      task.enhance(deps, &block)
      ...
    end

ここまでくるとコードを読むことができるようになり、↑の例だと resolve_args() の内容を辿ると引数の解釈部分の実装が出てきます。


定義からわかったtaskの引数

一行一行解説はしませんが、 resolve_args() を読んでいくと以下のようなフォーマットになっていることが分かりました

# dependency ( `:environment` など )がない場合
task(:task_name, :arg1_name, :arg2_name, ...) do |arg1, arg2, ...| 
  ...
end

# dependencyがあり、ブロックに引数がない場合
task({ :task_name => [ dependency1, dependency2, ...] }) do 
  ...
end

# dependencyがあり、ブロックに引数がある場合
task(:task_name, { [:arg1_name, :arg2_name, ...] => [ dependency1, dependency2, ...] }) do |arg1, arg2, ...|
  ...
end

# dependencyがひとつのみの場合は配列を外すことも可能
task({ :task_name => dependency }) do 
  ...
end

まとめ

ソースを読むために:

  • シンタックスシュガーを外す
  • メソッド定義を突き止める
  • その上で実装を読み解く
1
2
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
1
2