scramble cadenza

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

emacs in terminal に移行しようと思ったけど、GUI の emacs に戻った話

イントロ

今まで GUIemacs を使ってたけど、terminal の中で emacs を開くようにして、暫く使ってみた。

要するに emacs -nw です。

けど色々難しいことがあって、元に戻ったという話。
苦労したところと、経緯を残します

環境

  • emacs 24.4
  • OSX 10.10.4
  • iterm2 2.1.1

なんで terminal の中で開きたいの?

カッコイイからです!
理由はなんとなく。

やったこと

特定のキーバインドemacs に渡らない

ターミナル上のEmacsで本来使えない"Ctrlキー+何か"を使う方法 - すぎゃーんメモ を見て解決。

これで C-:, C-;, C-y 等よく使うキーバインドを機能させることができた。

コピペができない

emacskill-ringclipboard の共有ができてなくて困った。
emacs でコピーした時に、pbcopy を呼ぶことで、無理やり同期させるような elisp を書いた。

Emacs と Mac のクリップボードを共有する と似たような感じ。

-nw にした時に同期できてないことに気づいたけど、特に GUI では意識してなかったポイント。

tmux prefix key と emacsキーバインドが競合する

私は tmux の prefix key として C-q を使っていたけど、emacsquoted-insert と競合する。
悩んだ末 quoted-insert の prefix を変更した。

emacsclient の起動

# emacs -nw
function emacs () {
  EMACS_CLIENT='/Applications/Emacs.app/Contents/MacOS/bin/emacsclient'
  EMACS='/Applications/Emacs.app/Contents/MacOS/Emacs'

  if ! pgrep Emacs; then
    $EMACS --daemon
  fi

  $EMACS_CLIENT -nw -q
}

function ekill () {
  pkill Emacs
}

この辺りはまだ泥臭くやってます。 emacs が立ち上がってなかったら、 --daemon 付きで起動し、その後 emacsclient を立ち上げる、というスクリプトを使って凌いでいる。

terminal の中で起動してよかった事

太字は特に良かった(悪かった)ところ

  • カッコ良くなった
  • emacsclient の起動がかなり早くなった
    • パッと開いて、パッと閉じる事が可能になって気持ちいい
  • いい感じに emacs で完結するようになった
    • 操作性うp
    • shell-command を以前より多用するようになった
      • だけどまだまだ shell 周りは課題が多い

よくなかったこと

  • 設定で無茶をしている
    • 特に「ターミナルから特定のキーバインドが渡らない」現象を解決するのに色々苦労した
    • この環境を再現するために、iterm2 の設定をいじらないといけなくて微妙な感じがする
  • コマンド + TAB で emacs に移れない
    • emacs で完結している限りは困らないけど、時々困る(かも)
  • color-theme が好みなやつがない
    • 自分の中でコレ!っていうのが無かった
    • GUI の時と、terminal の時で、色合いが結構違う
  • elscreen-server が地味に機能してない気がする件
    • 別の emacsclient を立ち上げても tab が共有されてない
    • 色々なところで emacsclient 立ち上げると頭使うから、こういう使い方をしたくなかった

結局どうなったか

GUI に戻りました...

自分は zshemacs を両方よく使っていたのと、zsh in emacs をイメージ通りに使えなかったのが大きかったです。
なので iterm2 (zsh 使う) と emacs スイッチを コマンド + TAB で行えないのが辛かった。

これがうまく機能しないと、効率が落ちてしまいます。

けどいつかは使いこなしたいこの環境、またいつか挑戦したいと思います。

delayed_job まとめ

イントロ

collectiveidea/delayed_job

なんとなく使ってきて、なんとなくわかってるつもりの delayed_job について、改めてまとめてみたもの。
休日暇だったのでソースコード読んでみたり、step 実行したりして、何処に何が書いてあるかをざっくりとまとめたものです。

環境

  • rails (4.2.1)
  • delayed_job (4.0.6)
  • delayed_job_active_record (4.0.3)
  • daemons (1.2.3)

導入

  • Gemfile
gem "delayed_job_active_record"
  • ActiveJob

ActiveJob のバックエンドとして、delayed_job を指定する

config.active_job.queue_adapter = :delayed_job
  • command
rails generate delayed_job:active_record
rake db:migrate

基本的な仕組み

Queue

  • queue は database に保存される
  • テーブル名は delayed_jobs
  • rails generate delayed_job:active_record で自動生成される

serialize

  • ruby の object を yaml 形式で dump し、DB に保存する
    • handler カラムに保存する
  • 取り出す時に dump された yaml を load し、ruby のオブジェクトに戻す

worker

  • worker が delayed_jobs テーブルを数秒おきにポーリングする
  • レコードがあったら、yaml をロードして ruby のオブジェクトに戻し job を実行する
    • job を実行し終わったらレコードは削除される
  • worker はポーリングを続ける、の繰り返し

worker の詳細

要の worker について詳しく見てみる。

woker の起動方法

  • rake jobs:work
  • bin/delayed_job start

の二通り

共通してやっていること

両者の違いは?

  • rake jobs:work は worker を起動するだけ
    • worker プロセスは一つだけ
  • bin/delayed_job は 起動オプションを受け取り、柔軟な worker プロセスの設定ができる
    • daemons gem がないと 起動不可能

なので

  • 開発環境で軽く使う場合は rake jobs:work(もしくは rake jobs:workoff)
  • 本番環境で運用する場合は、オプションを指定して bin/delayed_job

のようなイメージ

worker の option

Delayed::Worker クラスのアクセサとして定義する。 README に書いてあるとおり、rails なら config/initializers の下に適当に書いておけばいい。

  • default_queue_name
    • queue の名前。queue カラムに入る。
  • destroy_failed_jobs
    • true だと、失敗した job を DB から削除する(デフォルト true)
  • max_run_time
    • job の timeout を設定(デフォルトは4時間)
    • この時間より長いジョブは Timeout::Error を継承した DelayedJob::WorkerTimeout をraise する
  • raise_signal_exceptions

bin/delayed_job のオプション

https://github.com/collectiveidea/delayed_job/blob/v4.0.6/lib/delayed/command.rb#L26-L71

書いてあるとおり。重要そうなものをピックアップ

  • --number_of_workers
    • 起動する worker 数
    • ただし --identifier option との併用は不可(identifier 指定とプロセスの自動連番が衝突するから)
    • stop と start の worker 数が等しくないと、bin/delayed_job stop で stop できない
  • --pid-dir
    • pid ファイルのディレクトリを指定
  • --log-dir
    • log ファイルのディレクトリを指定
  • --identifier
    • プロセス名を指定できる。指定すると delayed_job.#{@options[:identifier]} というプロセス名になる
  • --prefix
    • プロセス名を指定できる。指定すると File.join(options[:prefix], process_name) というプロセス名になる

bin/delayed_job stop で何が起こるか

起動中の delayed_job プロセスには TERM が送られる。(daemons gem の仕事) https://github.com/thuehlinger/daemons/blob/v1.2.3/lib/daemons/application.rb#L374

発生する例外

起動中のプロセスが TERM, INT を受け取った時、job の挙動はDelayed::Worker.raise_signal_exceptions の値で変わる

  • false
    • TERM を受け取った場合
      • 例外は出ない
    • INT を受け取った場合
      • 例外は出ない
  • :term
    • TERM を受け取った場合
      • SignalException を raise
    • INT を受け取った場合
      • 例外は出ない
  • true
    • TERM を受け取った場合
      • SignalException を raise
    • INT を受け取った場合
      • SignalException を raise

コードを見たほうが腑に落ちるかも https://github.com/collectiveidea/delayed_job/blob/v4.0.6/lib/delayed/worker.rb#L132-L142

例外が出た後に何が起こるか

callback について

  • error のフックとしては :before, :after が空席
  • 書き方は以下の様な感じ

    Delayed::Worker.lifecycle.before(:error) do |worker, job| # puts worker # puts job end

  • ブロック引数に run_callbacks(:error, self, job) の第二引数以降、即ち Delayed::Worker, Delayed::Job インスタンスを受け取れる

  • ただし、callback を定義する場所に注意
    • 実質 config/initializer しかない?
    • アプリ側で動的に callback を定義しても、既に起動中の worker には伝わらない
    • ワーカーに依存しない処理が書ける場所

Capistrano

https://github.com/collectiveidea/delayed_job/blob/v4.0.6/lib/delayed/recipes.rb

  • 各 host で bin/delayed_job コマンド + オプションを実行する

だけ。

つまり bin/delayed_job の章で述べたとおり、capistrano で deploy した時、実行中の job は中断し DB に戻される。 そして新しいコードを load した状態で、再度 job が実行される、という流れになる。

細かすぎて伝わらない delayed_job のネタ

実は Delayed::Job というクラスはない

正確には collectiveidea/delayed_job に定義されていない、ということ。

実体は collectiveidea/delayed_job_active_record になっていて、このクラスが include Delayed::Backend::Base している

>> Delayed::Job
=> Delayed::Backend::ActiveRecord::Job

なので Delayed::Job インスタンスメソッドを調べる時は delayed_job_active_recordDelayed::Backend::Base を見ると良い。

find_available メソッドの謎

Delayed::Backend::Base.reserve に定義されている find_available メソッド、よく調べてみたけど、実は何処にも定義されていない。なので NoMethodError が発生するはず。 https://github.com/collectiveidea/delayed_job/blob/v4.0.6/lib/delayed/backend/base.rb#L43

けど上記で述べたとおり、実体は collectiveidea/delayed_job_active_record のオブジェクトになっていて、https://github.com/collectiveidea/delayed_job_active_record/blob/v4.0.6/lib/delayed/backend/active_record.rb#L43reserve を上書きしているから、実質問題は起きていない。

delayed_job バックエンドによっては find_available を上書きしているものもあるらしい

まとめ

  • delayed_job の仕組みについて調べました
    • DB に queue を溜めて、worker がそれを取り出して実行する
  • worker の起動方法は2種類ある
    • rakebin/delayed_job の2つ
  • worker の設定は Delayed::Worker クラスのアクセサとして定義
    • worker の設定は沢山あるんやで
  • worker を stop させると、worker が既に動いていても DB に一旦戻されて再実行を行う仕組みになっている
    • デフォルト

authlogic で、ユーザーの password を編集した直後に強制ログアウトされる件

イントロ

authlogic でハマったネタ第二弾。
まとめると以下の様な感じ。

  1. authlogic でログインユーザーを作成
    • よくある 名前と password で認証するユーザー
  2. ログインユーザーを編集する画面を作る
    • password を編集可能にする
  3. ログインユーザーの password を更新すると、何故か強制的にログアウト状態になる
    • ログインを要求する url に強制 redirect される

解決法

class User < ActiveRecord::Base
  acts_as_authentic do |c|
    c.session_class = UserSession
  end
end

この解決方法はあくまで一例。

原因

  1. authlogic のログイン状態は、DB に保存されている persistence_tokensession[:user_credentials] の一致により判定される。
    • user_credentials という session の key はログイン用のモデルによって変更の可能性あり
  2. authlogic で password を更新すると、強制的に persistence_token も再生成される
    • この時 session の値も再生成された文字列に更新しなければならない
  3. 設定が甘くて、password を更新した時に session の値が更新されなかったため、ログイン状態が維持されなかった
    • authlogic がログイン画面にリダイレクト

のような感じ。

肝心の session の値も再生性された文字列で上書きしなければならない 設定は以下のメソッドで行っている。
恐らくこの値は true でなければいけないが、false になっているとこんなおかしな状況になる。

https://github.com/binarylogic/authlogic/blob/8e2acfe303ea68013ad5afa5b85c5569837a290d/lib/authlogic/acts_as_authentic/session_maintenance.rb#L87-L89

def update_sessions?
  !skip_session_maintenance && session_class && session_class.activated? && self.class.maintain_sessions == true && !session_ids.blank? && persistence_token_changed?
end

今回私が遭遇したケースでは session_class が何故か nil を返すようになっていたので、session が update されず、ログアウト状態になってしまっていた。

session_class について

session_class とは

  • Authlogic::Session::Base を継承した session クラスを返すメソッド
  • 普通は自動で判定される
    • Useracts_as_authentic なら UserSession という風に

ので、普段は意識しなくていいはず。

例えば Admin モデルを作って、Admin 専用の管理画面を作ろう、となった時に Session クラスを AdministratorSession としてしまうと、session_class を指定する必要がある。

設定を忘れるとこのような目にある。

まとめ

Authlogic::ActsAsAuthentic::SessionMaintenance::Methods#update_session? メソッドを調べればよろし

authlogic で blank の password で update 出来てしまう問題

イントロ

情報が古いけど

ruby on rails - changing password with authlogic - validation not catching blank inputs - Stack Overflow
と同じ現象。

改めてまとめると、以下の様な状態。

  1. authlogic でログインユーザーを作成
    • ログインユーザーは password を form に入力する
  2. ログインユーザーを編集する画面を作る
    • 新規作成ではないので注意
    • password を編集可能にする
  3. ログインユーザーを編集するとき、パスワードを空にして保存できてしまう

解決法

class User < ActiveRecord::Base
  acts_as_authentic do |c|
    c.ignore_password_blank = false
  end
end

とする。

よく見ると

https://github.com/binarylogic/authlogic/blob/23751e3eaba4c2b09751505079d7264fd1cc8b72/lib/authlogic/acts_as_authentic/password.rb#L44-L54

のコメントに書いてある。

前提条件も含めて説明すると...

  • 編集画面において、password を最初から入力済みにすることはできない
    • DB に保存されている password を復号することはできないため(暗号化方式によるが)
    • つまり編集画面を開いた直後はどうしても空になってしまう
  • 状況として「 password は変えたくないけど、ページを遷移してしまう」ケースが存在するだろう
    • どういうケースかは明記されてないけど、例えば間違って「更新」ボタンを押してしまったりするとか?
    • そうすると、空のパスワードが保存されてしまい、セキュリティ的に甘くなる
      • そういうケースは避けたい
      • オプションで変更できる

ということらしい。(多分)

Capybara::Poltergeist で remote debug が動かない件

イントロ

タイトルの通り。Capybara::Poltergest の目玉機能である remote debugging がうまく動作しなくて、色々試行錯誤した話。

https://github.com/teampoltergeist/poltergeist#remote-debugging-experimental

環境

  • OSX 10.10.3
  • phantomjs 2.2.0
  • poltergeist 1.6.0
  • chromium 45.0.2411.0

remote debugging とは

poltergeist の README に書いてあるとおり

Capybara.register_driver :poltergeist do |app|
  Capybara::Poltergeist::Driver.new(app, js_errors: false, inspector: true)
end

@session = Capybara::Session.new(:poltergeist)

@session.visit url
@session.driver.debug

のような感じで、driver.debug のタイミングでブラウザを起動し、debug できる機能らしい。

使い方は簡単で

  • Capybara::Poltergeist::Driver.newinspector: true オプションを渡す
  • driver.debug を呼ぶ

だけで良い。

苦戦した所

1. そもそもブラウザ起動しない

# @session.driver.debug を使った spec を実行させる
bundle exec rspec 
...

Poltergeist execution paused. Press enter to continue.
The file //localhost:9664 does not exist.

てっきり chrome か何かが自動で起動するんだろうなぁと思ってたが、file //localhost:9664 がないと言われて怒られてしまう。

解決法

微妙なアプローチだけど、chromium を install して回避してみた。

brew install Caskroom/cask/chromium
ln -s /opt/homebrew-cask/Caskroom/chromium/latest/chrome-mac/Chromium.app/Contents/MacOS/Chromium /usr/local/bin/chromium

として、chromium コマンドをむりやり PATH に追加。
これだけで良い。

というのも、ブラウザ起動の仕組みは単純で、 chromium chromium-browser google-chrome のいずれかのコマンドが PATH に通ってれば実行される、というものだから。

https://github.com/teampoltergeist/poltergeist/blob/9b73d618688c5cbdeaea600a193a514c58a754e6/lib/capybara/poltergeist/inspector.rb#L3

chromium http://www.google.co.jp

と叩いて chromium が起動して google トップページが表示されるならOKなはず。

google-chrome コマンドがどうやら linux 環境でのコマンドというのと、macgoogle-chrome コマンドに相当するものを見つけられなかったので、こんな感じになりました。

Check failed: feature. と言われて起動できない。

ところが上記をやっても解決しない。
chromium が動こうとするのだが、以下の様なエラーが出て、chromium が起動できない。

Poltergeist execution paused. Press enter to continue.
[86324:1299:0524/032749:ERROR:component_loader.cc(154)] Failed to parse extension manifest.
[86324:1299:0524/032749:ERROR:component_loader.cc(154)] Failed to parse extension manifest.
[86324:1299:0524/032749:ERROR:component_loader.cc(154)] Failed to parse extension manifest.
[86324:1299:0524/032749:ERROR:component_loader.cc(154)] Failed to parse extension manifest.
[86324:1299:0524/032749:ERROR:component_loader.cc(154)] Failed to parse extension manifest.
[86324:1299:0524/032749:ERROR:component_loader.cc(154)] Failed to parse extension manifest.
[86324:1299:0524/032749:ERROR:component_loader.cc(154)] Failed to parse extension manifest.
[86324:1299:0524/032749:ERROR:component_loader.cc(154)] Failed to parse extension manifest.
[86324:1299:0524/032749:ERROR:component_loader.cc(154)] Failed to parse extension manifest.
[86324:1299:0524/032749:FATAL:feature_provider.cc(75)] Check failed: feature. FeatureProvider 'behavior' does not contain Feature 'whitelisted_for_incognito'

色々調べてみたがよくわからない。
chromiumソースコードにそれっぽいものを発見して、右往左往したけど挫折。

解決法

これは 予め chromium を起動した状態で、@session.driver.debug を実行する と回避できる。

要するに chromium 起動させて rspec 叩くわけです。

すると

f:id:mgi:20150524035806p:plain

のように file:///localhost:9664 を開こうとしているから、file:/// を消してエンター。

f:id:mgi:20150524035816p:plain
URL があらわれた。URL をクリック。

f:id:mgi:20150524035831p:plain

無事コンソール画面へ

まとめ

溢れんばかりのバッドノウハウ感。
poltergiest の experimental な feature なので、参考になれば。

google-chrome 使うことができれば、妙なエラーはでない?のかもしれない。

みんなどうやってるの...

bootstrap-generators で bootstrap 導入後、一部の flash が使えなくなる現象

イントロ

bootstrap 導入すると、flash がちゃんと動かなくなって戸惑うやつ。
decioferreira/bootstrap-generators で生成する flash 部分にミスがあるのが原因(多分)

具体的には flash[:alert] 時でも、緑枠の flash メッセージになってしまう現象。
ちゃんと考えると直せるんだけど、思い出すのが面倒なので残す。

環境

  • rails 4.2.1
  • sass-rails 5.0.3
  • bootstrap-sass 3.3.4
  • bootstrap-generators 3.3.1

各 gem の install 方法なども省略。 README.md の通りです。

bootstrap-generators について

意外とスター数が少ないので補足。

railstwitter-bootstrap 導入しようとすると、有名なのは以下の2つ。

bootstrap-sass のほうが簡単に導入できるが、generator が付いていないので、view 等が手作りになってしまう。
一方で twitter-bootstrap-rails は導入が面倒だが、generator が便利で、一回インストールできてしまえば楽になってくる、という感じ。

正に一長一短な2つの gem なわけですが、実は boostrap の generator は gem になって切りだされていて、bootstrap-sass と合わせることで「install 簡単」と「generator」のイイトコロ取りをできるわけです。

その generator が decioferreira/bootstrap-generators です。

rails generate bootstrap:install

これで bootstrap のあの黒いヘッダー(やそれ以外も)が生成されます。
haml/slim/erb に対応している。

直し方

app/views/layouts/application.html.erb (bootstrap 導入後)

の class を作っている部分を直す。

<% flash.each do |name, msg| %>
-  <%= content_tag :div, :class => "alert alert-#{ name == :error ? "danger" : "success" } alert-dismissable", :role => "alert" do
+  <%= content_tag :div, :class => "alert alert-#{ name == "alert" ? "danger" : "success" } alert-dismissable", :role => "alert" do
    <button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
    <%= msg %>
  <% end %>
<% end %>

後はいつもどおり flash[:alert] してあげればよい。

if @user_session.save
  redirect_to root_url, notice: "Login successful"
else
  redirect_to login_url, alert: 'Login failed.'
end

何も考えず redirect_to login_url, error: 'Login failed.' みたいにやっても効かないので注意。
(view で :error とあるから引っかかった。)

参考

おまけ

記述量を最小限にして直すと、上記のようになるけど、いやちゃんと考えたい、という場合には以下が参考になる。
以下の 3 つのうち、どれかを使えばひとまずは大丈夫そう。
(要は flash のキーが bootstrap のクラスに対応していればいいだけなので)

heroku で Postgres 9.4 を使う

イントロ

タイトルの通り。jsonb 型のカラムを取り扱おうとしたら、激しく怒られた。

rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:
...
PG::UndefinedObject: ERROR:  type "jsonb" does not exist
LINE 1: SELECT 'jsonb'::regtype::oid
...
: SELECT 'jsonb'::regtype::oid/app/vendor/bundle/ruby/2.2.0/gems/activerecord-4.2.1/lib/active_record/connection_adapters/postgresql/database_statements.rb:155:in `async_exec'
...

原因

Postgres の default version が 9.3 だから。jsonb 型は 9.4 からサポートされているので、そんな型しらねーよと言われている。

https://devcenter.heroku.com/articles/heroku-postgresql#version-support-and-legacy-infrastructure

解決法

https://blog.heroku.com/archives/2015/1/8/postgres-9-4-on-heroku

9.4 に upgrade して、DATABASE_URL を切り替えるだけでよい。

% heroku addons:add heroku-postgresql --version=9.4 --app test
Adding heroku-postgresql on test... done, v14 (free)
Attached as HEROKU_POSTGRESQL_YELLOW_URL
Database has been created and is available
 ! This database is empty. If upgrading, you can transfer
 ! data from another database with pgbackups:restore.
Use `heroku addons:docs heroku-postgresql` to view documentation.

config:unset

% heroku config:unset DATABASE_URL --app test
Unsetting DATABASE_URL and restarting test... done, v15

config:set

% heroku config --app test
=== test Config Vars
....
HEROKU_POSTGRESQL_ROSE_URL:   postgres://xxxxx
HEROKU_POSTGRESQL_YELLOW_URL: postgres://yyyyy
....

upgrade した時のログにも書いてあった通りで、HEROKU_POSTGRESQL_YELLOW_URL という環境変数が attach されているので、この value を DATABASE_URL に set する。

heroku config:set DATABASE_URL=postgres://yyyy

再起動

migration して再起動して反映させる。

heroku run rake db:migrate --app test
heroku run rake db:seed --app test
heroku restart --app test

rails で id を BIGINT 型 + primary key + AUTO_INCREMENT する

イントロ

rails のモデルは自動で id というカラムを作ってくれますが、これは常に INT 型。
BIGINT 型にしようとしたら、結構ハマったのでメモする。

使っている database はmysql です。
(postgresql だとこの罠は回避できるのだろうか?)

tl;dr

  • 普通に頑張ると...
    • db:migratedb:setup の実行結果が異なる
      • migration ファイルの内容が、schema.rb に(一部)反映されないため
    • db:migratedb:setup の実行結果が異なると、 db:rollback ができなくなるので、辛くなりそう。
      • db:migratedb:setup どちらかを使わない、という運用方式もある
      • だが、面倒くさいので、そういうことを意識したくない
  • 解決方法は 2 つ

普通にやってみる

Rails探訪 ~ create_table 編 ~ | マネーフォワード エンジニアブログ の素晴らしい参考記事によると、こんな感じ。

(ところで参考記事を見ると、色々地雷を踏み抜いてこの結論なので、この時点で既に普通でない気もする)

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users, id: false do |t|
      t.column :id, 'BIGINT PRIMARY KEY AUTO_INCREMENT'
      t.string :name

      t.timestamps null: false
    end
  end
end

rake db:migrate

% rake db:drop db:create db:migrate
mysql> desc users;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| name       | varchar(255) | YES  |     | NULL    |                |
| created_at | datetime     | NO   |     | NULL    |                |
| updated_at | datetime     | NO   |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

無事成功。

rake db:setup

一見うまくいってそうだが、実はこれは問題がある。

db/scheme.rbBIGINT 型にしたよ、という内容が反映されていないために、db:setup をやると db:migrate の結果と異なったものになってしまう。

% rake db:setup
mysql> desc users;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | int(11)      | NO   | PRI | NULL    | auto_increment |
| name       | varchar(255) | YES  |     | NULL    |                |
| created_at | datetime     | NO   |     | NULL    |                |
| updated_at | datetime     | NO   |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
4 rows in set (0.01 sec)

INT型 に戻ってしまった。
じゃあどうするの?と言うと、schema_format = :sql 方式か activerecord-mysql-awesome 方式の二通りがある。

schema_format = :sql

よく見かける解決法。 デフォルトでは schema_formatruby なので、これを sql にする。
:ruby では db/schema.rb を生成するが、:sql にすると db/structure.sql を生成してくれる。

config/application.rb 等に予め設定しておく。

config.active_record.schema_format = :sql

rake db:migrate

migration ファイルはここと同じものを使う。

mysql> desc users;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| name       | varchar(255) | YES  |     | NULL    |                |
| created_at | datetime     | NO   |     | NULL    |                |
| updated_at | datetime     | NO   |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
4 rows in set (0.01 sec)

bigint 型になっている。ここまでは良い

rake db:setup

% rake db:setup
mysql> desc users;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| name       | varchar(255) | YES  |     | NULL    |                |
| created_at | datetime     | NO   |     | NULL    |                |
| updated_at | datetime     | NO   |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

今度は migratesetup の結果が同じになりました。

activerecord-mysql-awesome

kamipo/activerecord-mysql-awesome

を使う。 この場合 schema_format:ruby のままで良い。

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users, id: :bigint do |t|
      t.string :name

      t.timestamps null: false
    end
  end
end

のような migration ファイルを作る

rake db:migrate

% rake db:drop db:create db:migrate
mysql> desc users;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| name       | varchar(255) | YES  |     | NULL    |                |
| created_at | datetime     | NO   |     | NULL    |                |
| updated_at | datetime     | NO   |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

rake db:setup

% rake db:setup
mysql> desc users;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| name       | varchar(255) | YES  |     | NULL    |                |
| created_at | datetime     | NO   |     | NULL    |                |
| updated_at | datetime     | NO   |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

migratesetup の結果が同じになった。

change_column 編

create_table ではなく、change_column でやったらどうなるか。 create_table の時点で INTBIGINT にしようと思うことが少ないので、実は change_column で対応できるかどうかが重要。

create_table の時と違って、今度は db:rollback が成功するかどうかもチェックしなければならない。

activerecord-mysql-awesome / schema_format = :sql

sql の確認コマンドは鬱陶しいので省略。

class ChangeIdColumnToUsers < ActiveRecord::Migration
  def change
    reversible do |dir|
      dir.up do
        change_column :users, :id, :bigint, auto_increment: true
      end

      dir.down do
        change_column :users, :id, :int, auto_increment: true
      end
    end
  end
end

この migration ファイルで

  • rake db:drop db:create db:migrate
  • rake db:setup
  • rake db:rollback

が成功する。ばっちり。
activerecord-mysql-awesome でも schema_format = :sql でも同じ結果になったので、change_column の時はこうすれば良さそう。

普通にやる

勿論これも activerecord-mysql-awesomeschema_format = :sql をせずに migration しても、 db:setup で id が INT型 になってしまう。

おまけ

https://github.com/rails/rails/pull/18220 がマージされているので、現時点での rails の master では対応済みのよう。
なので、 第三の選択「もうしばらく待つ」 も有りかも。

少なくとも Rails 4.2.1 では修正は含まれていなかったです。

ubuntu で install 済みのパッケージリストを表示する

イントロ

yum list installed 的なやつ。
apt-get list のようなコマンドがあるだろうと思っていたが無かった。

コマンド

dpkg --get-selections

参考

http://www.howtogeek.com/howto/linux/show-the-list-of-installed-packages-on-ubuntu-or-debian/

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 を作ってしまった...
次はもっといいものを考えてます。