Edited at

Thor入門 - RakeからThorへ -

More than 1 year has 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