OpenID 開発ログ。

すでにローカルユーザーテーブルを持っている場合にどうOpenID対応するか。

まず、スキーマ変更。

class AddOpenIdSupportForWjUsers < ActiveRecord::Migration
  def self.up
    # add type
    add_column :wj_users, :type,  :string, :limit => 64, :null => false, :default => "WjLocalUser"
    # add open_id column
    add_column :wj_users, :open_id_uri,  :string, :limit => 255, :null => true, :default => nil
    # change :login_password_hash, :email change nullable.
    change_column :wj_users, :login_password_hash, :string, :limit =>  32,   :null => true, :default => nil
    change_column :wj_users, :email, :string, :null => true, :default => nil
  end

type列重要。OpenID ユーザーと、LocalUser は、ポリモーフィズムで解決します。単一テーブル継承戦略で。
そのため、パスワードとメールアドレスの列はnull値を可能に。

次に、クラスを作成。

$ ruby script/generate model WjLocalUser
$ ruby script/generate model WjOpenIdUser

でいずれも、元々持っているユーザークラスを継承する。

# app/models/wj_local_user.rb
class WjLocalUser < WjUser; end;

# app/models/wj_open_id_user.rb
class WjOpenIdUser < WjUser; end;

であとは、パスワード や メールアドレス列 に関連するメソッドを既定のクラスから、継承した子クラスに移動。

# app/models/wj_user.rb
class WjUser < ActiveRecord::Base
-  def self.authenticate(name, password)
-    user = self.find_by_login_name(name)

# app/models/wj_user.rb
class WjLocalUser < WjUser
+  def self.authenticate(name, password)
+    user = self.find_by_login_name(name)

最後にユーザー作成している部分(おそらくコントローラーコード)をUser.new => LocalUser.new とするか OpenIdUser.new とするか決めていけば完了です。Select する部分に関しては基底クラスのfind_* メソッドでOK(レコードがオブジェクト化されるときに、type列をみてActiveRecordが自動的にクラスを判断)。

これでモデルクラスはほぼOKで。