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 は「トール」と読むらしい

参考

CircleCI から deploy させる話

イントロ

CircleCI からテストが通ったら capistrano 使って deploy させたい。
けど、対象サーバーは IP 制限がかかっている。CircleCI の IP なんてコロコロ変わるし、どうしたらいいんや... って話。

これができると、PR を github ボタンでマージしたら、自動(当然テストも通した後)で deploy されるので、非常に嬉しい。

方針

before deploy

  • awscli を使い security group の inbound に自分自身の ip を付与
    • CircleCI の内部から IP address を割り出して、コマンドラインで一時的にinboud に追加させる

deploy

  • コマンド(bundle exec cap deploy)を実行

after deploy

  • awscli を使い security group から ip を取り外す
    • before deploy の逆をやればいい

前準備

IAM user や security group の名前は適当に決めてます。
やること多いです。

  1. IAM user circle_ci を作成
    • ec2 に関する権限を全て付与(今回に限って言えば、全て付与しなくても動くはず)
    • 詳細は後述
  2. CircleCI 用の security group を作成する
    • 名前は circle_ci
    • 普段は inbound の欄は空
    • 一時的に CircleCI の IP が許可される用の security group
  3. deploy したいサーバーに security group circle_ci を attach する
  4. AWS環境変数を CircleCI の設定画面上で予め登録する
    • IAM user circle_ci の credential を登録
  5. deploy 対象のサーバーにログインするための秘密鍵を CircleCI の設定画面上で登録する

実装

CircleCIからCapistranoを利用してAWS(EC2)にデプロイする - Qiita に大体書いてある。

deploy するコマンドとして、deploy.sh みたいなシェルスクリプトを作って、それを deployment で実行させればいい。

参考記事とは少し違っているけど、自分が作成した deploy.sh は以下のようになった。

#!/bin/sh
set -ex

SECURITY_GROUP_NAME="circle_ci"
IP=`curl -f -s ifconfig.me`

trap "aws ec2 revoke-security-group-ingress --group-name ${SECURITY_GROUP_NAME} --protocol tcp --port 22 --cidr ${IP}/32" 0 1 2 3 15
aws ec2 authorize-security-group-ingress --group-name ${SECURITY_GROUP_NAME} --protocol tcp --port 22 --cidr ${IP}/32
bundle exec cap prod deploy

追記

コメントを受け、修正しました

Before

IP=`curl-s ifconfig.me`

After

IP=`curl -f -s ifconfig.me`

-f は man で見てみると、以下のような意味だそうです。
確かエラーになった時の事を考えると、入れておいたほうが良いかもですね。

       -f, --fail
              (HTTP) Fail silently (no output at all) on server errors. This is mostly done to better  enable  scripts  etc  to  better  deal  with  failed
              attempts. In normal cases when an HTTP server fails to deliver a document, it returns an HTML document stating so (which often also describes
              why and more). This flag will prevent curl from outputting that and return error 22.

              This method is not fail-safe and there are occasions where non-successful response codes will slip through, especially when authentication is
              involved (response codes 401 and 407).

ポイント

set -ex

-x は毎度おなじみ debug 用途。実行したコマンドを標準出力するオプション。
-e はコマンドに失敗したら、即時にスクリプトを終了する。一行づつ && で挟んでいるイメージ。
コマンドが 3 つ書かれているけど、どれか一つでも失敗したら、deploy せず即時失敗扱いで良いので、このようにしている。

trap コマンド

何かしら異常があった場合、変更を加えた Security Group を元に戻さないといけない。
でないと、ある IP から接続可能な状態になってしまい、セキュリティ上良くないから。

つまり、deploy に失敗しようが成功しようが、revoke-security-group-ingress は実行させたい、ということ。
そのために trap コマンドでシグナルを補足しています。

0 1 2 3 15 を書いておけば、大抵のシグナルには反応してコマンドが発動する。

肝は最終的な exit コードは trap コマンドの実行結果ではなく、trap 以前に実行した最後のコマンドであること。
つまり bundle exec cap prod deploy で exit コード 1 が返ってきた場合、trap が発動して、revoke され、exit コード 1 でシェルスクリプトが終了する。
(CircleCI では exit コード 1 ならば、build 失敗扱いになる)

circle.yml

circle.yml は以下のようになる。
pip install しないと awscli のバージョンが古く、コマンド実行に失敗する。

machine:
  timezone: Asia/Tokyo
  ruby:
    version: 2.1.3
dependencies:
  pre:
    - sudo pip install awscli
deployment:
  master:
    branch: master
    commands:
      - ./deploy.sh

まとめ

ここまで準備して、ようやく master へのマージ + テスト通ったら deploy が自動化されます。

とはいえ、これでも revoke-security-group-ingress に失敗する可能性があり、CircleCI の IP address が許可されたままになる可能性があります。が、そのリスクには目を瞑っています。
revoke-security-group-ingress に失敗したら build にも失敗するはずなので、すぐに調査すればいい話だから。
circle_ci の inbound を削除すれば元通りなので、復旧作業も楽勝。と思ってる。

なお、Circles EC2 IP addresses and AWS security group - CircleCI なんてのがあって、この情報をうまく使えば deploy できるんや... って思いがちだけど、これは孔明の罠なので要注意。

できてないこと

  • pip install awscli をキャッシュさせること
    • 頑張れば出来そうだけど、まだやってない

参照