- 更新日: 2014年3月8日
- Rails
Rails でユーザーのサインアップ時にタイムゾーンを自動で判定して保存
ユーザーがサインアップする際に、ユーザーのタイムゾーンをフォームで選択してもらうのではなくて、自動で設定したいと考えました。これからサインアップするユーザーには、できるだけフォーム入力の負担をかけたくありませんので。
— 環境 —
rails-4.0.1
devise-3.2.2
【追記 2014/03/09】
やはり、Thread.current[:request] を用いて、モデルから request 変数にアクセスする方法は良い設計ではないと思いまして、別エントリーで実装をリファクタリングしました。
ユーザーのタイムゾーンを自動で保存するコードをリファクタリング | EasyRamble
【追記ここまで】
detect_timezone_rails, jquery-cookie-rails をインストール
調べたところ、detect_timezone_rails という便利そうな gem を発見。サインアップ時に、この gem で自動判定されクッキーに保存されたユーザーのタイムゾーン値を、users テーブル(User モデルのテーブル)に保存します。
Gemfile に以下を書いて、bundle install。
Gemfile
1 2 |
gem "detect_timezone_rails", "0.0.5" gem "jquery-cookie-rails", "1.3.1.1" |
timezone.js.coffee を作成
アセットパイプラインで読み込まれるように、以下を指定。
app/assets/javascripts/application.js
1 2 3 |
//= require detect_timezone //= require jquery.detect_timezone //= require jquery.cookie |
timezone.js.coffee を作成します。CoffeeScript で timezone という名前の cookie をセット。
app/assets/javascripts/timezone.js.coffee
1 2 3 |
$ -> unless $.cookie('timezone')? $.cookie('timezone', $("html").get_timezone(), {expire: 1}) |
Devise::RegistrationsController#build_resource をオーバーライドしてタイムゾーンを設定
認証エンジンとして Devise を使っていますので、Devise::RegistrationsController#build_resource をオーバーライド。User モデルの table に time_zone というカラムを作成して、それにユーザーのタイムゾーン値を保存します。build_resource はユーザーがサインアップでフォーム送信後、create アクション実行時に呼ばれるメソッドです。
app/controllers/users/registrations_controller.rb
1 2 3 4 5 6 |
def build_resource(hash=nil) hash[:time_zone] = User.get_user_timezone # ... super end |
User モデルに get_user_timezone を実装
User モデルに get_user_timezone クラスメソッドを実装しますが、ここで問題が一つ。モデルでは、cookies メソッドが使えず、そのままでは cookie を取得できません。これを回避するため、まず先に以下のコードを ApplicationController に書きました。
app/controllers/application_controller.rb
1 2 3 4 5 6 7 8 9 10 |
class ApplicationController < ActionController::Base before_filter :set_request_filter def set_request_filter Thread.current[:request] = request end #... end |
スレッドローカル変数に request を保持しておく。そして、モデル内で次のように書くと、timezone.js.coffee でセットした timezone クッキーを取得できます。
1 2 |
request = Thread.current[:request] tz = request.cookies["timezone"] |
最後に、User.get_user_timezone を実装しました。
app/models/user.rb
1 2 3 4 5 6 7 |
def self.get_user_timezone request = Thread.current[:request] tz = request.cookies["timezone"] timezones = ActiveSupport::TimeZone::MAPPING.select{ |k, v| v == tz }.keys timezone = timezones.select{ |t| t == tz.gsub("_", " ").split("/").last }.first || timezones.first || "Tokyo" timezone end |
メソッドの後半でごちゃごちゃやってるのは、timezone.js.coffee に書いた detect_timezone_rails の get_timezone メソッドでクッキーにセットされたタイムゾーン値を、Rails の ActiveSupport::TimeZone.MAPPING で得られるものと対応させるため。
get_user_timezone メソッドは、controller(registrations_controller.rb)に定義すべきかもしれません。モデルが request(HTTPリクエスト情報の変数)を持つのは、あまり良い設計とは思えませんので。controller に定義するのであれば、普通に cookies メソッドでタイムゾーンのクッキーを取得できるので、スレッドローカル変数も不要になります。
なんか結構面倒くさくて、ベストな方法ではない感じがするのが気になります…。
- – 参考リンク –
- scottwater / detect_timezone_rails
- Railsアプリでユーザのタイムゾーンを取得する方法 – tsimoのメモ
- How to get the actual HTTP request from a model in rails 3? – Stack Overflow
- railsでrequest変数をmodelで使う方法:short rails:Media hub
- Rails の関連記事
- RailsでMySQLパーティショニングのマイグレーション
- Rails ActiveRecordでdatetime型カラムのGROUP BY集計にタイムゾーンを考慮する
- RailsプラグインGemの作成方法、RSpecテストまで含めたrails pluginの作り方
- RailsでAMPに対応するgemをリリースしました
- Railsでrequest.urlとrequest.original_urlの違い
- Railsでwheneverによるcronバッチ処理
- Google AnalyticsのRails Turbolinks対応
- Railsアプリにソーシャル・シェアボタンを簡単設置
- Rails監視ツール用にErrbitをHerokuで運用
- Facebook APIバージョンのアップグレード手順(Rails OmniAuth)
Leave Your Message!