Railsでlike式を使った検索を行う場合、
Foo.find(:conditions => ["text like ?", "%"+params[:word]+"%"])
という方法が常套手段のようだが、PostgreSQLな環境では、フォームのword欄に'む%り'のような文字列を入れられると、「じゅげむじゅげむごこうのすりきれ」といったエントリにもマッチしてしまった。これは非常によろしくない。
で、安易に、
Foo.find(:conditions => ["text like ?", "%"+params[:word].gsub(/%/, '\%')+"%"])
とかエスケープしようとすると、プレースホルダの変換が効いてしまい、
SELECT * From foos WHERE (text like '%む\\%り%') ...
てな具合に'\'がエスケープされたSQLが発行されてうまくいかない。
ソースを当たってActiveRecord::ConnectionAdapters::Quoting.quote_string(s)らしいという所まではつきとめ、書き換えたら一応うまくいったが、INSERTとかにも使われていそうなので、副作用が怖い。
とかなんとか半日ぐらい唸っていたら、突然へんなのが降りてきた。
まず、コントローラに以下のメソッドを追加
def escape_for_like(s, chr) s.downcase.gsub(/\[/, chr+'[').gsub(/%/, chr+'%').gsub(/_/, chr+'_') end
で、以下のようなfindを書く
Foo.find(:conditions => ["lower(text) like E? ESCAPE 'A'", "%"+escape_for_like(params[:word], 'A')+"%"])
解説すると、
というわけで、マニュアルに
とか、
とかへんてこりんな仕様を書かずに、一言
と書くだけで済む。かな。
さらに
def escape_for_like(s, chr) s.gsub(chr, chr*2).gsub(/\[/, chr+'[').gsub(/%/, chr+'%').gsub(/_/, chr+'_') end
で、
Foo.find(:conditions => ["text like E? ESCAPE 'A'", "%"+escape_for_like(params[:word], 'A')+"%"])
とやれば、大文字小文字交じりでもOKだった。
[03/06 追記] flash.keep を追加してフラッシュエントリも保持するように修正
あるデータを表示して、それを確定するか破棄するか選択するようなアプリケーションを考える。
コントローラのメソッドは以下のような感じ。
show -+- commit | +- reject
app/controller/application.rb に以下の2メソッドを追加
# submit ボタンのリダイレクト def redirect_to_submit_action raise if params[:submit].size != 1 session[:stored_params] ||= Hash.new params[:orig_action] = params[:action] params[:action] = params[:submit].keys.first h = params.hash.to_s session[:stored_params][h] = params flash.keep redirect_to :action => params[:action], :id => params[:id], :k => h rescue session[:stored_params].delete(h) if h render :text => '<p>Redirect error</p>', :layout => 'application' return false end # パラメータの復元 def recover_params h = params[:k] or return false if session[:stored_params] && session[:stored_params][h] params.replace(session[:stored_params][h]) session[:stored_params].delete(h) end end
redirect_to_submit_actionでのredirectを、単に
redirect_to params
のように書いてしまっても一応動くが、その場合ブラウザのロケーションバーに他のパラメータがずらずらと並んでしまい、よろしくない。
対象となるコントローラでは次のようにフィルタを設定する。
class FooController < ApplicationController before_filter :recover_params, :only => [ :commit, :reject ] before_filter :redirect_to_submit_action, :only => :dummy_redirect : def commit # commit 処理 end def reject # reject 処理 end end
app/view/foo/show.rhtml のフォームでは:action に dummy_redirectを指定する。
<%- form_tag :action => 'dummy_redirect' do -%> : <%= submit_tag '確定', :name => 'submit[commit]' %> / <%= submit_tag '破棄', :name => 'submit[reject]' %> <%- end -%>
これで、[確定]ボタンをクリックすればcommitが、[破棄]ボタンをクリックするとrejectが呼ばれる。