1. yatmsu

    Posted

    yatmsu
Changes in title
+Thor入門 - RakeからThorへ -
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,370 @@
+## Rakeタスクの引数の取り扱いまとめ
+
+RakeタスクはRailsのプロジェクトでよく作るのだけれど、どうも引数の受け渡しが面倒でイライラする。
+Rakeタスクで引数を扱うには主に以下の2パターンのやり方があるのだが・・・・。
+
+### 1. 引数を定義する方法
+
+```rb:hoge.rake
+task :hoge, [:a, :b] do |task, args|
+ p args.a
+ p args.b
+end
+```
+
+```shell-session:実行結果
+$ bin/rake test[yo,hey]
+"yo"
+"hey"
+$ bin/rake "test[yo,hey]" # zsh拡張している場合
+"yo"
+"hey"
+```
+
+実行時に[]を付けたり、自分はzshユーザーなのでタスクを「"」でくくる必要があったりと面倒。
+第2引数だけ指定したい場合は bin/rake hoge[,'hey!'] などと微妙な書き方をしなければならず面倒。
+
+### 2. 環境変数を経由する方法
+
+```rb:hoge.rake
+task :hoge do |task|
+ p ENV['a']
+ p ENV['b']
+end
+```
+
+```shell-session:実行結果
+$ bin/rake hoge a=yo b=hey
+"yo"
+"hey"
+```
+
+実行時に環境変数名を書いてやらねばならず、やはり面倒。
+
+## Rakeタスクの引数がウザいのでThorを使う
+
+ならRakeを使わなければいい。[Thor](https://github.com/erikhuda/thor)を使おう。
+Thorはコマンドラインツールを作る為のgemで、引数の受け渡しは当然簡潔に書けるし、オプションのパースやUsage Messageの表示などを簡単に作成でき、おまけにシンタックスがRake-likeなのでRakeを理解しているエンジニアなら違和感なく移行できるCLIツールです。
+
+### インストール
+
+普通にインストールします。
+
+```shell-session
+% gem i thor
+```
+
+### Thorで書くとどうなるか
+
+先ほどのRakeタスクをThorを使って書くとこうなる。
+
+```rb:test.thor
+class Test < Thor
+ desc 'hoge [a] [b]', 'parameter test'
+ def hoge a, b
+ p a
+ p b
+ end
+end
+```
+
+問題のパラメータは・・・。
+
+```shell-session:実行結果
+$ thor test:hoge yo hey
+"yo"
+"hey"
+```
+
+これだ!こう書きたかったんだ!!234567890
+
+## Thor入門
+
+というわけで引数の問題は解決しましたが、それ以外にも機能が豊富なThorの使い方を解説します。
+
+### 基本
+
+では試しに超基本的なコードを書いてみましょう。
+現在のディレクトリにtest.thorという名前のファイルを作成します。内容は以下のとおり。
+
+```rb:test.thor
+class Test < Thor # Thorクラスを継承
+ desc 'say [Name]', 'say task' # Usage Message, 詳細な説明
+ def say(name) # publicで定義したメソッドが実行するタスク
+ puts "Hello, #{name}!"
+ end
+end
+```
+
+実行してみましょう。
+
+```shell-session:実行結果
+$ 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の名前空間を元に決まります。
+これはそのままなので分かりやすいでしょう。
+
+```rb:app.thor
+class App < Thor
+ desc 'say', '言う'
+ def say
+ puts 'Yo!'
+ end
+end
+```
+
+```shell-session:app.thor実行結果
+$ thor app:say # thor downcaseしたクラス名:メソッド名
+Yo!
+```
+
+当然moduleをAppの上に追加すると、そのままタスクの名前に追加されます。
+
+```rb:app.thor
+module Livesense
+ class App < Thor
+ desc 'say', '言う'
+ def say
+ puts 'Yo!'
+ end
+ end
+end
+```
+
+```shell-session:app.thor実行結果
+$ thor livesense:app:say
+Yo!
+```
+
+名前空間を変更することもできます。
+
+```rb:app.thor
+module Livesense
+ class App < Thor
+ namespace :yo # namespace :任意の名前 で指定
+ desc 'say', '言う'
+ def say
+ puts 'Yo!'
+ end
+ end
+end
+```
+
+```shell-session:app.thor実行結果
+$ thor yo:say
+Yo!
+```
+
+個人的にはnamespaceをあまり多用されると辛くなるかなと思っている。
+正しく使えば問題ないのだが。
+
+### 実行可能なコマンドを作る
+
+ThorをRakeタスクの代替として使うのを前提に説明をしてきましたが、実行可能なコマンドも作成可能です。
+
+```rb:yo
+#!/usr/bin/env ruby
+require 'thor'
+
+class YoCommand < Thor
+ desc 'say', '言う'
+ def say
+ puts 'hey!'
+ end
+
+ desc 'say', '言う'
+ def say
+ puts 'hey!'
+ end
+end
+
+YoCommand.start
+```
+
+上記のようにSheBangを書いてthorをrequireして、実行権限を付加し、実行します。
+
+```shell-session:実行結果
+$ chmod a+x ./yo
+$ ./yo say
+hey!
+```
+
+しかしこれだとパラメータを毎回指定しなければならないので、startメソッドのパラメータに配列でメソッド名を書いておけば実行時にパラメータを省略できます。
+
+```rb:yo
+YoCommand.start %w(say)
+```
+
+```shell-session:実行結果
+$ chmod a+x ./yo
+$ ./yo
+hey!
+```
+
+PATHに通して普通のUNIXコマンドみたいに使うならこんな感じですね。
+
+### メソッドオプション
+
+実行可能なコマンドを作るとなると、今度は実行時にオプションを付けたくなる。
+**method_option**を使えばよくあるオプションの挙動を簡単に定義できます。
+
+#### コマンドに実行時オプションを追加する
+
+とりあえずは超基本的なサンプルコードです。
+
+```rb: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
+```
+
+```shell-session:実行結果
+$ ./yo
+yo!
+$ ./yo --force
+yo!hey!
+```
+
+#### メソッドオプションのオプション
+
+コマンドにオプションを追加すると、色々とお決まりの処理を書かなければならなくなる。
+オプションのエイリアスを設定したいとか、デフォルト値を設定しておけばロジックが綺麗になるな・・などなど。
+
+```rb: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
+```
+
+当然その手の機能も上記のようにオプションを指定するだけ。
+
+```shell-session:実行結果
+% ./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に公開しているファイルからでもインストールできます。
+
+```shell-session:実行結果
+$ 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一発で更新できます。
+
+```shell-session:実行結果
+$ 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にアクセス出来ません。
+
+```rb:$RAILS_ROOT/Thorfile
+require File.expand_path('../config/environment', __FILE__)
+```
+
+各タスクでrequireしている例を見かけるんですが、冗長的なのでこっちの方がDRYでおすすめ。
+
+### Thorでタスクを作成
+
+で、普通にThorでタスクを作成。
+
+```rb:$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
+```
+
+実行してみます。
+
+```shell-session:実行結果
+$ bundle exec thor sample:users --limit 3
+洋平
+陽平
+遥平
+```
+
+よし動いた・・!
+これでRakeのウザい引数を気にせず年を越せる!
+(∩´∀`)∩ワーイ
+
+## 参考URL
+
+* https://github.com/erikhuda/thor/wiki - もっと詳細を知りたい人はここを見るのが一番。