2013-09-08 2 views
0

Я пытаюсь создать фильтр для класса Article, который использует SQL-запрос, построенный на лету, на основе параметров, представленных в форме HTML. Поскольку у меня есть несколько отношений «многие ко многим», я не могу использовать Article.where (я так не думаю). Хотя следующий код работает, я не уверен, является ли это наиболее эффективным способом выполнения этого запроса и насколько он безопасен. Я пытаюсь защитить SQL-инъекцию, используя? ключевое слово в строке sql (соглашение la la Rails), но хотелось бы убедиться, что этого будет достаточно. Любой совет, как я могу сделать это более элегантным?Создание настраиваемого метода фильтрации в Rails/SQL

def self.filter(hash) 
    hash.delete_if {|k,v| v == ""} 
    hash[:writer_type] = (hash[:writer_type]) if hash[:writer_type] != nil 
    sql_base = "select distinct articles.* from articles 
     join tags 
     on tags.article_id = articles.id 
     join categories 
     on tags.category_id = categories.id 
     left outer join itineraries 
     on itineraries.article_id = articles.id 
     left outer join cities 
     on itineraries.city_id = cities.id 
     join users 
     on users.id = articles.user_id" 

    condition_array = [] 
    key_array = [] 
    hash.each_key {|key| key_array << key} 
    key_array.each_with_index do |key, i| 
     operator = "and" 
     operator = "where" if i == 0 
     case key 
     when :writer 
     sql_base << "\n#{operator} users.username like ?" 
     condition_array << hash[:writer] 
     when :writer_type 
     sql_base << "\n#{operator} users.status in (?)" 
     condition_array << hash[:writer_type] 
     when :city 
     sql_base << "\n#{operator} cities.name like ?" 
     condition_array << hash[:city] 
     when :category 
     sql_base << "\n#{operator} categories.name like ?" 
     condition_array << hash[:category] 
     end 
    end 
    sql_array = [sql_base,condition_array].flatten 
    articles = Article.find_by_sql(sql_array) 
    articles 
    end 

ответ

1

Конечно, вы должны быть в состоянии сделать что-то вроде этого:

q = Articles.join(:tags #, etc) 

if condition 
    q = q.joins(:user).where("users.status in ?", hash[:writer_type]) 
else 
    q = q.joins(:cities).where("cities.name LIKE ?", hash[:city]) 
end 

q 

Это работает, потому что ActiveRecord::Relation только выполняет запрос на первом доступе. Поэтому, пока вы не вызовете to_a или не перечислите записи, вы можете продолжить цепочку на объект.

Если вы делаете сложные запросы, вы можете захотеть взглянуть на squeel Это позволит вам переписать ваши условия, как

q.where { users.status >> my { hash[:writer_type] } } 

или

q.where { cities.name =~ my { hash[:city] } }