ユーザー別にデータベース管理
ユーザー別に管理する方法は以下の方法でやってみた。(表計算ソフトに例えれば、ユーザーを識別する列を追加して、フィルターで対象ユーザーだけ表示するようなイメージだ。)
- ユーザー別に管理するためには、softwareテーブルにユーザーを識別するフィールドを追加する。
- 新規作成した時は、ユーザー識別フィールドへ、ログインユーザーのidを記録する。
- 一覧表示や詳細、編集する時は、ユーザー識別フィールドがログインユーザーと同じものしか照会できないようにする。
それでは、作業開始。
user_idフィールドの追加
ユーザーを識別するために、softwaresテーブルにuser_idフィールドを追加した。Login Engineがusersテーブルを利用しているので、そのテーブルと関連を持たせるために、フィールド名はRailsの規約に則りuser_idにする。
- マイグレーションファイルを以下のように設定した。
class AddUserIdToSoftwares < ActiveRecord::Migration def self.up add_column :softwares, :user_id, :integer end def self.down remove_column :software, :user_id end end
- RadRailsの「Rake タスク」タブで、プルダウンメニューのdb:migrateを選択、実行ボタンを押す。
以上で、softwaresデーブルに、user_idフィールドを追加できた。
with_scopeの利用
最初は、conditionsでログインユーザーのidを条件に設定してみた。しかしその後、with_scopeというメソッドの存在を知り、嬉しくなって早速、利用。下記が追記したコードの全体像。オレンジ色の行が追記したコード。
softwares_controller.rbへの追記
class SoftwaresController < ApplicationController include SoftwaresHelper ...(途中省略)... protected def login_user { :find => {:conditions => ["user_id = ?", session[:user].id]}, :create => {:user_id => session[:user].id} } end public def index list render :action => 'list' end def list Software.with_scope(login_user) do @keyword_id = params[:keyword_id] unless params[:keyword_id] == flash[:keyword_id] flash[:keyword_id] = @keyword_id @order_field = params[:order_field] @order_direction = params[:order_direction] if params[:order_field] == flash[:order_field] @order_next_direction = @order_direction == nil ? 'desc' : nil @direction_mark = @order_direction == nil ? '▲' : '▼' flash[:order_field] = @order_field #conditions = ["user_id = ?", session[:user].id] conditions = ["keyword_id = ?", @keyword_id] unless @keyword_id == nil order = "#{@order_field} #{@order_direction}" unless @order_field == nil @software_pages, @softwares = paginate :softwares, :per_page => 2, :conditions => conditions, :order => order end end ...(途中省略)... def show Software.with_scope(login_user) do @software = Software.find(params[:id]) end end def new @software = Software.new end def confirm_create @software = Software.new(params[:software]) render :action => 'new' unless @software.valid? end def create @software = Software.new(params[:software]) @software.user_id = session[:user].id if params[:btn_cancel] render :action => 'new' elsif @software.save flash[:notice] = localize(:model, 'software') + localize(:command, :successfully_created) redirect_to :action => 'list' else render :action => 'new' end end def edit Software.with_scope(login_user) do @software = Software.find(params[:id]) end end def confirm_update @software = Software.new(params[:software]) render :action => 'edit' unless @software.valid? end def update Software.with_scope(login_user) do @software = Software.find(params[:id]) if params[:btn_cancel] @software.attributes = params[:software] render :action => 'edit' elsif @software.update_attributes(params[:software]) flash[:notice] = localize(:model, 'software') + localize(:command, :successfully_updated) redirect_to :action => 'show', :id => @software else render :action => 'edit' end end end def confirm_destroy Software.with_scope(login_user) do @software = Software.find(params[:id]) end end def destroy Software.with_scope(login_user) do if params[:btn_cancel] redirect_to :action => 'list' else Software.find(params[:id]).destroy redirect_to :action => 'list' end end end end
with_scopeのために追記した部分を詳しく見てみると...
- login_userというメソッドを定義して、単にスコープの条件を返すようにしている。with_scope()..doを複数書く時に、条件が一箇所にまとまっている方が見通しが良い。
- :findには、findやpaginateの読み出しの制限条件を書く。ここでは、ログインユーザーのデータだけ見ることが出来る条件。
- :createには、新規作成createの制限条件を書く。ここではcreateした時に、ログインユーザーのidがデフォルトで設定される条件。
protected
def login_user
{ :find => {:conditions => ["user_id = ?", session[:user].id]},
:create => {:user_id => session[:user].id} }
end
- with_scope()..doのブロックで囲ってあげれば、そのブロックでは、データベースへのアクセスがwith_scopeの条件範囲に限定される。
def show
Software.with_scope(login_user) do
@software = Software.find(params[:id])
end
end
- しかし、ここで一つ問題が発生した。標準のscaffoldで確認してみると、下記のようにブロックで囲ってもwith_scopeの条件は無視されてしまう。どうやら、newやsaveはwith_scopeでは制限できないようだ。
def create
Software.with_scope(login_user) do
@software = Software.new(params[:software])
if @software.save
flash[:notice] = 'Software was successfully created.'
redirect_to :action => 'list'
else
render :action => 'new'
end
end
end
- 新規作成で制限できるのはcreateだけ。with_scopeを利用するなら、以下のようにすれば制限されるようだ。
def create
Software.with_scope(login_user) do
if @software = Software.create(params[:software])
flash[:notice] = 'Software was successfully created.'
redirect_to :action => 'list'
else
render :action => 'new'
end
end
end
- ところが、jascaffoldではキャンセルボタンを押した時に、入力されたフォームの内容を保持して新規作成ページへ戻る必要がある。そのため、最初の@software = Software.new(params[:software])は、どうしても必要なコードになる。with_scopeを使うのはやめて、下記のようにuser_idにログインユーザーのidを代入することにした。
def create
@software = Software.new(params[:software])
@software.user_id = session[:user].id
if params[:btn_cancel]
render :action => 'new'
elsif @software.save
flash[:notice] = localize(:model, 'software') + localize(:command, :successfully_created)
redirect_to :action => 'list'
else
render :action => 'new'
end
end
jascaffold生成コードの修正
user_idフィールドを追加したので、その追加による修正も必要だ。修正したのは以下の2ファイル。
softwares_controller.rbへの追記
- hide_feildに、:user_idを追加した。
class SoftwaresController < ApplicationController include SoftwaresHelper hide_field :id, :url, :user_id, :only => [:index, :list, :list_by_keyword, :list_in_order] hide_field :id, :created_on, :updated_on, :user_id, :only => [:new, :confirm_create, :create, :edit, :confirm_update, :update] ...(以下省略)...
views/models/softwares/_hidden.rhtmlへの追記
- _hidden.rhtmlは、新規作成や修正の確認ページでキャンセルボタンを押した時に、直前の入力ページにフォームの内容を保持して戻るために存在している。*1
<%= localized_error_messages_for 'software' %>
<!--[:]-->
<%- unless @controller.class.hidden_field?(@action_name, 'title') -%>
<%= hidden_field "software", "title" %>
<%- end -%>
...(途中省略)...
<%- unless @controller.class.hidden_field?(@action_name, 'user_id') -%>
<%= hidden_field "software", "user_id" %>
<%- end -%>
<!--[:]-->
これでなんとか、ログインユーザー別に管理できるようになった。
参考リンク
以下のページをたいへん参考にさせて頂きました。感謝です!
*1:確認ページではフォームを利用してないので、入力データを保持するためにhidden_fieldで隠れたフォームを作成しているようだ。confirm_create.rhtmlやconfirm_update.rhtmlでは、render :partial => 'show2'とrender :partial => 'hidden'の両方を呼び出している。試しに_hidden.rhtmlを削除してしまったら、キャンセルボタンで戻った時に、今まで入力した内容がクリアされてしまった。