Rails3 + Sorceryで複数のモデル(管理者と一般ユーザとか)を扱う
Deviseが魔術すぎるので一部のユーザには人気のあるシンプル指向のユーザ認証用プラグインSorceryですが、例えば「管理者」と「一般ユーザ」みたいに複数の種類があるアカウントをそれぞれ別のモデルとして扱うのはあまり得意ではありません。どのウェブアプリケーションでも普通はこれくらいのアカウント種別はあるでしょうし、もし一般ユーザに管理側の機能やアカウントが使えるようになってしまっては困るので、うっかりミスでとんでもないことにならないよう、それぞれ別のモデルで扱いたくなるのは当然です。これが出来ないのであれば、折角シンプルさが気に入ってSorceryを使い始めても、すぐに挫折してしまいます。大変残念です。
でも、まあ確かに得意じゃないとはいっても、別に出来ないというわけではありません。Sorceryで複数のモデルで複数のアカウント種別を扱う最も一般的なやり方は、Single Table Inheritance(単一テーブル継承)を利用するものです。備忘録として使い方をまとめておきます。
単一テーブル継承といっても、別にPostgreSQLにあるようなテーブルの継承を利用するような高度なものではありません。単純に、同じテーブルにデータを格納しつつ、typeカラムに入った文字列でアカウントの種別を判定する、というだけのことです。データとしてはそんなしょぼい構成になりますが、アクセスするモデルを別にすることができるのが利点です。
ややこしいのでモデルの例で説明すると、
$ bundle exec rails g sorcery:install
これでusersテーブルを扱うUserモデルが作成されます。これにtypeカラムを追加します。
$ bundle exec rails g migration addTypeToUsers type:string $ bundle exec rake db:migrate
さて、これで親となるモデル「User」が完成しました。Sorceryのマニュアルに従い、app/models/user.rbを編集してSorceryで使えるようにしましょう。initializerの設定も忘れずに。config.user_classはUserで構いません。ひとつだけ、config/initializers/sorcery.rbで継承の利用をtrueにしておくことをお忘れなく。
user.subclasses_inherit_config = true
次に、これを「継承」した子モデルを作成します。そうですね、memberとadminという名前にしましょうか。
railsのジェネレータで作成したモデルはActiveRecord::Baseを継承しています。でもSingle Table Inheritanceのモデルは親テーブルを扱うクラスを継承します。これにより親テーブルのvalidationなどの情報を引き継ぐことができます。またSorcery固有のauthenticates_with_sorcery!も子テーブルでは呼ぶ必要はありません。
class Admin < User end class Member < User end
これで何が出来るようになるかというと、Sorceryの各種メソッドが使えるようになったのはもちろん、例えば
@member = Member.new(params[:member]) @member.save
これでtypeカラムに「Member」という文字列が入った状態でデータが保存されます。保存されるテーブルは「users」です。同じくAdminモデルから作成したオブジェクトではtypeカラムに「Admin」という文字列が入ります。
またそれぞれのモデルからデータを引っ張り出すときには、勝手にtypeカラムの内容を検索条件に追加してくれます。
User.find(params[:id]) #=> SELECT `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1 Member.find(params[:id]) #=> SELECT `users`.* FROM `users` WHERE (`users`.`id` = 2 AND `users`.`type` IN ('Member')) LIMIT 1 Admin.find(params[:id]) #=> SELECT `users`.* FROM `users` WHERE (`users`.`id` = 2 AND `users`.`type` IN ('Admin')) LIMIT 1
あとは、各コントローラでAdminかどうか認証する場合は、application_controller.rbに
protected def is_admin? if current_user && current_user.type == 'Admin' true else false end end def admin_required unless is_admin? redirect_to root_path, :alert => "You are so cool..." end end
こんな感じのフィルタ用メソッドを用意しておけば、管理者のみアクセスさせたいコントローラで
class TopSecretsController < ApplicationController before_filter :admin_required
アクセス制限をかけることができるようになります。「Admin」という文字がハードコーディングされているのがキモいので定数にしておくといいでしょう。
コメントを残す