■flashを使って、レンダリングとリダイレクトの違いを学ぶ。flashの理解にも良い
【1.flashの原則】
flashに格納されたオブジェクトは、そのセッションの次のリクエストの処理が終わるまでの間、使用可能
となり、その後、自動的に削除される。
そして、これを読むと、flashはリダイレクトと一緒に使うのであって、renderと共に使うことはほぼない
ということが分かる。
【2.例、検索結果をメッセージとして出す】
▼controller(serch_controller)
pattern = params[:query]
split_pattern = pattern.split()
products = Product.find(:all)
@products = []
products.each{|product|
product_title = product.title.downcase
matched_count = 0
split_pattern.each{|pat|
pat = pat.downcase
break unless product_title.include?(pat)
matched_count += 1
}
@products << product if split_pattern.size == matched_count
}
flash[:notice] = "#{@products.size} matched"
#redirect_to :action => "results"
#@items = @products
render(:action => "results")
end
def results
#@items = @products
end
▼view(result.rhtml)
<% @page_title = "検索結果" -%> <div class="results" > <% for product in @products -%> <div class = "catalogentry"> <%= h(product.title) %> </div> <% end -%> </div>
まず、文字列をGETで受け取り、それを含む商品を表示しようという作戦である。flashには、
何件一致したかが書き込まれる。
【3.レンダリング】
上の例ではrenderを使っている。レンダリングの良い所は、処理結果の変数(@product)を
そのまま使えるところにある。そもそもレンダリングは、処理結果を別のビューに反映させるもの
なので当たり前だが・・・。
これを実行すると、結果として、「0 matched」のようにflashメッセージがでる。しかし、ここで奇妙
なことが起る。それは、その場でもう一度リクエスト(他のページに移ったり)しても、まだメッセージ
が表示されるということ。もう一度どこかに移ると消える。
つまり、flashは一回分しか表示されないのに、2回表示されてしまう。
【4.リダイレクト】
リダイレクトはレンダリングと違って、処理結果をそのまま反映するということはない。リセットされる。
よって、上のコードのまま、ただリダイレクトすると、@productの結果は反映されない。
@items = @products
redirect_to :action => "results"
render(:action => "results")
end
def results
@items = @products
end
※viewは、for item in @itemsのようにする。
とかしてもだめである。reirectした瞬間に、@productsが飛んでしまうので、nll.eachでエラーする。
ここは、@productをセッションで保持する必要がある。→フィルタの導入
before_filter :find_prod
private
def find_prod
@products = session[:products] ||=
end
これで、viewの方は、@productsで参照できる。もちろん、resultで@items=〜などしなくてOK。
これによって、
products = Product.find(:all)
@products =
の@products=は不要になる。むしろ、あると面倒。これを書くと、find_prodによりsessionデータが
常に入ってしまうので、sessionデータは更新できず、最初に検索した結果のまま表示されてしまう。
しかし、逆に、これを書かないと大変なことになる。sessiionデータに@products << productとするので、
検索結果がたまっていってしまう。結論、次のようにする。@products= → @products.clear
すると、万事解決!
【5.レンダリングの時のメッセージの問題点】
レンダリングの際に、メッセージが2回表示されるのは、レンダリングが「一連の処理」だからである。
リダイレクトの場合、flashにメッセージを格納した後に、redirectする。これは新たなリクエストなので、
このリクエストの全処理、(この処理中にviewによってflashが表示される)、が終わると、メッセージ
は消える。
最初のリクエスト(メッセージがflashに入る)→リダイレクト+その処理(flash表示)=2回目のリクエスト
→2回目のリクエスト終了(flashが破棄)
レンダリングの場合は、こうだ。
最初のリクエスト(メッセージがflashに入る→レンダリング→flashが表示される)
つまり、最初のリクエストで、flashの表示まで言ってしまうのである。リダイレクトのように、2回目のリクエスト
は入らない。よって、メッセージが表示された状態からページの移動(新たなリクエスト)をしても、それは
2回目のリクエストなので、flashのメッセージは保持されていて、表示されてしまう。新たなリクエストの
処理が終わると破棄される。
ポイントは、リクエストは、リダイレクト+処理(この中で表示される)と、処理も含まれていることだ。
よって、renderを使う時には、フィルタで対処する。
after_filter :flash_clear
def flash_clear
flash[:notice] = nil
end
このように、メソッドを抜ける際に、flashをクリアする。よって、コードはこうなる。
class SearchController < ApplicationController layout "admin" before_filter :find_prod after_filter :flash_clear def search #う〜ん。railsのfindを使えば、完全一致でなく、含まれる一致も簡単に書ける気がする。 pattern = params[:query] split_pattern = pattern.split() products = Product.find(:all) @products.clear #クリアしないと、セッションに保持されたままなので、検索する度につみあがる products.each{|product| product_title = product.title.downcase matched_count = 0 split_pattern.each{|pat| pat = pat.downcase break unless product_title.include?(pat) matched_count += 1 } @products << product if split_pattern.size == matched_count } flash[:notice] = "#{@products.size} matched" #redirect_to :action => "results" render(:action => "results") end def results end private def find_prod @products = session[:products] ||= [] end def flash_clear flash[:notice] = nil end end
※リダイレクトを用いる際には、afterフィルタをコメントアウトしないと、メッセージが表示されない。
それ以外の部分は変えないでよいようなコードになっている。