flashと、リダイレクトとレンダリング

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フィルタをコメントアウトしないと、メッセージが表示されない。
 それ以外の部分は変えないでよいようなコードになっている。