1. yatmsu

    重複コードを削除

    yatmsu
Changes in body
Source | HTML | Preview
@@ -1,370 +1,365 @@
## 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 hoge[yo,hey]
"yo"
"hey"
$ bin/rake "hoge[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 - もっと詳細を知りたい人はここを見るのが一番。