一分一秒真剣勝負!

Ruby, Railsなど。Web系の技術ネタを充実させたい・・が、そうなるかは分からない。

RailsでCustom FormBuilderのテストってみんなどう書いているんだろう

RailsでFormBuilderを独自拡張した場合のSpecってあまり言及されていないよなと思う。

例えば、こんな感じで独自FormBuilderを書いたとする。

app/forms/application_form_builder.rb

するとSpecはこんな感じかなあと思っているのだけど・・・。

spec/forms/application_form_builder_spec.rb

なんかしっくりこない。 ActionView::Helpers::FormBuilderのコミットログを見ると、initializeメソッドの引数が変更になることもあるので、form_forを呼んでから書くというのがいいかなとか考えてる。 コードとしてはこんな感じ。 form_forから書いた方がClassを直で呼ぶより保守性が高いかな?

参考URL

Ginza.rb 第8回 Gemfileみせっこ!みんなどんなの使ってます?に先月行ってきた

2月に参加したGinza.rb第8回目についてのエントリー。 書いたのはいいけど、POSTし忘れてました。 というわけでGinza.rbで紹介したgemと紹介し切れなかったgemをまとめました。 全部ではないですが、これってものをピックアップしてます。

Ginza.rbで紹介したgem

seed-fu

seed-fuはseedデータの管理を行うためのgemです。

これに乗り換えたキッカケはRails4でrake db:fixturs:loadを実行してみたらFIXTURES_PATHの指定時にdeprecatedが出た事でした。 (マスターデータをfixtruesでロードしたかった)

Using FIXTURES_PATH env variable is deprecated, 
please use ActiveRecord::Tasks::DatabaseTasks.fixtures_path = '/path/to/fixtures' instead.

FIXTURES_DIRは今まで通りにパラメータで指定できるのに、FIXTURES_PATHは何故かActiveRecord::Tasks::DatabaseTasks.fixtures_pathで指定する方向になるようです。 これはキモいし、なんか不自然な変更なのでまた仕様が変わりそうな予感もする。ならseedでやろうかと思ったのだけど、

rake db:seed # rakeタスク
Rails.application.load_seed # コードで呼び出す場合

どちらもパラメータを受け取らない。実装を見ると、

# File railties/lib/rails/engine.rb, line 538
def load_seed
  seed_file = paths["db/seeds.rb"].existent.first
  load(seed_file) if seed_file
end

"db/seeds.rb"を決め打ちで読み込んでいるのでRailsのseedだとrake db:fixtures:loadの代わりに使う事はできなかった。そこで偶然見つけたのがseed-fuだった。

# rakeタスク
rake db:seed_fu FIXTURE_PATH=path/to/fixtures # パスを指定できる
rake db:seed_fu FILTER=users,articles # 読み込むseedデータを指定できる
# コードで呼び出す場合
SeedFu.seed(fixture_paths, filter) # rakeタスクと同様のオプションを指定可能

という感じでrailsのfixtures:loadとseedのいいとこ取りみたいなライブラリだったので使うことにしました。 パスが未指定の場合"#{Rails.root}/db/fixtures" と "#{Rails.root}/db/fixtures/#{Rails.env}"がデフォルトで設定されているので、環境ごとにseedデータを分けるのも簡単です。production環境のマスターにデータを追加したいなあって時も使えますね。

というわけでseed-fuオススメです。別のエントリーで使い方まとめようかな。

grape

RESTライクなAPIマイクロフレームワーク・・と書かれてますが、 要するにSinatraっぽいAPI専用のフレームワークで、活発に開発が行われています。 Grapeは単体で使用することもできますが、Railsと組み合わせることも可能です。 実際これで某サービスのAPIを実装しました。採用した決め手になったポイントは、

  • Railsのコントローラで作るより高速
  • 実装が読みやすい
  • HTTPリクエストメソッドにPATCHがある(Railsっぽい)
  • Railsと同居できる(ActiveRecordとか呼べる)
  • 最悪やっぱりRailsのコントローラにしようと思った時に、移行作業がそれほど重くなさそう

といったところで、非常に使いやすかったです。 これ流行ってほしいなー。

Ginza.rbで紹介しなかったgem

paper_trail

世代管理用ライブラリ。RailsCastsで紹介されている割には知名度が低い気がする。全く同じ機能をゴリゴリ書いていたプロダクトを思い出して、これを知ってたら工数減らせたなーとか思った。

pundit

権限管理ライブラリ。ポストcancan!最近流行りつつあるようだ。 cancanよりOOPぽく書ける。

まとめ

これぐらいですかね。全部個人的に流行ってほしいなと思ってるgemの紹介でした。

関連URL

Ginza.rb 第8回 Gemfileみせっこ!みんなどんなの使ってます? Ginza.rb 第8回 Gemfileみせっこ!みんなどんなの使ってます? を開催した

Railsのエラーハンドリング

Rails3.2から結構いい感じになったエラーハンドリング

Railsは1.xからやっているけど、気に喰わないのがエラーハンドリング周りだった。 特にRoutingエラーを補足する為に各バージョンごとに対応が微妙に違ったりして、毎回調べたりRailsの実装を追っていた記憶がある。 だけどRails3.2から実装された機能で、個人的にはこれで落ち着いたかなと思えた。 既に4が出ているので今更感があるけど、備忘録として書いておく。 Rails4.0.2,Ruby2.0.0で確認しています。

Release Notesでは以下のように書かれている。

Added config.exceptions_app to set the exceptions application invoked
by the ShowException middleware when an exception happens.
Defaults to ActionDispatch::PublicExceptions.new(Rails.public_path).

つまり、例外発生時に呼ばれる例外処理アプリケーションを config.exceptions_app で設定できるようになった。 デフォルトではActionDispatch::PublicExceptions.new(Rails.public_path)が設定される。

どうやって使うか

色々と調べた結果、ActionDispatch::Routing::RouteSetをセットしてやるのが一番シンプルかなーと思った。 まずはself.routesをセット。

# config/application.rb
config.exceptions_app = self.routes #=>ActionDispatch::Routing::RouteSet

で、例外処理用のコントローラを作成しておく。

# app/controllers/erros_controller.rb
class ErrorsController < ApplicationController
  def not_found
    render status: 404
  end

  def internal_server_error
    render status: 500
  end
end

最後にroutes.rbで各httpステータスコードを判定し、ErrorsControllerの各メソッドを指定してやる。

# config/routes.rb
  get '/404', to: 'errors#not_found'
  get '/500', to: 'errors#internal_error'

他のやり方

self.routes以外を指定する事も当然出来る。 というかRackアプリを指定できるのでRailsで無くてもOKです。 例えば以下の様な感じ。

# config/application.rb
config.exceptions_app = ->(env) { ErrorsController.action(:error_page).call(env) }

明確にエラーコードを表示したいウェブアプリだと使うかも。

思うこと

エラーハンドリングに関してはRails newした時に雛形を作るなどしてしまってもいいのではと思った。フリーランスで働いていた時に、途中から入ったプロジェクトの場合、エラーハンドリングの実装方法がバラバラで、その内容によっては無駄に工数がかかるケースもあったので。 ま、Rails is omakaseなので嫌なら使うなって話ですが。

Rails4にアップデートした

Rails4にアップデートする情報は十分にあるので、あえて書くことは無いんですが、今やってるプロジェクトでRails3.2.14からRails4に上げた時のメモ。

基本的にRails3のうちにstrong_parametersさえ対応しておけば大したことはないというのが感想。
問題は使っているgemがRails4に対応しているかどうかですね。
ほぼ全てのgemが対応済みだったり、対策方法があってスムーズに移行が進んだのですが、ちょっとはまったのがglobalize3でした。rails4というブランチがあるので、それをGemfileで指定してbundle installしたら以下のエラーが発生。

Bundler could not find compatible versions for gem "activerecord":
  In Gemfile:
    globalize3 (>= 0) ruby depends on
      activerecord (~> 3.0) ruby

    rails (= 4.0.0) ruby depends on
      activerecord (4.0.0)

Bundler could not find compatible versions for gem "rails":
  In Gemfile:
    globalize3 (>= 0) ruby depends on
      rails (~> 3) ruby

    rails (4.0.0)

調べてみたらpaper_trailとの依存関係が問題で、paper_trailを使っていなくても同じくrails4ブランチを指定してGemfileに追記すればおkでした。
Rubyも2.0になってるし、とりあえずは安心。

Git勉強会@Krayに行ってきた

KrayさんのGit勉強会に行って来ました。以下感想。

Gitはなぜ難しいのか(irohiroki)

コマンドを実行した時に.git/ 配下で何が起こっているのか?という部分にフォーカスを当てていた部分が印象に残りました。初心者向けという前提っぽかったので、最初にそれ説明しちゃうの・・・?と思っていたけど、前半で.git/配下の動きを説明しておいた方が理解しやすくなるなと感じました。これは新発見。(この時、メタプログラミングRubyを連想してしまった。タイトルに「メタプログラミング」って入っているので勘違いしていたのだけど、他言語経験者にとってはいい入門書として読める良書です。)

Gitをなんとなく使ってる人に丁度いい感じの内容でした。

まとめ: pullは恐ろしいコマンド

Gitがどう動いているかについて(f96q)

Gitのソースコードの解説。初音ミクのカツラが気になってあまり記憶に残っていない。

まとめ: GitはCで実装されてるけど、構造体使ってOOPな感じに作ってるよ!おもしろーい!リーナス!リーナス!

次回予告

次もあるらしいのでダニーさんに脅されて行くと思います。

HerokuでBambooStackからCedarStackに移行する

Herokuでずっと更新していないアプリをruby2.0に上げようとしたのだが、Gemfileに「ruby "2.0"」と書いてもpushするとbundle installでエラーが出る。調べてみると過去にHeorkuで作ったアプリはBambooStack上で動いていて、使用出来るRubyのバージョンが古いなどなどが原因だった。
なので、Ruby2.0を使えるCedarStackに移行しようと思ったのだがBambooStackからCedarStackへの移行ツールなどは用意されておらず、CedarStackにするには新しくアプリを作るしかなかった。旧アプリ→新アプリへのデータ移行作業をすればいいかと考えたのだけど、BambooStackで作ったアプリのURLは「http://appname.heroku.com/」だったものがCedarStackだと「http://appname.herokuapp.com/」で作られるらしい。独自ドメインを設定していないので、URLが変わってしまう。これでは移行できない・・・と思ったが、アプリ名さえ同じであればheroku.comもherokuapp.comと同じIPを指してくれるので問題は無し。

というわけでまずDBのバックアップ。

bundle exec heroku db:pull sqlite://db/production.sqlite3 --app appname

ここで自分の環境ではエラーが出た。このエントリーを書いている時点ではRuby1.9.3以降でsqlite形式にデータを変換する時にバグがあるらしく、Ruby1.9.2以前のバージンでエクスポートしなくてはならない。(Macで開発している場合は「brew install apple-gcc42」でRuby1.9.2をコンパイルできるようになります。)

そして現在使っているHerokuドメイン"appname"をブラウザから別名にリネーム*1し、今まで使っていた"appname"でアプリを再作成します。

heroku create appname --stack cedar # アプリ削除からこのコマンド実行までに"appname"を取られたら作れなくなるので急いで作る

そしてバックアップしたDBを新アプリにインポート。

bundle exec appname db:push sqlite://db/production.sqlite3 --app appname

これでアクセスしてみて動作確認がとれれば旧アプリは削除してしまってOK。

ちょっと面倒ですね。ブラウザからクリックひとつでStackを移行できるツールがあったらいいのに。

*1:コマンドでもできます

入門Chef Solo補足メモ

入門Chef Solo - Infrastructure as Code

入門Chef Solo - Infrastructure as Code


そろそろChefでも覚えるかって時に伊藤直也さんが「入門Chef solo」を出版されたので購入しました。
Chefを覚えようとして最初の段階でハマッて諦める人が多いみたいですね。僕はいきなり本書を読みながら進めていったので殆ど止まること無く覚えられましたが、Chefのマニュアルを見た印象では確かにいきなり詳細過ぎる解説を見せられても分かりにくいかなー、と感じました。たったの890円だし、Chefをこれから覚えたい人は買って損は無いと思います。

補足

knife-soloの最新版インストール時の解説で、git cloneしてrake installと書かれていましたが、自分の環境ではjsonが入らずにエラーになりました。gem update --systemを実行し、gem installでオプションの--preをつければ0.3.0が入ります。

$ gem update --system
$ gem install knife-solo --pre

knife solo init実行時にuninitialized constant KnifeSolo::Pathname (NameError)が発生しました。knife-soloのインストール先のknife-solo.rbでrequire 'pathname'を追記して下さい。例えばrbenvを使っている環境なら以下のファイルになります。

# require 'pathname' を追加
~/.rbenv/versions/2.0.0-p0/lib/ruby/gems/2.0.0/gems/knife-solo-0.3.0.pre3/lib/knife-solo.rb