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

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

ActiveJob はまだちょっと使うには早いかも

Rails 4.2 から導入された ActiveJob は、sidekiq や resque などのバックグラウンドジョブ系 gem を、同じ利用方法で扱えるようにしてくれます。

これは便利だなーと思い、sidekiq を ActiveJob を通じて使ってみたのですが、しばらく使った後に「まだ本格的に使うのは早いかも」と感じました。

リトライ機能が貧弱

sidekiq は、ジョブが失敗した時にリトライする機能があります。失敗するたびに次にリトライする間隔が伸び、一定回数失敗したら完全に失敗として扱われます。リトライ間隔や、完全に失敗になるまでの回数はもちろん変更可能です。

Web上でリトライしているジョブや完全に失敗したジョブを確認することもできます。

ActiveJob を使うと、この細やかなリトライ機能を失うことになります。例えば、次のように例外を拾って再度キューにジョブを入れることはできますが、リトライ間隔や最大のリトライ回数などはサポートしていません。この場合は無限にリトライすることになってしまいますね。

class SiteScrapperJob < ActiveJob::Base
  rescue_from(ErrorLoadingSite) do
    retry_job queue: :low_priority
  end

  def perform(*args)
    raise ErrorLoadingSite if cannot scrape
  end
end

リトライをサポートしてくれる gem

gocardless/activejob-retry という gem があります。これを使えば問題は解決しそうですが、READMEを読むと

This is an alpha library in active development, so the API may change.

との事なので、まだ実践で使うには早いのかなと思います。また、仮に alpha でなくなったとしても、ActiveJobの性質上、sidekiq の WebUI でリトライしているジョブの数を確認したりはできないはず。

まとめ

ActiveJob を使うと、様々なバックグラウンドジョブ系の gem を意識せず同じ使い方で使えるのがメリットだと思います。しかし1つの gem をずっと使うのであれば、その gem 特有の機能が使えなくなるデメリットが大きいように思えます。個人的には、今の段階ではなるべく ActiveJob は避けていくのがよいのかなと思います*1

*1:deliver_later くらいなら問題ないと思います

色彩検定2級を受けてきた

6月28日(日)に色彩検定2級の試験を受けてきました。

色彩検定協会/カラーコーディネーター

webサービスを作るときに、どんな色を使ったらキマるのか全然わからず適当に勘で決めていたのですが、色彩検定の勉強をする中である程度のセオリーをつかめた気がします。

まず去年受けた3級の試験勉強を通じて、色には色相とトーン(彩度、明度)があり、それぞれの種類と人に与える印象の違いについて学びました。

これがわかると、例えばビジネス関連のサービスを作りたかったら、色相は誠実な印象を与える青で、トーンは落ち着いた印象のライトグレイッシュを使おうか…などと、論理的に色を絞り込むことができます。今書いたような例がぴったりハマるようになるには経験が必要ですが、勘で決めていた頃よりはだいぶ前進できた気がします。

2級では、複数の色をどのように組み合わせると調和して見えやすいか学びました。例えば同系のトーンの組み合わせだと調和するとか。webサービスを作るには複数色必要なので、2級の範囲までは学んで損はないように思います。色とかどうやって決めたらいいのという人にはオススメです。

色彩検定の範囲には、色の分類の仕方などのwebデザインに直接使える項目以外にも、目の構造とか光の波長についてとかファッション、インテリアなどの項目もあります。僕は雑学を学ぶつもりでひと通りやりましたが、無駄なことをしたくない人は、色の分類のところだけ拾い読みするのもいいんじゃないかと思います。

勉強したいけどモチベーションが続かない!という人は毎月やっているデザインビギナーズのミートアップに来るとよいと思います ;-)

プロフィールサイトを作った

先日ブログをはてなに戻したのですが、はてなブログはいわゆる naked ドメインに対応していないため、ドメインを willnet.in から blog.willnet.in に変更しました。

結果として willnet.in が空いてしまったので、プロフィールっぽい感じのページとして利用することにしました。

willnet.in

各エントリのリダイレクトをする必要があったので、sinatra で簡単に実装。heroku に置いてサーバは Passenger にしています。

willnet/willnet_in

これでようやく落ち着いてブログ書ける環境になったかな…

はてなに出戻りました

3年ほど Lokka + heroku でブログを書いていましたが、この度はてなに戻ることにしました。

もともと Lokka を使おうとしたのは、

  1. markdown が使いたかった
  2. 自分でカスタマイズしたかった

という理由からでした。

しかし

  • 1 については今のはてなブログでは普通に markdown が使える
  • 2 については、今は他のプロダクトを複数持っており、そっちを優先したいので、ブログについてはなるべくカスタマイズせずに済ませたくなってしまった

というわけで出戻ることにしたのでした。

以前の willnet.in で書いていた記事はインポートしてきましたが、URLが変わってしまったため以前の記事にブックマークをつけていた場合はリンク切れになってしまっています*1。追記: リダイレクトするように修正しました。

最近は昔ほどエントリを書けていませんが、数は少なくてもなるべく質の高いものを書いていきたいなと思っております。これからもご愛顧のほどよろしくお願いしますm(__)m

*1:これがあったためなかなかブログの引っ越しに踏み切れなかった

find_by_sql したレコードで preload する方法

どうしても AR の query methods だけでは要求を満たせない場合の最後の手段として、find_by_sql メソッドがあります。皆さんよく使ってると思うのですが、戻り値のレコードからさらに association をロードしようとすると N+1 になるケースがあります。かといって

User.preload(:comments).find_by_sql(sql)

のようにしても期待通りには動きません。単純に preload 部分が無視されるだけです。

ではどのように書いたらよいでしょうか。

次のように書くと期待通りに動きます(Rails 4.2.1 で試しています)。

records = User.find_by_sql(sql)
ActiveRecord::Associations::Preloader.new.preload(
  records, :comments
)
records # comments が preload されている

:comments の部分は、通常の preload と同じように、配列やハッシュを指定できます。

これで N+1 の心配をせずに済みそうです。

参考

preload associations with find_by_sql · Prathamesh Sonpatki

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

まとめ

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