2013-09-20 95 views
2

使用Rails 3中的Active Record模型(Postgres的DB)我要帶的東西用戶搜索輸入這樣的:Rails:如何獲取逗號分隔值並構建SQL查詢?

"some string, some other string, final string" 

從那裏我想拆就逗號串並構建一個SQL查詢,看起來像這樣

SELECT * FROM items WHERE item_name SIMILAR TO '%(some string)%' OR item_name SIMILAR TO '%(some other string)%' OR item_name SIMILAR TO '%(final string)%' 

我竭力想出一種方式,我比較熟悉的Ruby的語法來構建這個查詢。

+1

'海峽= 「一些字符串,其他一些字符串,最後一個字符串」 .split( ''){地圖|。S | 「%(#{s.strip})%」}'然後'相對於任何(ARRAY#{str})'可能工作嗎? – j03w

+0

@ j03w:我無法與SIMILAR TO一起工作,因此我認爲你會陷入LIKE或跳到真正的正則表達式。 –

回答

3

我會跳過相似並直行到POSIX regexes,我敢肯定SIMILAR TO將被轉換爲正則表達式內部何必呢?此外,~將讓您使用ANY產生一個很好的可讀表達式。你可以做這樣的事情:

str = 'some string, some other string, final string' 
items = Item.where('item_name ~ any(array[?])', str.split(/\s*,\s*/)) 

這最終將運行SQL這樣的:

select "items".* from "items" where item_name ~ any(array['some string', 'some other string', 'final string']) 

,這將產生相同的結果作爲相似到版本不扯皮一串字符串。

如果你面對的是一個可以包含正則表達式元字符的CSV字符串,那麼你可能希望在混合中引入一些轉義。 Backslashing任何非字母數字應該是足夠安全:

str = 'some string, some other string, final string' 
pats = str.split(/\s*,\s*/) 
      .map { |s| s.gsub(/\p{^Alnum}/) { '\\' + $& } } 
items = Item.where('item_name ~ any(array[?])', pats) 

切換到LIKE也是一種選擇,那麼你只需要擔心_%

str = 'some string, some other string, final string' 
pats = str.split(/\s*,\s*/) 
      .map { |s| s.gsub(/[_%]/, '%' => '\\%', '_' => '\\_') } 
      .map { |s| '%' + s + '%' } 
items = Item.where('item_name like any(array[?])', pats) 

在現實生活中您可以將實用程序方法中的逃逸亂七八糟(以及「添加LIKE百分號」混亂)變得更清晰。

如果您不關心case,那麼您可以使用~*ilike進行不區分大小寫的模式匹配。

+0

太棒了,謝謝 - 作品一種享受!我應該在這裏擔心SQL注入還是Rails爲我逃避? – Simon

+0

當代替'?'佔位符時,Rails會處理引用。 –

1

這應該起作用。

search_string = "some string, some other string, final string" 

strings = search_string.split(", ").map{|s| '%' + s + '%'} 
instructions = '' 
strings.each do |string| 
    instructions << ' OR ' unless instructions == '' 
    instructions << 'item_name like ?' 
end 
DatabaseTable.where(instructions, *strings) 
3

嘗試這種方式,

string = 'some string, some other string, final string' 
patterns = search_string.split(", ").map{|str| "%#{str}%" } 
items = Item.where('item_name like any(array[?])', patterns)