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

Thor入門 - RakeからThorへ -

More than 3 years have passed since last update.

Rakeタスクの引数の取り扱いまとめ

RakeタスクはRailsのプロジェクトでよく作るのだけれど、どうも引数の受け渡しが面倒でイライラする。
Rakeタスクで引数を扱うには主に以下の2パターンのやり方があるのだが・・・・。

1. 引数を定義する方法

hoge.rake
task :hoge, [:a, :b] do |task, args|                                                                                                                                                   
  p args.a                                                                                                                                                                                                                
  p args.b                                                                                                                                                                                                                
end
実行結果
$ bin/rake hoge[yo,hey]
"yo"
"hey"
$ bin/rake "hoge[yo,hey]" # zsh拡張している場合
"yo"
"hey"

実行時に[]を付けたり、自分はzshユーザーなのでタスクを「"」でくくる必要があったりと面倒。
第2引数だけ指定したい場合は bin/rake hoge[,'hey!'] などと微妙な書き方をしなければならず面倒。

2. 環境変数を経由する方法

hoge.rake
task :hoge do |task|                                                                                                                                                                                                        
  p ENV['a']                                                                                                                                                                                                                
  p ENV['b']                                                                                                                                                                                                                
end 
実行結果
$ bin/rake hoge a=yo b=hey
"yo"
"hey"

実行時に環境変数名を書いてやらねばならず、やはり面倒。

Rakeタスクの引数がウザいのでThorを使う

ならRakeを使わなければいい。Thorを使おう。
Thorはコマンドラインツールを作る為のgemで、引数の受け渡しは当然簡潔に書けるし、オプションのパースやUsage Messageの表示などを簡単に作成でき、おまけにシンタックスがRake-likeなのでRakeを理解しているエンジニアなら違和感なく移行できるCLIツールです。

インストール

普通にインストールします。

% gem i thor

Thorで書くとどうなるか

先ほどのRakeタスクをThorを使って書くとこうなる。

test.thor
class Test < Thor
  desc 'hoge [a] [b]', 'parameter test'
  def hoge a, b
    p a
    p b
  end
end

問題のパラメータは・・・。

実行結果
$ thor test:hoge yo hey
"yo"
"hey"

これだ!こう書きたかったんだ!!234567890

Thor入門

というわけで引数の問題は解決しましたが、それ以外にも機能が豊富なThorの使い方を解説します。

基本

では試しに超基本的なコードを書いてみましょう。
現在のディレクトリにtest.thorという名前のファイルを作成します。内容は以下のとおり。

test.thor
class Test < Thor # Thorクラスを継承
  desc 'say [Name]', 'say task' # Usage Message, 詳細な説明
  def say(name) # publicで定義したメソッドが実行するタスク
    puts "Hello, #{name}!"
  end
end

実行してみましょう。

実行結果
$ thor test:say # パラメータ無しだとUsage Massageを出力
ERROR: "thor say" was called with no arguments
Usage: "thor test:say [NAME]"
$ thor test:say Thor
Hello, Thor!

簡単ですね。

名前空間

基本を理解出来たら次は名前空間の説明です。
Thorのタスク名はデフォルトだとRubyの名前空間を元に決まります。
これはそのままなので分かりやすいでしょう。

app.thor
class App < Thor
  desc 'say', '言う'
  def say
    puts 'Yo!'
  end
end
app.thor実行結果
$ thor app:say # thor downcaseしたクラス名:メソッド名
Yo!

当然moduleをAppの上に追加すると、そのままタスクの名前に追加されます。

app.thor
module Livesense                                                                                                                                                                                                            
  class App < Thor                                                                                                                                                                                                         
    desc 'say', '言う'                                                                                                                                                                                               
    def say                                                                                                                                                                                                                 
      puts 'Yo!'                                                                                                                                                                                               
    end                                                                                                                                                                                                                     
  end                                                                                                                                                                                                                       
end 
app.thor実行結果
$ thor livesense:app:say
Yo!

名前空間を変更することもできます。

app.thor
module Livesense                                                                                                                                                                                                            
  class App < Thor                                                                                                                                                                                                         
    namespace :yo # namespace :任意の名前 で指定
    desc 'say', '言う'                                                                                                                                                                                               
    def say                                                                                                                                                                                                                 
      puts 'Yo!'                                                                                                                                                                                               
    end                                                                                                                                                                                                                     
  end                                                                                                                                                                                                                       
end  
app.thor実行結果
$ thor yo:say
Yo!

個人的にはnamespaceをあまり多用されると辛くなるかなと思っている。
正しく使えば問題ないのだが。

実行可能なコマンドを作る

ThorをRakeタスクの代替として使うのを前提に説明をしてきましたが、実行可能なコマンドも作成可能です。

yo
#!/usr/bin/env ruby
require 'thor'

class YoCommand < Thor
  desc 'say', '言う'
  def say
    puts 'hey!'
  end
end

YoCommand.start

上記のようにSheBangを書いてthorをrequireして、実行権限を付加し、実行します。

実行結果
$ chmod a+x ./yo
$ ./yo say
hey!

しかしこれだとパラメータを毎回指定しなければならないので、startメソッドのパラメータに配列でメソッド名を書いておけば実行時にパラメータを省略できます。

yo
YoCommand.start %w(say)
実行結果
$ chmod a+x ./yo
$ ./yo
hey!

PATHに通して普通のUNIXコマンドみたいに使うならこんな感じですね。

メソッドオプション

実行可能なコマンドを作るとなると、今度は実行時にオプションを付けたくなる。
method_optionを使えばよくあるオプションの挙動を簡単に定義できます。

コマンドに実行時オプションを追加する

とりあえずは超基本的なサンプルコードです。

yo
#!/usr/bin/env ruby                                                                                                                                                                                                         
require 'thor'                                                                                                                                                                                                              

class YoCommand < Thor                                                                                                                                                                                                      
  desc 'say', '言う'                                                                                                                                                                                                        
  method_option :force                                                                                                                                                                                                      
  def say                                                                                                                                                      
    if options.force # options[:force]でもOK
      # optionsはhash形式で値が入っている => {"force"=>"force"}
      puts 'yo!hey!'                                                                                                                                                                                                        
    else                                                                                                                                                                                                                    
      puts 'yo!'                                                                                                                                                                                                            
    end                                                                                                                                                                                                                     
  end                                                                                                                                                                                                                       
end                                                                                                                                                                                                                         

YoCommand.start %w(say) + ARGV
実行結果
$ ./yo
yo!
$ ./yo --force
yo!hey!

メソッドオプションのオプション

コマンドにオプションを追加すると、色々とお決まりの処理を書かなければならなくなる。
オプションのエイリアスを設定したいとか、デフォルト値を設定しておけばロジックが綺麗になるな・・などなど。

yo
#!/usr/bin/env ruby
require 'thor'

class YoCommand < Thor
  desc 'say', '言う'
  method_option :number, aliases: '-n', default: 1, type: :numeric                                                                                                                                                          
  def say                                                                                                                                                                                                                   
    puts 'yo!hey!' * options.force                                                                                                                                                                                          
  end                                                                                                                                                                                                                       
end                                                                                                                                                                                                                         

YoCommand.start %w(say) + ARGV

当然その手の機能も上記のようにオプションを指定するだけ。

実行結果
% ./yo --number 2
yo!hey!yo!hey!
% ./yo -n 2 # aliasesオプション
yo!hey!yo!hey!
% ./yo --number stirng # typeオプション
Expected numeric value for '--number'; got "stirng"
% ./yo # defaultオプション
yo!hey!

他にもありますが、使用頻度が高そうなのはこのぐらいですかね。

Thorコマンドをインストールしたい

実行可能なコマンドであればbinに置くなりPATHを通せばいいのだけど、もう少し手軽に使えればそれでいいという場合、Thor installでシステム領域にスクリプトをインストールし、使うことができます。
ローカルのファイルは当然ですが、Thorのinstallはウェブ上にアップしたファイルからでもインストールOKなので、例えばGistに公開しているファイルからでもインストールできます。

実行結果
$ thor install https://gist.githubusercontent.com/yatmsu/d156000dd6eefb9c52bd/raw/4fe831ce6e987e46833abee0444ef1505c1251f5/yo.thor
$ thor list
gist
----
thor gist:yo  # gist sample task

$ thor gist:yo
hey!

更にスクリプトがアップデートされても、update一発で更新できます。

実行結果
$ thor installed # インストール済みのスクリプトを表示
Modules  Namespaces
-------  ----------
yo.thor  gist

gist
----
thor gist:yo  # gist sample task

$ thor update yo.thor # モジュール名を指定することによって更新

これは便利。

ThorをRailsで使いたい

「Rubyやってます!」って人は大体Railsを使っていることが殆どなので、RailsのRakeタスクをThorに置き換えたいと思うはず。手順は以下のとおり。

インストール

RailsはGeneratorでThorを既に使っています。scaffoldとかファイルを生成するやつでお馴染みのアレです。なので、特別インストールする必要はありません。

Thorfileを作成

次はThorfileを作成します。これはRakefileと同じようなファイルです。
これがないとActiveRecordにアクセス出来ません。

$RAILS_ROOT/Thorfile
require File.expand_path('../config/environment', __FILE__)

各タスクでrequireしている例を見かけるんですが、冗長的なのでこっちの方がDRYでおすすめ。

Thorでタスクを作成

で、普通にThorでタスクを作成。

$RAILS_ROOT/lib/tasks/sample.thor
class Sample < Thor
  desc 'users', 'user名を表示'
  method_option :limit, default: 10
  def users
    User.all.limit(options.limit).each do |user|
      puts user.first_name
    end
  end
end

実行してみます。

実行結果
$ bundle exec thor sample:users --limit 3
洋平
陽平
遥平

よし動いた・・!
これでRakeのウザい引数を気にせず年を越せる!
(∩´∀`)∩ワーイ

参考URL

yatmsu
Qiitaどう使おうか。 メモっぽいのを書いていくかな。
http://yatmsu.hatenablog.com/
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