おもしろwebサービス開発日記

Ruby や Rails を中心に、web技術について書いています

Rails で DB の Time 型を扱う

Rails では、DB の Time 型 を扱うことができます。これは日付を必要とせず、時間だけを格納したいときに使います。

定義方法は、他の型と同じく add_column メソッドなどで time を指定するだけです。

add_column :users, :lunch_time, :time

ただ、これを使おうとするにはちょっとしたノウハウが必要です。

Ruby や Rails には、時間のみを扱うクラスはありません。DB の Time 型は Ruby の Time オブジェクトに変換されます。Time オブジェクトは年月日の情報を持っています。その際、タイムゾーンは utc (正確には ActiveRecord::Base.default_timezone で設定されたタイムゾーン) として扱われます。

lunch_time カラムに 12:00 が格納されていた場合、次のような Time オブジェクトが返ります。

user.lunch_time #=> 2000-01-01 12:00:00 UTC

日本向けのアプリケーションであれば、 Rails のタイムゾーンを次のように Tokyo に設定するケースはとても多いと思います。この場合、UTC と JST の違いで苦しむことになります。

config.time_zone = 'Tokyo'

具体的に例を見ていきましょう。

フォームからの入力を保存する

このケースは特に問題がありません。他の型と同じように格納できます。

time_field メソッドで input タグを作りフォームを submit すると、'12:00' のような文字列がサーバに投げられます。

user.lunch_time = '12:00'
user.save

で DB には 12:00 が格納されます。

現在時間と比較する

user.lunch_time #=> 2000-01-01 12:00:00 UTC

先程も書きましたが、Time オブジェクトは年月日の情報を含むので、JSTとの時間を比較するには工夫が必要です。現在時刻が、特定の User のランチタイムより前であるかを調べるには次のようにするとよいでしょう。

t = user.lunch_time
t.to_s(:time) > Time.zone.now.to_s(:time)

to_s(:time) とすると、タイムゾーンにかかわらず単純に時間だけが文字列として出力されます。しかしダサいですね…><

時間だけを扱うクラスを提供する gem もあります。これを使うともう少しマシな感じになるかもしれません。

jackc/tod

クエリを投げる

現在時刻以降にランチタイムを設定した User を取得したい場合、次のように書くと思ったとおりに動きません。

User.where('lunch_time >= ?', Time.zone.now)

理由は Time.zone.now が JST から UTC に変換されてからクエリが実行されるからです。

例えば今が日本時間の 11:00 であれば、次のような SQL が発行されるでしょう。

SELECT "users".* FROM "users" WHERE (lunch_time > '2015-05-28 2:00:00.000000')

これでは、2時より後にランチタイムを設定しているユーザを取得してしまいます(※)。想定している挙動と違いますね。これをうまく扱うにはどうしたらよいでしょうか。

※ PostgreSQL で試した場合は、日付の箇所は無視されるようです。sqlite は time 型でも日付が入るようで、結果は異なります。

次のようにするとうまく動きます。

User.where('lunch_time >= ?', Time.zone.now.to_s(:time))

次のようなクエリが発行されます。

SELECT "users".* FROM "users" WHERE (lunch_time > '11:00')

結局 to_s(:time) に頼ってしまいました。もっと綺麗に書く方法があるかもしれませんが、とりあえずはこれでうまく動きます。

Rails 5 ではこうなる

さて、time 型の微妙さは伝わりましたでしょうか。嬉しい事に、Rails 5 では time 型の挙動を変更することができるようになるようです。

  • Rails 5.0 で time 型をタイムゾーン対応に設定できる
  • Rails 5.1 で time 型はデフォルトでタイムゾーン対応になる
    • 設定で非対応にもできる

現在の rails の master ブランチを利用し、time 型のカラムを持つ User モデルで User.new などとすると次の warning メッセージが表示されます。

  Time columns will become time zone aware in Rails 5.1. This
  still causes `String`s to be parsed as if they were in `Time.zone`,
  and `Time`s to be converted to `Time.zone`.

  To keep the old behavior, you must add the following to your initializer:

  config.active_record.time_zone_aware_types = [:datetime]

  To silence this deprecation warning, add the following:

  config.active_record.time_zone_aware_types << :time

config.active_record.time_zone_aware_types に設定した型のカラムはタイムゾーン対応になるようです。

試しに config/application.rb に次の行を追加してみます。

config.time_zone = 'Tokyo'
config.active_record.time_zone_aware_types = [:datetime, :time]

time 型を入力すると、JST の時刻になっているのがわかります。

user.lunch_time = '12:00'
user.lunch_time #=> Sat, 01 Jan 2000 12:00:00 JST +09:00

DB には UTC 時間である "03:00:00" が格納されます。

というわけで、Rails 5.0 からは楽に time 型を取り扱えるようになりそうです。

参考

Time columns should support time zone aware attributes by sgrif · Pull Request #15726 · rails/rails

ゆるデブ合宿で島根に行ってきた

ひょんなことから島根の自治体の方と知り合いになり、島根県が企業やコミュニティの合宿を誘致しているという話を聞きました。

合宿またやりたいなあ。島根まだ行ったことないし行ってみたいなあ。という気持ちからゆるデブのメンバーに相談したところ、反応がよかったので色々詳細詰めて、ゴールデンウィークを利用して島根に行くことにしました (\( ⁰⊖⁰)/)

内容については、すでに iR3 さんがブログに書いてくれています。

島根県松江市美保関町北浦海岸でyurufuwa開発合宿 - iR3’s diary

感想

全体を通じて、とても楽しい合宿でした!また行きたい!

島根の田中さんや井上さん、民宿の中村屋さんにいろいろご配慮&差し入れいただいて快適な合宿になりました。

4泊5日の合宿でしたが、2日は移動日(ゴールデンウィークなので調度良い時間のチケットが取れなかった><)に当て、2日もくもく、1日はアクティビティにあてました。

東京から二時間かからず島根までいけるので、思ってたよりも移動は楽でした。

もくもく

開発風景。和室だと腰が辛くなるので、椅子があると嬉しいですとお願いしたところ、人数分の椅子と机を用意していただけました。お陰でとても快適に開発できました。

DSC06202

コードを書くのに疲れたら、窓ごしに海を眺めてHPを回復させることができます。

あとは海辺でコードを書いたり。

IMG_3854

美味しい海鮮系ご飯を大量にいただきました。

アクティビティ

アクティビティはシーカヤックと、浜辺でのバーベキューでした。溢れるリア充感。

DSC06329

DSC06318

DSC06399

DSC06402

DSC06404

合宿中、ちょうど@fukayatsu氏の誕生日があったのでケーキを食べました。

IMG_3885

最終日の一枚。

IMG_3041

まとめ

合宿場所の候補として、島根はちょうオススメです!近場の合宿に飽きたら島根に足を伸ばしてみると良いとおもいます (\( ⁰⊖⁰)/)

LOCAL Community Summit 2015 で ginza.rb について LT した

LOCAL Community Summit とは、 地方の技術者やコミュニティの取り組みを、地元だけではなく東京で発表してみよう! 私達の地域の楽しさを、他の地域の技術者にも伝えよう! 地元を離れ首都圏で活躍している技術者や、いろんな地域のコミュニティと繋がろう! というテーマのもと、LOCALとして初の東京開催となるIT勉強会イベントです。

LOCAL Community Summit 2015 - LOCAL Community Summit | Doorkeeper

「いろんな地域のコミュニティ」の1つとして、ginza.rb の紹介LTをしてきました (\( ⁰⊖⁰)/)

スライド見ただけだと内容はわかりにくいかと思いますので簡単に補足。

LOCALは、いろんなコミュニティの人が集まるメタコミュニティ!という話を聞いていたので、運営的に気をつけている点を中心にしつつ ginza.rb の紹介をしました。

ginza.rb は他のコミュニティの良い所を吸収しています。例えば自己紹介用の文章をプルリクエストで集めるのはよちよち.rbのやり方を参考にしたし、初参加の人に一言感想をもらうのはP4D、KPTで振り返りをするのはSendagaya.rbで実施しているのを見て真似をしてみました。

さらに毎回少しずつ改善を重ねて楽しいミートアップになるようにしています。その中でも、ミートアップ中に idobata を併用して、情報を補足するやり方はうまくワークしているように思えます。

「ginza.rb らしさとは何か?」と聞かれたら、そういうところなのではないかと思います。

Rails でセッションの有効期限を延長する

config/initializers/session_store.rb で次のように設定する

Rails.application.config.session_store(
  :cookie_store,
  key: '_hoge_session',
  expire_after: 1.month
)

Rails のドキュメントや railsguides で全然見つからなかったのはなぜなのだろう…。

参考

What happens to Rails session after :expire_after time is up? - Stack Overflow

最近の Rack サーバ事情について

先月、heroku の推しサーバが unicorn から puma に変わったという発表がありました。unicorn だとスロークライアントの影響を受けやすいというのが理由なようです。

もう少し詳しく調べてみましょう。

そもそもスロークライアントってなに

その名の通り遅い回線のクライアントです。3G環境のモバイル端末などが該当します。

「unicorn だとスロークライアントの影響を受けやすい」とは

unicorn はプロセスモデルのサーバであり、blocking I/O モデルを採用しています。つまり、クライアントとの通信中プロセスが専有されるということです。

例えば unicorn がワーカプロセスを3つ立ち上げていて、そこへ通信完了に10分かかるようなスロークライアントが3つ接続されたら…、続くクライアントはスロークライアントの通信が完了するまで実行を待たなければならなくなります。プロセスの数をもっと増やせば対応できますが、それはその分メモリやサーバ台数、最終的には月額のサーバ費用に影響を与えます。

unicorn はなぜそんな設計になっているのか

unicorn は、「ひとつのことをうまくやる」unix哲学に則っており、その上で "バックエンドの" アプリケーションサーバとして作られています。

つまり、nginx などのリバースプロキシを前面にたてるのを前提とした作りになっているわけです。

The Philosophy Behind unicorn

リバースプロキシを使うと、unicorn はレスポンスをリバースプロキシまで届ければよくなります。リバースプロキシはたいてい同一ネットワーク内にあるので、とても早く通信できます。スロークライアントが通信するのはリバースプロキシです。これにより、スロークライアントが unicorn のプロセスを長時間専有するのを防ぐことができます。

nginx & unicorn は、heroku 以外の Rails アプリケーション環境では現時点で一番多い構成なのではないでしょうか。

heroku にはリバースプロキシはないのか

ないようです。ただ、heroku の HTTP Routing のドキュメントを見ると、1MB まではレスポンスをバッファしてくれるようですね。

HTTP Routing | Heroku Dev Center

puma に変更したら全て解決するのか

この発表このへんの文章を見ると、puma にしたらオールオッケーなように読めてしまいますが、そんなことはありません。

この記事 の Multi-threaded blocking I/O の箇所を読むと分かるのですが、確かにスレッドベースのサーバを採用すると、1スレッドがスロークライアントで専有されても別のスレッドが処理を受け持つことができます。ただし、全てのスレッドがスロークライアントと通信したら、結局同じことです。

同じメモリ量でも、プロセスベースのサーバより、スレッドベースのサーバの方が作成できるスレッド数が多いので、スレッドベースのほうがスロークライアントに強いということは言えると思います。

puma にすることによるリスク

puma はスレッドベースのサーバです。ということは、アプリケーションの書き方によっては race condition を引き起こす可能性があります。普通に Rails アプリケーションを書けばスレッドセーフになるはずですが、その辺りをよくわかっていない初心者が安易に手を出すのは危険なのかなと思います。

よくわからない人は Working With Ruby Threads を読みましょう。

unicorn の方が優っていることもあります。この間 ginza.rb で unicorn や puma などの Rack サーバのベンチマーク結果を比較をしました。結果としては、大量アクセス時の処理としては unicorn の方が puma よりも安定している印象でした。

「puma が不安定だ」とは思わないのですが、安易に「herokuが推奨してるからこれからは puma だ! unicorn はオワコン!」とならないようにリスクについても書いてみました。

まとめと雑感

基本的に、リバースプロキシのない heroku を使うのであれば、現時点では puma が適していると思います。しかしその場合はスレッドセーフなコードを書くよう気をつけなければなりません。

また、heroku 以外の環境ではリバースプロキシが使えるため、puma にすることのメリットは(heroku ほどには)ないと言えます。まだまだ nginx & unicorn の天下が続くのではないでしょうか。

リバースプロキシ内蔵のプロセスベース(スレッドベースにもできる)のサーバである raptor (passenger 5) 正式版ががそろそろリリースされます。以前の ginza.rb でベンチマークをとった時は beta2 だったのでまだまだ不安定な印象でしたが、正式版になって安定したら、heroku 環境での鉄板サーバになるかもしれません。

取り急ぎこのブログ(heroku & lokka)を passenger 5.0.0.rc2 にしてみました(注: はてなではなく、以前使っていたブログの時の話です!)。このブログは特にアクセス数が多いわけではないので高負荷時の安定性などはわかりませんが、とりあえず普通に動きますね。

RubyMotion Android で、Java のクラスをRubyで継承するときの落とし穴

自分用のメモ。RubyMotion 3.3。

前提

  • RubyMotion では Java のクラスを Ruby で継承できる
  • ただ、その際はコンストラクタだけは Java で書かないとダメ

単純にコンストラクタを書く用途以外に、「現行のRubyMotionでうまく動かない箇所をJavaで書いて何とか動かす」用途としても使える(バッドノウハウ)。

落とし穴のレシピ

  • コンストラクタでインスタンス変数に値を格納する
  • 当然、Java の方で変数宣言する

この時、Ruby 側でも attr_accessor などを使ってインスタンス変数にアクセスするメソッドを生やすとハマる。なぜか Java の方で入れた値を Ruby 側で見ることができず、 nil になる。

解決策

attr_accessor を消せば OK

第19回 ginza.rb ミートアップ

第19回目の ginza.rb ミートアップを開催しました。

Ginza.rb 第19回 だれが一番?Railsアプリサーバ徒競走!&Ruby2.2について話そう - Ginza.rb | Doorkeeper

raptor と rhebok のパフォーマンスをみる

第17回ミートアップ で、Rack サーバの比較をしましたが、その時はまだ raptor の実装が公開されていませんでした。

今回は raptor(Passenger5) のベンチマークがとれるようになったので、ベンチを取ってみました。さらに、unicorn の2倍早いという rhebok というサーバも登場したので、それも一緒に。

@y_yagi さんが、第17回に利用したengine yardさんのアプリを使ってベンチをとってくれました。多謝!

結果としては、比較対象としてベンチをとった unicorn と Passenger4 が安定しているなという印象。rhebok や raptor はまだ不安定な気がします…。

あとは、raptor の sleep(sleep 1 してから render するアプリ)の結果の爆速ぶりが異常。多分HTTPサーバのレベルでキャッシュをして、それを返しているのだと思いますが、現時点でキャッシュを返す条件のドキュメントがないので詳細がわからず。

続いては raptor のキャッシュ機能、Turbocaching についてのブログ記事があったのでその内容についてシェア。

Researching a potential new form of HTTP caching optimization - Phusion Blog

簡単にまとめると、Cookieの"一部の"値を利用して、ユーザ種別を判別し、ユーザに応じたキャッシュを返すようにして高速化を図っています。Varnish などのキャッシュ−サーバにも似たような機能があるのですが、Cookie全体の値を利用するため、サードパーティーCookieを利用したアプリなどでは想定するような高速化はできないとのこと。

この機能を使うと、

  • 大多数の人が同じ画面をみるアプリ(ブログなど)
  • 一部だけがユーザ個別のアプリ(ログインユーザの名前の表示部分だけが異なるアプリなど)

の高速化が見込めます。詳細は元記事を参照してください。 たぶんこの記事以外にもTurbocachingの機能があると思うので、それに期待しています。

Ruby 2.2.0 について話す

Ruby 2.2.0 の関連の記事を読みました。

個人的には

{"hoge": "fuga"}

という書き方ができるようになったのが嬉しいです。

次回

次回は2/17(火)です。お題はコーディング規約です。いろんな会社の規約を見ながらワイワイ話しましょう!