ちょっと高機能なチャットCGIになると必ずついている”現在ログインしているユーザ一覧を表示”を作ってみました。
まずはテーブル定義から
create table logon_users ( id int not null auto_increment, user_id int null, updated_at datetime not null, constraint fk_user_id foreign key(user_id) references users(id), primary key(id) );
Rails(RoRと呼んでいましたが、今後はこちらで)のルールに従った構成になっています。ActiveRecordのテーブルリレーション機能を使うため、user_idはnull可にしてます。
テーブルの親子関係を1レコード:1レコードのリレーションに設定した場合、新しい子レコードを生成した際に、古い子レコードとのリレーションを切断する必要があります。この際にRailsは古い子レコードの外部キーカラムをnullに更新しようとします。このため、nullを許容する必要があります。
モデルを生成。
ruby script/generate model LogonUser
リレーションを設定。usersテーブル(親)、logon_usersテーブル(子)。関係は1:1。親テーブルのリレーション設定が”has_one”になっている点に注目。子の方は”belongs_to(所属している)”としか設定しないでよい。子を何人まで保持するかは親側が決めるという思想。上にあるとおり、二人目の子が設定された場合、自動的に一人目との関係は解除される。
class User < ActiveRecord::Base has_one :logon_user (略)
class LogonUser < ActiveRecord::Base belongs_to :user end
ここまででルール設定終了。あとは、実際にログオン情報を記録するだけ。どのタイミングで更新すればいいのか悩むところだけど
の3つに設定。最終発言時刻などを記録するなら、メッセージ送信時などにも必要ですな。
まずはログイン時に実装。ログイン処理はRails本のユーザ管理を参考に作ってます。そのうち、こちらも紹介。
def login if request.get? session[:user_id] = nil @user = User.new else @user = User.new(params[:user]) logged_in_user = @user.try_to_login if logged_in_user session[:user_id] = logged_in_user.id session[:name] = logged_in_user.name logon_user = LogonUser.find_by_user_id( logged_in_user.id ) || LogonUser.new( :user => logged_in_user ) logged_in_user.logon_user = logon_user redirect_to(:controller => "admin", :action => "index" ) else flash[:notice] = "ユーザ/パスワードの組み合わせが無効です" end end end
実際の処理は強調表示している部分。DBからfind_by_user_idで更新対象レコードを探す。見つからなかったら、newで新規作成。次の行で親子関係を設定。この時点で自動的にUpdateが実行される。
続いてログアウト処理。
LogonUser.destroy_all( ["user_id = ?", session[:user_id]] ) session[:user_id] = nil flash[:notice] = "ログアウトしました" redirect_to(:action => "login")
「ログインしているユーザー一覧を保持するテーブル」なので、ログアウトしたら削除なのだ。destroy_allで条件を指定して実行。
んで、リスト更新要求時。これはログイン中に定期的に実行されるので、ハートビート(心音)確認するため。ブラウザを閉じるなどでログアウト処理を行わなかった場合、ハートビート最終時間を元に古いデータを削除する。
def checklogonuser logon_user = LogonUser.find_by_user_id( session[:user_id] ) logon_user.save @users = LogonUser.find( :all, :order => "user_id" ) render(:layout => false) end
find_by_user_idで対象レコードを特定。んで、いきなりsave。updated_atというRailsが特殊扱いするカラム名を使っているため。名前の通り、更新の際に最終更新日時を設定してくれる。古いデータの削除処理はまだ入れてないです。システムからのチャット発言(○○さんが入室しました)などと一緒にやろうかな、と。
最後の表示。無駄にAJAXを使っております。メイン画面のRHTMLファイルで。
<div id="userlist"> <%= render :partial => 'userlist' %> </div> <%= periodically_call_remote(:update => 'userlist', :url => { :action => :checklogonuser }, :frequency => 30 ) %>
30秒おきに自動更新。これだけでうまいことやってくれるんだから、すごいもんだ。
続いて、checklogonuserのビュー。
<%= render :partial => 'userlist' %>
呼び出しルートが2種類あるため、実処理を別に分けている。2種類とは「初期表示(通常リクエスト)」と「AJAX表示」。
実体はこちら。
参加者: <% for user in @users %> <%=h user.user.name %> <% end %>
んで、コントローラー。
def checklogonuser logon_user = LogonUser.find_by_user_id( session[:user_id] ) logon_user.save @users = LogonUser.find( :all, :order => "user_id" ) render(:layout => false) end
findでデータを設定して、renderで共通レイアウト出力を無効化。(HTMLに填め込む形で利用するから)
と、こんなところでございます。開始が25時と遅かったんで、システムメッセージまで手が回りませんでした。
んで、こんな駄文、ネットに上げていいんかね?Web2.0的には完成度50%で上げるべきらしいが、完成度1%ぐらいなのですががが。
コメント
えっと、よくわかりませんでした(・∀・)
でも、がんがってるのは分かったので、
てきとにガンガレ(‘A`)b
うん、がんばるお
ありがとう( ゚д゚)
久しぶりにPGらしいBlogで(・∀・)イイ!!
完成度1%で見せたくなる気持ちは、すげー分かるなーw
ちゃんと理解してからまとめて書きたいと思うのだけど、
理解できるかもわからないし、
納得できるクオリティを実現できるのは、さらに先になりそう
つーことで、無知は無知なりにがんがん書いていく方向で