scramble cadenza

技術ネタのガラクタ置き場

embulk-filter-eval というフィルタープラグイン書いた

イントロ

まぁ半分ネタも入ってますが、何かちょっとした事を行う程度なら役に立つはず。

mgi166/embulk-filter-eval

これは何が便利?

input として与えられたカラムに対して、Ruby のコードでちょっとした変換を加える事ができる embulk プラグインです。
embulkversion 0.5.2 で作りました。

example

github の README に書いてあるサンプルそのままなのですが。(サボってるわけじゃなくて、何かサンプルがあったほうが説明しやすい)
以下のように embulk コマンドを使って sample を作り、preview できたとします。

$ embulk example
$ embulk guess embulk-example/example.yml -o config.yml

preview すると、以下の様な出力です。

$ embulk preview config.yml

+---------+--------------+-------------------------+-------------------------+----------------------------+
| id:long | account:long |          time:timestamp |      purchase:timestamp |             comment:string |
+---------+--------------+-------------------------+-------------------------+----------------------------+
|       1 |       32,864 | 2015-01-27 19:23:49 UTC | 2015-01-27 00:00:00 UTC |                     embulk |
|       2 |       14,824 | 2015-01-27 19:01:23 UTC | 2015-01-27 00:00:00 UTC |               embulk jruby |
|       3 |       27,559 | 2015-01-28 02:20:02 UTC | 2015-01-28 00:00:00 UTC | Embulk "csv" parser plugin |
|       4 |       11,270 | 2015-01-29 11:54:36 UTC | 2015-01-29 00:00:00 UTC |                       NULL |
+---------+--------------+-------------------------+-------------------------+----------------------------+

eval_columns

では、例えば全ての id に 1 を足して出力したい、という要望があったとします。
こんな時に filter-eval では簡単に書くことが出来ます。 config.yml に以下のように指定するだけ。

filters:
  - type: eval
    eval_columns:
      - id: value + 1

value には id 列の要素が入ってきます。
なので value + 1 とすると、最初は value =1 なので、 value + 1 が評価されて 2 が返って、次は 3 が返って... というわけです。

$ embulk preview config.yml

+---------+--------------+-------------------------+-------------------------+----------------------------+
| id:long | account:long |          time:timestamp |      purchase:timestamp |             comment:string |
+---------+--------------+-------------------------+-------------------------+----------------------------+
|       2 |       32,864 | 2015-01-27 19:23:49 UTC | 2015-01-27 00:00:00 UTC |                     embulk |
|       3 |       14,824 | 2015-01-27 19:01:23 UTC | 2015-01-27 00:00:00 UTC |               embulk jruby |
|       4 |       27,559 | 2015-01-28 02:20:02 UTC | 2015-01-28 00:00:00 UTC | Embulk "csv" parser plugin |
|       5 |       11,270 | 2015-01-29 11:54:36 UTC | 2015-01-29 00:00:00 UTC |                       NULL |
+---------+--------------+-------------------------+-------------------------+----------------------------+

実行してみたら、id のカラムが 2, 3, 4, 5 となりました。

out_columns

このプラグインでは出力したいカラムを制御することも可能で、out_columns という設定で、出力したいカラムを配列で書けば可能です。

filters:
  - type: eval
    eval_columns:
      - id: value + 1
    out_columns:
      - id

これは out_columnsid しか指定していないので

$ embulk preview config.yml

+---------+
| id:long |
+---------+
|       2 |
|       3 |
|       4 |
|       5 |
+---------+

こんな感じになります。

まとめ

またゴミのような gem を作ってしまった...
次はもっといいものを考えてます。

rbenv の plugins

イントロ

今使ってる rbenvplugin たち。
rbenv はこういうエコシステムが便利だから使い続けている。

最近務めている会社では流行りのようなので、私もやってみる。

plugins

% l .rbenv/plugins/
rbenv-ctags
rbenv-default-gems
rbenv-gemset
rbenv-update
ruby-build

順に紹介

rbenv-ctags

tpope/rbenv-ctags

ruby のコアライブラリにタグファイル(ctags 用)を作ってくれるやつ。 ruby のコアライブラリを追うときに便利。だが、これは vim 用のタグしか吐いてくれない。(作者が vim 使いだから?)

vim 使える人は本家を使えばいいのだけど、私は emacs しか使えないので、emacs 用のタグを吐くように、fork したものを使ってる。

mgi166/rbenv-ctags

PR 出すと vim マッチョな作者に殺されそうなのでしていない。

rbenv-default-gems

sstephenson/rbenv-default-gems

rbenv build を使って ruby を install した時に、自動で gem を install してくれるやつ。 一回入れてしまうと便利。有名。

少し変わったことといえば、mgi166/dotfilesdefault-gems ファイルをバージョン管理していて

rake install default-gems

~/.rbenv/default-gems ディレクトリにシンボリックリンクを貼ってくれるようにしたが、このメソッド、全く流行る気がしない。

% cat ~/.rbenv/default-gems
bundler
pry
pry-byebug
pry-stack_explorer
ripper-tags

デフォルトの環境汚したくないけど、汚しまくっていて、羞恥心がなくなりつつある今日この頃。

rbenv-gemset

jf/rbenv-gemset

一言で言うと rbenvrvm gemset gem を管理するやつ。bundler はプロジェクト単位で管理するけど、これは自在に namespace を作ることができる。

昔は gem 作るたびに gemset 作ってた。Gemfile 作るのめんどいけど、何か作ろうかなーって時に使う。
最近は何かやる際には gem にするつもりがなくても bundle gembundle init で頑張る場合が多く、出番が少ない。

あ、でも rails っていう gemset 作っておくと、rails new する時楽で好き。

ruby-build のように、.rbenv-gemsets ファイルの有無で gemset が決まるので、bundler共存する道はある、と思ってる。

% rbenv gemset list
1.9.3-p545:
  chef_repo
  veewee-boxes
2.1.0:
  d_parallel
  hipchat_searcher
2.1.2:
  dangerous_open_uri
  hipchat_searcher
  qiita-notify
  rails
  s3_utils
2.1.5:
  rails
2.2.1:
  rails
2.3.0-dev:
  rails

rbenv-update

rkh/rbenv-update

rbenv update コマンドで、全ての plugin を git pull してくれるやつ。 これのお陰で ruby の新しいバージョンが出るたびに、毎回

cd ~/.rbenv/plugins/ruby-build
git pull

しなくてよくなった。 コマンド覚えやすいから好き。

ruby-build

sstephenson/ruby-build

rbenv-build じゃなくて、ruby-build rbenv を有名にした MVP。(と思っている)

説明不要。

まとめ

ネタになると思ったけど、有名どころしか使ってない。

boot2docker 内のコンテナに ssh で接続する

イントロ

最近ようやく docker を触り始めています。

にわか丸出しなので、【翻訳】Dockerコンテナ内でSSHDを実行してはいけない理由 | POSTD のような記事を見かけたのにも関わらず、ssh で接続しようとしています。

理由は serverspec 使ってコンテナのテストしたいと考えたからです。
(ssh 接続しないとできない... はず)

方法

Dockerfile

FROM ubuntu:14.04

RUN apt-get update
RUN apt-get install -y openssh-server
RUN mkdir /var/run/sshd
RUN echo 'root:root' | chpasswd
RUN sed -i 's/PermitRootLogin without-password/PermitRootLogin yes/' /etc/ssh/sshd_config

EXPOSE 22
CMD /usr/sbin/sshd -D

build

% docker build -t sshd .
% docker port $(docker run -d -P openssh) 22
#=> 0.0.0.0:49155

% docker ps -a                                                                                                
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS                   NAMES
5d215294c5cf        sshd:latest         "/bin/sh -c '/usr/sb   17 seconds ago      Up 16 seconds       0.0.0.0:49155->22/tcp   boring_hodgkin

無事コンテナが作れた。
docker run-P オプションを渡さないと expose された port が公開されないらしく、若干ハマった。(https://docs.docker.com/reference/run/#expose-incoming-ports)

docker port コマンド実行結果の port 部分を覚えておく。

接続してみる

-p の引数は先ほどメモった docker port コマンド実行結果を渡す。

% ssh root@$(boot2docker ip) -p 49155                                                                         

The authenticity of host '[192.168.59.103]:49155 ([192.168.59.103]:49155)' can't be established.
RSA key fingerprint is 6c:52:de:f7:85:8f:29:69:8c:5c:78:d4:ec:43:3d:69.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[192.168.59.103]:49155' (RSA) to the list of known hosts.
root@192.168.59.103's password:
Welcome to Ubuntu 14.04 LTS (GNU/Linux 3.2.0-76-generic x86_64)

 * Documentation:  https://help.ubuntu.com/

The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

root@5d215294c5cf:~#

入れたー

おまけ

boot2docker で試した理由は、docker 周辺の仕組みを理解したかったからであって、boot2docker でないといけない理由はありません。
後、観測範囲では、何故か boot2docker で試した例が見当たらなかったから。

ActiveSupport::Configurable の話

イントロ

よく設定ファイルで見るクラスを簡単に作れる module ActiveSupport::Configurable の紹介
使い方は、rails/configurable.rb at master · rails/rails のコメント文を読んだほうが早い。

よくあるやつ

具体例を幾つか紹介。
引数が違ったり、ブロック引数有り/無しだったり、メソッド名が違う等の差はあれど、どれも似ている

vagrant

Vagrantfile を拝借

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "centos7"
  config.vm.network "private_network", ip: "192.168.33.10"
  ....
end

rails

config/environments/development.rb を拝借

Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

  # In the development environment your application's code is reloaded on
  # every request. This slows down response time but is perfect for development
  # since you don't have to restart the web server when you make code changes.
  config.cache_classes = false
  ...
end

tapp

esminc/tapp より

Tapp.configure do |config|
  config.default_printer = :awesome_print
  config.report_caller   = true
end

rails_config

railsconfig/rails_config より

RailsConfig.setup do |config|
  config.const_name = 'AppSettings'
  config.use_env = true
end

他にもあったけど、紙面の関係上省略。

使ってみる

インスタンスを使う

require 'active_support/configurable'

class MyConfig
  include ActiveSupport::Configurable
  config_accessor :id, :name
end

c = MyConfig.new
c.id = 'aaa'
c.id
#=> 'aaa'

c.name = 'bbb'
c.name
#=> 'bbb'

c.hoge
#=> NoMethodError: undefined method `hoge'

config_accessorメソッド名を渡すと、(内部でゴニョゴニョしてるが)アクセサが作られるというだけ。
また、config というインスタンスメソッドが定義されて、それを呼ぶと登録した設定が全て取れる。

c.config
#=> {:id=>"aaa", :name=>"bbb"}

クラスを使う

ActiveSupport::Configurable を include したクラスでは、ブロックを渡すお馴染みのインターフェイスが提供される。

class MyConfig
  include ActiveSupport::Configurable
  config_accessor :id, :name
end

# みたことあるやつや!
MyConfig.configure do |config|
  config.id = 'aaa'
  config.name = 'bbb'
end

MyConfig.id
#=> 'aaa'

MyConfig.name
#=> "bbb"

MyConfig.config
#=> {:id=>"aaa", :name=>"bbb"}

とはいえ、これだとクラス自体が設定を持っていて、何か気持ち悪い。

応用

例えば以下のようにクラスを書いてみると、しっくりくる。

class MyApp
  class Config
    include ActiveSupport::Configurable
    config_accessor :id, :name
  end

  def self.configure(&block)
    yield config
  end

  def self.config
    @config ||= Config.new
  end
end

MyApp.configure do |config|
  config.id = 'aaa'
  config.name = 'bbb'
end

MyApp.config
#=> <MyApp::Config:0x007f9b7b9ab870 @_config={:id=>"aaa", :name=>"bbb"}>

config = MyApp.config
config.id
#=> 'aaa'
config.name
#=> 'bbb'
  • 設定を保持するクラス(MyApp::Config)に ActiveSupport::Configurable を include させる
    • 更に config_accessor でアクセサを宣言する

とするだけで

  • MyApp.config とすれば設定インスタンスを参照可能
    • MyApp を参照できれば、誰でも config を知ることができる
  • MyApp::Config はアクセサを定義しているだけ

みたいな事ができ、馴染みのあるコードになってきた。

けどこれだけだと、config_acessor の代わりに attr_accessor でいいんじゃね?ってなる。

オプションを渡す

ところがどっこい config_accessor にはオプションを渡すことができる。
instance_readerinstance_writer だ。

class MyConfig
  include ActiveSupport::Configurable
  config_accessor :id
  config_accessor :name, instance_reader: false
end

c = MyConfig.new
c.id = 'aaa'
c.id
#=> "aaa"

c.name = 'bbb'
c.name
#=> NoMethodError: undefined method `name'

instance_reader: false を与えると、MyConfig#name という reader のメソッドが提供されなくなるので、NoMethodError になる。

instance_writer: false はというと、その逆になる。

class MyConfig
  include ActiveSupport::Configurable
  config_accessor :id
  config_accessor :name, instance_writer: false
end

c = MyConfig.new

c.id = 'aaa'
c.id
#=> "aaa"

c.name = 'bbb'
#=> NoMethodError: undefined method `name='
c.name
#=> nil

今度は MyConfig#name= というメソッドだけ提供されていないので、writer を呼ぼうとすると NoMethodError になる。

ブロックを渡す

default 値を定義することもできて、ブロックを渡せば可能。

class MyConfig
  include ActiveSupport::Configurable
  config_accessor :id
  config_accessor :name do
    'bbb'
  end
end

c = MyConfig.new
c.name
#=> "bbb"

まとめ

rails/configurable.rb at master · rails/rails

デフォルト値などを考えだすまでは attr_accessor でいいんじゃないか...? って思いました。
けど使い方次第でしょうねー

React.js で親子関係にある component の更新順序

イントロ

話題の React.js を去年末ぐらいから、ちょこちょこと触っています。
殆ど Javascript 触ったこと無い状態から、いきなり React 始めて大分修羅の道だったのですが、最近ようやく慣れてきました。

何となくわかってきた React の性質っぽいものを残しておくメモ

これは何か

React.js で作った component の更新順序を見てみた、というもの。
結論を言ってしまうと、親子関係にあると、必ず 「親 → 子」の順序で更新が行われる

現象

React.js の基本原則として、this.setState を呼ぶと、それがトリガーになって render が呼ばれる、というものがある
(state とは Ruby でいうインスタンス変数のようなもの。setStatesetter だと思えばいい)
以下のように、子も親も同時に setState を呼ぶとどうなるのだろう?という話。

超簡単なサンプルを作ってみた。

var Parent = React.createClass({
  getInitialState: function() {
    return (
      { age: 0 }
    );
  },
  setAge: function() {
    var value = this.refs.age.getDOMNode().value;
    this.setState({ age: Number(value) });
  },
  increment: function() {
    this.setState({ age: this.state.age + 1});
  },
  render: function() {
    console.log('in parent');

    return (
      <div id="parent">
        <h2>parent</h2>
        <p>input parent age: {this.state.age}</p>
        <input type="text" ref="age"/>
        <button onClick={this.setAge}>submit</button>

        <h2>child</h2>
        <Child increment={this.increment} />
      </div>
    );
  }
});

var Child = React.createClass({
  getInitialState: function() {
    return (
      { age: 0 }
    );
  },
  increment: function() {
    this.setState({ age: this.state.age + 1});
    this.props.increment();
  },
  render: function() {
    console.log('in child');
    return (
      <div id="child">
        <p>parent age: {this.state.age}</p>
        <button onClick={this.increment}>a year ago</button>
      </div>
    );
  }
});

React.render(
  <Parent />,
  document.getElementById('parent')
);

最初に親の年齢を入力して、a year ago ボタンを押すと、子供の年齢も親の年齢も 1 インクリメントするやつ。

ところでこのアプリ、親の年齢を入力しないと、親も子も 0 歳からスタートする。0 歳の親ってどういうこっちゃ。
けど細かいところは気にしない。

サンプルの重要なところは React の render で parent から child を作ってるところ。 つまり

- parent
   - child

のような構造を作っている。

一番下の a year ago ボタンをクリックすると、console には

 in parent
 in child

と出力される。
これは先に親の render が呼ばれて、その後に子の render が呼ばれることを意味している。

で、何がいいたい?

どうしてこういう順序になるのか、わからないです。
直感的には setState が先に呼ばれた方、でも良い気もするので。

こういうもの、という理解でいいのだろうか?
「親→子」という流れは理解しやすんだけど、それで無理やり理解した気になるのもなんだかなー。

「理性の限界」を読んだ

イントロ

理性の限界――不可能性・不確定性・不完全性 (講談社現代新書)

理性の限界――不可能性・不確定性・不完全性 (講談社現代新書)

人間の「理性の限界」について、色々な角度からディスカッション形式で話が進んでいく。
途中「ハイゼンベルク不確定性原理」や「ラプラスの悪魔」といった物理学の面白い話がでてきて、大学が物理学科の私は思わずニンマリ。

本を知ったきっかけ

最近ニコニコ動画 3つのボタンでカービィボウル という動画を見てしまった。
動画は勿論面白いんだけど、作者がすごい人だなと思っていて、彼がオススメするならって記憶に残っていた。

個人的な動機

読んでみようと思った理由はそれだけじゃない。

より合理的で、理性的に判断できるようになりたかったから、というのもある。
合理的な判断とはナンゾヤ、という話もあるけど、「今、自分は落ち着いていて、冷静に考えているか」ってこと。

常に意識したいことの一つです。
そういう基準や客観性をどうやって評価するか、維持するか、ということに興味があった。

背景

自分が「こっちのほうが良いんじゃないかなぁ」と考えることが、相手は「あっちのほうが良い」と思うことが、往々にしてある。
日常生活レベルの話なら「個性」と言い換えることができて、結構面白い。

けど合理的な判断を求められる仕事でも起こりえて、こちらは途端に厄介になる。

時に感情的な判断をしがちで、環境によっては到底理性的とは思えない判断をすることがある。不思議なことに。

でも仕事とは限らずに、自分もそういうことはあるし、他人を観察してそう思えることもある。

振り返ると「なんであんなことを」と思うけど、実際そういう時は客観性を失っていて「感情的になっていること」自体に気づかなかったり、あるいは気づかない振りをするものだと思う。

動機の動機

自分は冷静だと考えているけど、相手が感情的になっている場合。人によっては説得しようとすることが逆効果なこともある。

自分の説明が下手なのかな?と思ったけど、なんだか納得してくれているようには見える。でも相手の主張は変わらない。
そんな不思議な体験をした。

きっと自分が感情的になっている事に気づいていないからだろう、と思われる。

そんな時に「相手が自発的に自身の客観性を認識するためには、どうすればよいか」「自分は冷静になっているつもりで、実は論理的な説明ができていないのか?」という悩みを抱える事になった。
難しい。

本を読んで

「理性的とはナンゾヤ」という話にあまり触れず、相対的な議論が展開される。
「◯◯という方法は××という方法より、こういう理由で理性的だ」という感じ。
だから客観性をどうやって評価するか、という疑問の答えにはならなかったけど、ヒントにはなったし、単純に読み物として面白かった。

「神の非存在論」はなるほどなーという感じ。「神は理性では認識不可能」って結論は興味深い。
認識不可能だけど、神様という概念をほとんどの人間が理解していたり、或いは創りだしたりする。

神様 SUGOI
そんな神様を創れる人間はもっと SUGOI

「運と実力の間」を読んだ

イントロ

年末に読んだ本のうちの一つ。暇だったからブログに書いてみる
私の最近のマイブームが poker で、poker といえば木原直哉氏。
というわけで氏が書いた本を読んでみるか、と極めて安直な理由。

感想

面白い。今の自分が持ってない知見もあった。
内容は木原氏の生い立ちと WSOP 出場に至るまでの経緯、出場経験について語られていた。

元々興味を持って購入したので、その分楽しめた。

エンジニアリングとギャンブルについて

ここからは本の感想ではなく、極めて個人的な意見。

(観察対象があまりに少なすぎると思うけど) プログラムが書くことが得意なプログラマは(私を含む)、こういうギャンブル系が苦手な人が多いと思っている、という話。

言い換えると「確率で考え、実際に行動すること」が不得意な人が多い、ということ。
わかっていても行動できなかったり、そもそもそういう方面に興味が沸かない方が多い。

どうしてそう思うんだろうなーと今まで疑問だったけど、「運と実力の間」を読んでその理由っぽいものを思いついたので書き残します。

勝つまでのプロセスが違う

「勝つまでのプロセスが違う」というのがその有力候補。

当然「勝つ」とは何か、という定義は難しい。人によって違うから。

poker 側の「勝ち」は簡単に言うと「お金が増えること」
プログラム側は仮に、プログラムがどこかでエラーを吐いていて、それをデバッグし、直す作業を考えてみる。
直せたら「勝ち」としよう。直せなくて諦めたら「負け」

poker の話

本の中では以下のように述べられている。

  • 負けている時にやり方を変えるな

曰く、負けている時にやり方を変えたら、何が原因で負けているか、わからなくなるからだ。と。
私の拙い経験としても賛成意見。

これは運が絡むゲームにはよくある考え方だと思う。

プログラミングの話

じゃあプログラミングではどうだろうか。

状況にもよるだろうけど

  • デバッガを起動し、原因を突き止める
  • 設定ファイルを見直す
  • ログを追う

等が思い浮かぶ。

共通しているのは、プログラム(またはそれに関連する物)を少しだけ書き換えて、何度も実行する、ということ。 行った修正でエラーが出なくなるか、ちゃんと確かめないといけない。
どんなときも根気強く前に進み、心が折れるのが一番マズい。(経験上)

逆に言えば、プログラムを書き換えないで再実行する意味は無い。(時間に関連するバグなど例外はあるが)
何となく rspec をもう一回叩いてみるか... は時間の無駄だと思う。失敗する時は原因を潰さない限り、何度も失敗する。
だからどこに原因があるか色々考えたり、調べたりして、あらゆる方法を試すのだ。

何が言いたいかというと、負けている時には色々試すのが、プログラミングをする上で良いやり方だと思うってこと。

前提の話

プログラミングと poker では勝てるようになるまでのプロセスが違う、という意見なんだけど、もう一つ重要な考え方がある。

それは人は自身の向き不向きを自覚していて、自然に向いているものに興味を持つ、という考え方。方位磁石が自然と北を向くような感じ。

特に子供の頃はそのような傾向が強く見られるように思える。 これはもう一つ示唆するところがあって、それは「そう簡単に自分の向き不向きが変わらない」という事。

まとめの話

  • プログラミングと poker は勝つ方法が違う
  • 人は自分に合った興味を持つ
  • しかもそれは中々変わらない

という意見から、プログラミングが好きな人はギャンブルに興味を持ちにくく、しかも不得意である可能性が高い。と思うようになった。まる。
言語化すると長い...

で、何が言いたい?

グダグダ書きましたが、全部極論です。
視野が狭いとこうなるので、気をつけねば。

株や投資などには、個人の自己責任でお楽しみください。

binding.pry 使ってる時に、一気にループを抜ける方法

イントロ

みんな大好き pry の話
何回も binding.pry が呼ばれる環境下で、毎回 exit を入力するのめんどいよね、という話。

サンプル

class Test
  def aaa
    binding.pry
    'aaa' 
  end
end
describe '#aaa' do
   it '1' do
     expect(test.new.aaa).to eq('1')
   end

   it '2' do
     expect(test.new.aaa).to eq('2')
   end 
end

この場合 binding.pry は 2 回、処理を停止する。

    4: def aaa
 => 5:   binding.pry
    6:   'aaa'
    7: end

exit 叩く。

    4: def aaa
 => 5:   binding.pry
    6:   'aaa'
    7: end

もう一回 exit 叩く。

あるあるですね。
こういう時、一回でプロンプトまで戻りたいわけです。

解決法

!!! or exit! or exit-program を入力する

    4: def aaa
 => 5:   binding.pry
    6:   'aaa'
    7: end

>> !!!

% # 即座に終了した!

知らなかった...

rails でカスタム generator 作る話

これは何か

好きな generator を作る話。ここで述べる generator とは

rails generator hoge

のような rails generate コマンドのこと。 知識整理のために書いてみる

雛形作成

rails には generator を生成する generator があって、以下の様なコマンドを叩けば良い。

rails generate generator <generator名>

試しに test という generator を作ってみる。

% rails generate generator test                                                                                               
      create  lib/generators/test
      create  lib/generators/test/test_generator.rb
      create  lib/generators/test/USAGE
      create  lib/generators/test/templates
      invoke  test_unit
      create    test/lib/generators/test_generator_test.rb

ファイルが作られた。 作られた generator は以下のように実行できる

rails generate <generator名> (<引数1>, <引数2>, ...)

引数は generator 側の定義で制御する

generator 作成

生成されたファイル

先ほどの rails generate generator test コマンドで生成されたファイルを順番に見ていく

lib/generators/test/test_generator.rb

class TestGenerator < Rails::Generators::NamedBase
  source_root File.expand_path('../templates', __FILE__)
end

メインの generator ファイル。 Rails::Generators::NamedBase を継承した {generator名}Generator というクラスが生成されている。

ここにメソッドを定義していく。

具体例

TestGenerator に何も生産しないメソッドを作る。

class TestGenerator < Rails::Generators::NamedBase
  source_root File.expand_path('../templates', __FILE__)

  def ore
    puts "俺が"
  end

  def dhh
    puts "DHH、こと"
  end

  def da
    puts "David Heinemeier Hanssonだ!"
  end
end

rails generate コマンドを叩くと TestGenerator クラスのインスタンスメソッド上から全部実行される

% rails generate test test # test という引数を仮に与えている
俺が
DHH、こと
David Heinemeier Hanssonだ!

ご覧のとおり上から実行されていることがわかる。
なのでファイルを作りたかったら、そういうメソッドを書けばよい。
ディレクトリを掘りたかったら、そういうメソッドを書けば良い。簡単

lib/generators/test/USAGE

中身は以下の通り。

Description:
    Explain the generator

Example:
    rails generate test Thing

    This will create:
        what/will/it/create

ヘルプコマンドの下の方にそのまま使われる

% rails generate test -h
Usage:
  rails generate test NAME [options]

Options:
  [--skip-namespace], [--no-skip-namespace]  # Skip namespace (affects only isolated applications)

Runtime options:
  -f, [--force]                    # Overwrite files that already exist
  -p, [--pretend], [--no-pretend]  # Run but do not make any changes
  -q, [--quiet], [--no-quiet]      # Suppress status output
  -s, [--skip], [--no-skip]        # Skip files that already exist

Description:
    Explain the generator

Example:
    rails generate test Thing

    This will create:
        what/will/it/create

lib/generators/test/templates/

ディレクトリ。現在は空。 erb テンプレートファイルを置いておく用のディレクトリ

test/lib/generators/test_generator_test.rb

require 'test_helper'
require 'generators/test/test_generator'

class TestGeneratorTest < Rails::Generators::TestCase
  tests TestGenerator
  destination Rails.root.join('tmp/generators')
  setup :prepare_destination

  # test "generator runs without errors" do
  #   assert_nothing_raised do
  #     run_generator ["arguments"]
  #   end
  # end
end

テストの雛形。rspec 使ってれば rspec で出してくれる。 ...と思いきや、rspec の場合はスケルトン作成に失敗する。
(失敗の原因を詳しく追ってない。本当は作られるはず... なのかな)

継承クラス

親クラスは若干注意が必要なので、ここでやりたいことに合わせて分岐する

Rails::Generators::NamedBase

default だとこっち。 rails generate コマンドの引数として、一つ何かを受け取ることが必須のクラス

cf. Rails::Generators::NamedBase

% rails generate controller Users xxxx のような馴染みのある generator を作りたい場合はこちらで良い。

Rails::Generators::Base

NamedBase とは異なり rails generate コマンドの引数を自由に定義できるクラス。 Rails::Generators::NamedBaseRails::Generators::Base を継承している。

こちらのほうが柔軟性があるが、特別な用途がない限りは使わなくても良いかと思います。

便利メソッド

generator なので、テンプレートからファイル生成したり、ディレクトリを掘ったりする用途が主である。
rails はすごいから、そういう簡単なものは既にメソッドが用意してある。

これらのメソッドは全て Thor::Actions に定義されているので、そっちを見たほうがいい。 というのも Rails::Generators::BaseThor::Actions を include しているから。

もっと言うと Rails::Generators::BaseThor::Group を継承して作られているので、rails の generator は大体 thor でできてると言って良い。

したがって困ったときは thor で検索すると良い

以下は便利メソッドの内、いくつかを紹介

create_file

https://github.com/erikhuda/thor/blob/067f6638f95bd000b0a92cfb45b668bca5b0efe3/lib/thor/actions/create_file.rb#L22
file を生成する

directory

https://github.com/erikhuda/thor/blob/067f6638f95bd000b0a92cfb45b668bca5b0efe3/lib/thor/actions/directory.rb#L49

directory を copy する

template

https://github.com/erikhuda/thor/blob/067f6638f95bd000b0a92cfb45b668bca5b0efe3/lib/thor/actions/file_manipulation.rb#L108

ERB テンプレートを使って、ファイルを生成

具体例

class TestGenerator < Rails::Generators::NamedBase
  source_root File.expand_path('../templates', __FILE__)

  def create_empty_file
    create_file "aaaa.txt"
  end
end
% rails g test arg1
      create  aaaa.txt

見たことある出力やー

% rails g test arg1
   identical  Test.txt

もう一回やると、またまた見たことある出力に。
大体こんな感じでノリで generator 作れてしまいます。すごい簡単です。

まとめ

  • 継承するクラスによって、generator に渡せる引数が変わる
    • Rails::Generators::NamedBase は 1 つの引数を受け取る
    • Rails::Generators::Base は自在に定義可能
  • rails generator の内部は殆ど thor
    • 詰まったら thor で調べると良い
    • thor を一度触ったことがある人は知見を十分に活かせる
      • thor はとても素直な実装になっているから
      • thor の spec はとてもわかり易いので、読むだけで理解できる
  • hook_for は謎が多いので、使う時は若干注意
  • thor は「トール」と読むらしい

参考