すがブロ

sugamasaoのhatenablogだよ

Rails 4.1のenumの挙動(2)

Rails 4.1 rc2での動作です。 前回の続きです。

前回、キーの項目と数値を定義したのだけど、実はキーの項目だけ指定しても数値として保存されるようだ(キーだけだと文字列で保存されるのかと思っていた……)。

class Product < ActiveRecord::Base
  enum status: %w(normal sale empty)
end

こんな感じで定義してから ./bin/rails cして確認する。 まずはどのようにデータが保存されるか。

Loading development environment (Rails 4.1.0.rc2)
irb(main):001:0> Product.create(status: 1)
   (0.2ms)  begin transaction
  SQL (0.9ms)  INSERT INTO "products" ("created_at", "status", "updated_at") VALUES (?, ?, ?)  [["created_at", "2014-04-06 02:53:47.039739"], ["status", 1], ["updated_at", "2014-04-06 02:53:47.039739"]]
   (1.3ms)  commit transaction
=> #<Product id: 1, name: nil, status: 1, created_at: "2014-04-06 02:53:47", updated_at: "2014-04-06 02:53:47">
irb(main):002:0> Product.create(status: "sale")
   (0.2ms)  begin transaction
  SQL (0.4ms)  INSERT INTO "products" ("created_at", "status", "updated_at") VALUES (?, ?, ?)  [["created_at", "2014-04-06 02:53:57.177845"], ["status", 1], ["updated_at", "2014-04-06 02:53:57.177845"]]
   (1.0ms)  commit transaction
=> #<Product id: 2, name: nil, status: 1, created_at: "2014-04-06 02:53:57", updated_at: "2014-04-06 02:53:57">
irb(main):003:0> Product.create(status: :sale)
   (0.2ms)  begin transaction
  SQL (0.6ms)  INSERT INTO "products" ("created_at", "status", "updated_at") VALUES (?, ?, ?)  [["created_at", "2014-04-06 02:54:05.389452"], ["status", 1], ["updated_at", "2014-04-06 02:54:05.389452"]]
   (1.3ms)  commit transaction

ついでに、前回は書かなかったんだけど述語メソッドも生えているのでそっちも確認しておく。ここで保存したのはsaleの値なので、sale?で問い合わせすることができる。

irb(main):013:0> pro = Product.where(status: 1).first
  Product Load (0.3ms)  SELECT  "products".* FROM "products"  WHERE "products"."status" = 1  ORDER BY "products"."id" ASC LIMIT 1
=> #<Product id: 1, name: nil, status: 1, created_at: "2014-04-06 02:53:47", updated_at: "2014-04-06 02:53:47">
irb(main):014:0> pro.sale?
=> true

ここではどうでも良いけど、emptyってステータスはメソッド呼び出しだとちょっと誤解を招きそうなので辞めたほうが良いですね。

検索する場合、ステータス毎のクラスメソッドがある。当然、メソッドチェインでwhere区等もつなげることができる。

irb(main):003:0* Product.sale
  Product Load (3.3ms)  SELECT "products".* FROM "products"  WHERE "products"."status" = 1
=> #<ActiveRecord::Relation [#<Product id: 1, name: nil, status: 1, created_at: "2014-04-06 02:53:47", updated_at: "2014-04-06 02:53:47">, #<Product id: 2, name: nil, status: 1, created_at: "2014-04-06 02:53:57", updated_at: "2014-04-06 02:53:57">, #<Product id: 3, name: nil, status: 1, created_at: "2014-04-06 02:54:05", updated_at: "2014-04-06 02:54:05">]>
irb(main):004:0> Product.sale.where('id > 2')
  Product Load (0.4ms)  SELECT "products".* FROM "products"  WHERE "products"."status" = 1 AND (id > 2)
=> #<ActiveRecord::Relation [#<Product id: 3, name: nil, status: 1, created_at: "2014-04-06 02:54:05", updated_at: "2014-04-06 02:54:05">]>

ただ、where区に文字列やシンボルで渡しても想定通りの動作にはならない。シンボルはともかく、文字列を渡すと0で検索しちゃうのはウッカリハマる可能性がありそう……。

irb(main):008:0> Product.where(status: 'sale')
  Product Load (0.3ms)  SELECT "products".* FROM "products"  WHERE "products"."status" = 0
=> #<ActiveRecord::Relation []>
irb(main):009:0> Product.where(status: :sale)
  Product Load (0.4ms)  SELECT "products".* FROM "products"  WHERE "products"."status" = 'sale'
=> #<ActiveRecord::Relation []>

私からは以上です。

Rails 4.1のenumの挙動

みんな大好きenum

DBのカラムでステータスとか使うとき、ぼくは数値派なんですけど、enumで掛けたら楽で良いですよねみたいのあります。ありますよね。

で、Rails 4.1ではそういうのが導入されたっぽいので試してみました。

rails 4.1をインストールしてプロジェクト作ったりする

$ gem install rails --pre --no-ri --no-rdoc
$ rails new sample
$ cd sample
$ ./bin/rails g scaffold product name:string status:integer
$ ./bin/rake db:migrate

rails cでフツーに登録してみる(ここは何の変哲も無い)。

irb(main):005:0> Product.create!(name: "hoge", status: 99)
   (0.1ms)  begin transaction
  SQL (0.2ms)  INSERT INTO "products" ("created_at", "name", "status", "updated_at") VALUES (?, ?, ?, ?)  [["created_at", "2014-03-26 01:25:21.026233"], ["name", "hoge"], ["status", 99], ["updated_at", "2014-03-26 01:25:21.026233"]]
   (1.9ms)  commit transaction
=> #<Product id: 2, name: "hoge", status: 99, created_at: "2014-03-26 01:25:21", updated_at: "2014-03-26 01:25:21">

statusの値が99でも保存できちゃう。そのままでも良いんだけど、validatesのおさらいとして例えば0〜2までに制限してみようか。

class Product < ActiveRecord::Base
  validates :status, inclusion: {in: 0..2}
end

ためしてみよう

Loading development environment (Rails 4.1.0.rc2)
irb(main):001:0> Product.create!(name: "hoge", status: 99)
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
ActiveRecord::RecordInvalid: Validation failed: Status is not included in the list
irb(main):003:0* Product.create!(name: "hoge", status: 2)
   (0.1ms)  begin transaction
  SQL (0.4ms)  INSERT INTO "products" ("created_at", "name", "status", "updated_at") VALUES (?, ?, ?, ?)  [["created_at", "2014-03-26 01:27:51.238248"], ["name", "hoge"], ["status", 2], ["updated_at", "2014-03-26 01:27:51.238248"]]
   (1.6ms)  commit transaction
=> #<Product id: 3, name: "hoge", status: 2, created_at: "2014-03-26 01:27:51", updated_at: "2014-03-26 01:27:51">

数値の範囲指定ができましたね。

それではenumを使ってみよう

enumの設定をしてみよう。ややこしくなるので、validatesは消します。ついでに書いておくと、enumの項目自体はテキトウなのでスルー推奨です。

class Product < ActiveRecord::Base
  enum status: {
    normal: 0, # 通常
    sale: 1,   # 特売
    empty: 2,  # 在庫切れ
  }
end

これで試してみよう。まずは正常系で。

irb(main):005:0* Product.create!(name: "hoge", status: 2)
   (0.1ms)  begin transaction
  SQL (0.2ms)  INSERT INTO "products" ("created_at", "name", "status", "updated_at") VALUES (?, ?, ?, ?)  [["created_at", "2014-03-26 01:33:06.501462"], ["name", "hoge"], ["status", 2], ["updated_at", "2014-03-26 01:33:06.501462"]]
   (1.6ms)  commit transaction
=> #<Product id: 6, name: "hoge", status: 2, created_at: "2014-03-26 01:33:06", updated_at: "2014-03-26 01:33:06">
irb(main):006:0> Product.create!(name: "hoge", status: :sale)
   (0.1ms)  begin transaction
  SQL (0.6ms)  INSERT INTO "products" ("created_at", "name", "status", "updated_at") VALUES (?, ?, ?, ?)  [["created_at", "2014-03-26 01:33:13.877152"], ["name", "hoge"], ["status", 1], ["updated_at", "2014-03-26 01:33:13.877152"]]
   (1.5ms)  commit transaction
=> #<Product id: 7, name: "hoge", status: 1, created_at: "2014-03-26 01:33:13", updated_at: "2014-03-26 01:33:13">
irb(main):005:0> Product.create!(name: "hoge", status: 'sale')
   (0.1ms)  begin transaction
  SQL (0.6ms)  INSERT INTO "products" ("created_at", "name", "status", "updated_at") VALUES (?, ?, ?, ?)  [["created_at", "2014-03-26 01:57:26.957196"], ["name", "hoge"], ["status", 1], ["updated_at", "2014-03-26 01:57:26.957196"]]
   (1.4ms)  commit transaction
=> #<Product id: 15, name: "hoge", status: 1, created_at: "2014-03-26 01:57:26", updated_at: "2014-03-26 01:57:26">

このように、シンボルや文字列、そして数値でも保存してくれるようになった。便利。

では範囲外の値を入力すると?

irb(main):007:0> Product.create!(name: "hoge", status: 99)
ArgumentError: '99' is not a valid status
irb(main):009:0* Product.create!(name: "hoge", status: :hoge)
ArgumentError: 'hoge' is not a valid status
irb(main):011:0* Product.create!(name: "hoge", status: 'hogehoge')
ArgumentError: 'hogehoge' is not a valid status

ことごとくArgumentErrorとなる。create!で試しているけど、createnewでも試してみよう。

irb(main):007:0> Product.create(name: "hoge", status: 99)
ArgumentError: '99' is not a valid status
irb(main):009:0* Product.new(name: "hoge", status: 99)
ArgumentError: '99' is not a valid status

create!だけに限らず、newメソッドとかでも引数に異なる範囲の値が入るとArgumentErrorになる。 普通のvalidatesに条件を書く場合は ActiveRecord::RecordInvalid: Validation failed: Status is not included in the list みたいにActiveRecord::RecordInvalidの例外クラスが送出されるけど、そうではない(例外クラス的にも、発生タイミング的にも)ので気をつけた方が良さそう。

validatesではないから挙動が異なるのは当然といえば当然だけど、使い勝手としては同じような挙動のほうが嬉しかったんだけどそうではなかったという備忘録エントリーです。

Rails詳しくないのでよくわかりませんが、ここら辺、正式リリースまでに挙動が変わったりするかしら?

書かなくてはならない事柄(追記)

こちらも何卒よろしくお願い致します。

Webアプリエンジニア養成読本[しくみ、開発、環境構築・運用…全体像を最新知識で最初から! ] (Software Design plus)

Webアプリエンジニア養成読本[しくみ、開発、環境構築・運用…全体像を最新知識で最初から! ] (Software Design plus)

SJISでもgit diffしたい

SJISのファイルでもgit diffしたい。いじわるしないで;;

どうするか

追記:NKF使わなくても良かった(後述)

$ git diff | nkf -u

でdiffの文字列をUTF-8にすれば良い。今日日NKFもそうそう使わないしMac OSXにはデフォルトで入ってないっぽいのでHomebrewでインストールすると良いでしょう。

$ brew install nkf

これでgit diffできて生きる希望が湧いてきますね。

NKFじゃなくてiconvで良かった

アドバイス通り、Mac OSXにはiconvがデフォルトで入ってるので、NKFを使わずとも実現できるのであった。

$ git diff | iconv -f SJIS

やった便利!!

書かざるをえない事柄

3/11に共著で本を書きましたのでよろしくお願いいたします。

Webアプリエンジニア養成読本[しくみ、開発、環境構築・運用…全体像を最新知識で最初から! ] (Software Design plus)

Webアプリエンジニア養成読本[しくみ、開発、環境構築・運用…全体像を最新知識で最初から! ] (Software Design plus)

あと、以下の日程でトークセッションをやる予定ですので、こちらも興味あれば是非(残席わずからしい)

SimpleMagick v1.1.0をリリースした

オプションの使い方を(内部的に)変更した

いままで、明示的にresizeやquality等いくつかのメソッドを実装し、他は足りなければ additional_option で直接呼んでねっていうつもりで書いてたんだけど、それはあんまりだろうってことでmethod_missing使ってImageMagickで定義されてるオプションだったらそのオプション名を使うことにした。

一番でかい変更はImageMagickのオプションを定義するの毎回コピペするのだるいのでRakeタスクにして、Rubyのファイル自体作っちゃってる所ですね。使う分には互換性は保てているハズ。たぶん。

ところで今日は3/11ですね

3/11といえば、今年からはWebアプリエンジニア養成読本の発売日という意味が含まれるようになりました。よろしくお願いいたします。

Webアプリエンジニア養成読本[しくみ、開発、環境構築・運用…全体像を最新知識で最初から! ] (Software Design plus)

Webアプリエンジニア養成読本[しくみ、開発、環境構築・運用…全体像を最新知識で最初から! ] (Software Design plus)

Ruby編、PHP編で書かれているサンプルソースGitHub上で公開されていますので、学びがありそうでしたら本書を手にとっていただけると嬉しいです。

ImageMagickをラップするSimpleMagickっていう薄いライブラリを作った

なぜか

MiniMagickが遅すぎたのだった。 MiniMagickはナウなヤングが使うイケテルツールとのことだったのだけど、コマンドにオプションを渡す度に mogrify コマンドを実行するため、でかい画像であればあるほど、前処理の為に時間が掛かる。まー初回にresizeを噛ませばちょっとはましになるところだけど、それにしても毎回実行してほしくない感じなのだった。

で、作ったのがこれ

使い方はREADMEにある通りだけど、こんな感じ。

require 'simple_magick'

if SimpleMagick.imagemagick_installed?
  image = SimpleMagick::Image.new('/path/to/src_image.jpg')
  image.resize '150x150'
  image.convert! '/path/to/dest_image.jpg'
end

実行するには ImageMagick 自体がインストールしてある必要がある(mogrifyを直接実行しているため)。

ベンチマークを取ると、ひとまずMiniMagickよりは早いことがわかる(これもREADMEに書いてある)。サンプルで使う画像次第で、如実に結果は変わるんだけど、自分が必要に迫られている現実ではもっと大きな差が出た(というか、ちょっと実用に耐えられなかったので自分で作ったのだけど)。

% bundle exec ruby benchmark.rb
                      user     system      total        real
simple_magick     0.080000   0.270000  28.530000 ( 29.687068)
mini_magick       0.410000   0.790000  31.300000 ( 37.115507)
ImageMagick       0.030000   0.170000  28.620000 ( 29.094316)

どうしても書かなくてはいけない宣伝

ぼくも一部執筆をした書籍が発売されます!!ジュンク堂等で先行発売しているところもあるようですが、3/11が正式な発売日です。

Webアプリエンジニア養成読本[しくみ、開発、環境構築・運用…全体像を最新知識で最初から! ] (Software Design plus)

Webアプリエンジニア養成読本[しくみ、開発、環境構築・運用…全体像を最新知識で最初から! ] (Software Design plus)

内容に関しては、献本しがいのある素晴らしい感想が書かれているエントリを見て頂ければ良いかなと思います。

出版記念イベントがあります!!!

電話で予約するという高難度イベントですが、まだ席に空きはあるそうなので、もしお時間があればぜひ。 - https://www.junkudo.co.jp/mj/store/event_detail.php?fair_id=4314

毎年忘年会に参加し続けた結果www

執筆する機会を得ることができまして、「Webアプリエンジニア養成読本」という本が近日発売する運びとなりました(ごめんね、ごめんね、タイトルは釣りです……)。

Webアプリエンジニア養成読本[しくみ、開発、環境構築・運用…全体像を最新知識で最初から! ] (Software Design plus)

Webアプリエンジニア養成読本[しくみ、開発、環境構築・運用…全体像を最新知識で最初から! ] (Software Design plus)

著者紹介の文章、テンションおかしくて胸が熱くなります。

この本ってどんな本なの

今までWebアプリを書いたことがない人向けです。例えば、学校でプログラミング言語自体はならったけどWebアプリ作ったことないわーみたいな人とか。

正直な所、この本をもってプログラミング言語が理解できるような深い内容ではないです。まずは写経して、「なるほど、わからん」という部分はその他の入門書やWebサイトなどで補完してもらうという形です。

その代わり、HTTPの通信の仕組みやサーバーを構築するための基礎知識や運用監視についてまで、抑えておくと良い点が揃っていると思います。ようするに、Webアプリを作りたい!!だがどうすれば?という時に、この本に書かれている要素を頼りにしてより深く調べて行ってもらえると良いのかなと思います。

また、本の出版イベントも行う予定ですので、もし興味のある方はいらしてください。

三行で執筆経緯を

  • ハチイチ忘年会に毎年参加してたら仲の良い人ができた
  • 毎年朝までいるもんで幹事をやることに
  • そんなこんなでゆーすけべーさんからお声がけ頂くことに

所感

履歴を見ると、11/14に声を掛けてもらって、その後編集者さんと顔合わせや構成についての意識合わせをしたのが11月末くらいです。なので、2〜3ヶ月で執筆や校正をした感じですね。 こういうスプリント戦は(書くのが決まれば)楽で良いです……*1

執筆陣のスピード感が非常に良く、レスポンスの良さだったり締め切りにきちんと合わせてくるであったり、なんというか息のあった良いチームでした。

Webアプリを作ろうという2章の構成ではPHP編とRuby*2という別の言語それぞれ同じような題材を扱うという話なんですけど、PHP編を書いてる @uzulla さんが超絶文章書きまくってて*3本当にスゴいと思いました。スゴいんですよ。いやースゴい。

合わせて読みたい:『Webアプリエンジニア養成読本』を共著で執筆しました+イベントやります!

*1:パーフェクトRubyは2年近くかかったので

*2:あとPerlもちょっとある

*3:予定の分量の3倍とか

ささたつ会議(あるいは新年会)に行ってきました

去る1/26、ささたつ会議が行われました。

f:id:seiunsky:20140126174223j:plain

ノリで参加したのですが、ワイワイしながら料理を作るというリア充臭あふれるイベントでした。

記憶に残っているメモ

  • 2kgあった鶏肉が一瞬で無くなった
  • 酒もドンドンなくなっていく
  • すぎゃーんさんの将来が心配です
    • 一流の人間は何をやっても*1一流になるのがわかったし心配いらないよ
  • 料理も酒もうまくてヤバい感じだった
    • 料金も2000円くらい?でコスパ厨歓喜
  • というか手作り料理って滅多に食べないので感動もひとしおでしたね

f:id:seiunsky:20140126202135j:plain

写真で振り返るささたつ会議

写真NGの人もいるかもしれないので、とりあえず大丈夫そうな人だけ写ってるヤツを乗っけてみます。

これはしめ鯖という食べ物で、とてもおいしい

f:id:seiunsky:20140126182412j:plain

これは福井県民が涙を流しながら食べるという逸話のあるおあげ。おいしい

f:id:seiunsky:20140126184324j:plain

「たーっちゃん」って呼ばれて照れてる世界一の幸せ者

f:id:seiunsky:20140126191223j:plain

いわゆる唐揚げと呼ばれる食べ物なのですが、皿がテーブルに置かれた瞬間に食べられしまう

f:id:seiunsky:20140126203231j:plain

ラーメン屋のポーズ

f:id:seiunsky:20140126203056j:plain

どあきちゃん(CV: @do_aki)

f:id:seiunsky:20140126205759j:plain

イケメンはネギを持ってもイケメンであると暗に自慢している図

f:id:seiunsky:20140126215219j:plain

ちなみに、世界一の幸せ者タスキは @kamipo に継承されました

唐揚げの作り方を学ぶことは出来ませんでしたが、とても楽しかったですし、料理も美味しかったです。料理を作ってくれた方、お酒を持ってきてくれた方、企画・実行をしてくれたささたつさん、ありがとうございます!!1

関連リンク:ささたつ会議(新年会?) - Togetterまとめ

*1:ドルヲタでも