scramble cadenza

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

respond_to vs routes 指定の format

イントロ

例えばAPI サーバーを作るときに「この URL にアクセスする時は、常に json でアクセスしてほしい」みたいな事を実現したい。

それを実現する具体的な方法としては namespace :api, format: 'json' と routes を書けばいいと思ってました。
(デフォルトの format が json になるので)

ただ

  • それ以外の format を渡した時にどう動くか
  • 似たような事ができる実装とどう違うのか

がよくわかってなかったので、その時にした実験をもう一度やってみたメモ。

パターン1

class Api::AccountsController < ApplicationController
  def index
    @accounts = Account.all

    respond_to do |format|
      format.html { render }
      format.json { render }
    end
  end
end
Rails.application.routes.draw do
  namespace :api, format: 'json' do
    resources :accounts
  end
end
  • controller 側では respond_to複数 format を受けつける準備ができている
  • namespace では json を指定

結果

% curl -I http://localhost:3000/api/accounts
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Type: application/json; charset=utf-8
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 01d34bba-60c5-4185-9e96-e343d179c45b
X-Runtime: 0.014599
Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)
Content-Length: 10
Connection: Keep-Alive
% curl -I http://localhost:3000/api/accounts.html
HTTP/1.1 404 Not Found
Content-Type: text/html; charset=utf-8
Content-Length: 41119
X-Web-Console-Session-Id: 939cd9fb2834ea8353d1332a62d97e15
X-Request-Id: a77ea27e-f5c0-4766-b357-09b964188e55
X-Runtime: 6.597595
Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)
Connection: Keep-Alive
  • controller で受け取る formatjson になる
  • response の Content-Typeapplication/json
  • json 以外の format の request が投げられたら、404 Not found
    • controller 側では html をの時は html の render をする準備ができているのにも関わらず
    • routes 側で 404 になってしまうので、controller に到達しない
  • controller 側に到達した時には、常に json 形式
    • (書いてしまってるけど) 実質 renspond_to複数 format 対応は 不要

パターン2

class Api::AccountsController < ApplicationController
  def index
    @accounts = Account.all

    respond_to do |format|
      format.html { render }
      format.json { render }
    end
  end
end
Rails.application.routes.draw do
  namespace :api do
    resources :accounts
  end
end
  • format 指定を削除
  • controller 側は変わらず

結果

% curl -I http://localhost:3000/api/accounts
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Type: text/html; charset=utf-8  # text/html に注目!
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 4db51f09-53cc-42ca-a6a0-49e4080b5030
X-Runtime: 0.492789
Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)
Content-Length: 1715
Connection: Keep-Alive
% curl -I http://localhost:3000/api/accounts.html
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Type: text/html; charset=utf-8
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 7d446561-2768-4f20-8832-4d6476115d45
X-Runtime: 0.050431
Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)
Content-Length: 1715
Connection: Keep-Alive
  • json が通るか確かめる
% curl -I -X GET http://localhost:3000/api/accounts.json
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Type: application/json; charset=utf-8
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: c3c4a0fb-847c-4240-9470-606695c6b935
X-Runtime: 0.023534
Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)
Content-Length: 10
Connection: Keep-Alive
  • respond_to での指定以外の format
% curl -I http://localhost:3000/api/accounts.xml
HTTP/1.1 406 Not Acceptable
Content-Type: text/html; charset=utf-8
Content-Length: 115323
X-Web-Console-Session-Id: bfb96347bd3ab4058a468ebb5762595f
X-Request-Id: 74ee3306-aa22-4615-a72d-c1a7602ef434
X-Runtime: 0.640820
Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)
Connection: Keep-Alive
  • respond_to do ~ end で指定する format の内、 最初に書いてある方が default になる
    • これは controller のコードで、優先順位が決定するということ
    • 今回は format.html { render } を先に書いたので、html が default になった
      • json を上にすると、format 指定なしの状態では json が返る
  • respond_to ブロックで指定している format 以外の request が投げられたら 406 Not acceptable

responders

class Api::AccountsController < ApplicationController
  respond_to :json
  def index
    @accounts = Account.all
    respond_with @account
  end
end
Rails.application.routes.draw do
  namespace :api do
    resources :accounts
  end
end

結果

% curl -I http://localhost:3000/api/accounts
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Type: application/json; charset=utf-8
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: f7a06375-320d-4d37-a93c-12d9d3c17cf0
X-Runtime: 0.299781
Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)
Content-Length: 0
Connection: Keep-Alive
% curl -I http://localhost:3000/api/accounts.html
HTTP/1.1 406 Not Acceptable
Content-Type: text/html; charset=utf-8
Content-Length: 115331
X-Web-Console-Session-Id: 780f1e5e4050cb0b7e72d36f60841f4c
X-Request-Id: b0f92ee8-c6fa-4dc4-872e-f60adc942104
X-Runtime: 0.379881
Server: WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)
Connection: Keep-Alive
  • パターン2 とほぼおなじ
    • respond_to複数個 format が渡せるが、format 指定無しでリクエストされた場合、最初に指定されてある format が利用される
      • respond_to :html, :json なら、html がデフォルト値
  • 指定以外の format でのリクエストは、406 Not acceptable
  • responders の利点は、controller 毎に format の指定をしなくて済むことなのかな?
    • 一箇所にまとめられていれば、controller 毎にデフォルトの format が異なるという間違いを避けられる。
    • (一瞬触っただけなので、よくわかってない)

結論

サーバーが指定外の format で request を受けた時、どう返したいかで実装が変わる

  • 406 Not acceptable を返す場合は respond_to (あるいは responders)
    • 複数 format を受け付ける URL なら、こちらのほうが自然
  • 404 Not Found を返す場合は namespace :prefix, format: 'format名'
    • 文字列 で指定すること。(symbol じゃダメ)
    • 例えば「json しか絶対受け付けない」なら、こういうのもありかも。

参考

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/