Cześć,
Mam w modelu taki scope:
scope :finder_customers, ->(q) { where("(customers.name like :q or customers.address_city like :q or customers.pesel like :q)", q: "%#{q}%") }
no i jest OK, gdy parametr q
przyjmuje wartości typu:
“Kowal” lub “Warsza” lub “811210”
klauzula “where” w SQLu wygląda wtedy jakoś tak:
... where customers.name like "Kowal" or customers.address_city like "Kowal" or customers.pesel like "Kowal"
lub
... where customers.name like "Warsza" or customers.address_city like "Warsza" or customers.pesel like "Warsza"
Ale parametr q
może przyjmować wartość złożoną z kilku słów rozdzielonych spacją np.:
“Kowal Warsza” i wtedy chciałbym aby zapytanie SQL wyglądało tak:
... where (customers.name like "Kowal" or customers.address_city like "Kowal" or customers.pesel like "Kowal") AND (customers.name like "Warsza" or customers.address_city like "Warsza" or customers.pesel like "Warsza")
Czuję podskórnie , że jest na to już jakiś patent i nie muszę pisać własnej funkcji, która zbuduje mi takie zapytanie.
Podpowiedzcie proszę, jak elegancko zbudować ten Scope
zrobiłem tak:
scope :finder_customer, ->(q) { where( my_sql("#{q}") ) }
def self.my_sql(query_str)
array_params_query = query_str.split
sql_string = ""
array_params_query.each_with_index do |par, index|
sql_string += " AND " unless index == 0
sql_string += one_param_sql(par)
end
sql_string
end
def self.one_param_sql(query_str)
"(customers.name like '%#{query_str}%' or
customers.address_city like '%#{query_str}%' or
customers.pesel like '%#{query_str}%')"
end
Działa dobrze, ale mam wątpliwości, czy to “ładny” i “railsowy” kod.
Co sądzicie o tym rozwiązaniu?
Jeśli chodzi o podejście SQL - na początek wystarczy. Jak będziesz miał problemy wydajnościowe to zainteresuje się full text searchem w postgresie albo pełnotekstowymi silnikami typu elasticsearch.
Co do kodu - jest tu sql injection a także można troszkę go uprościć.
def self.my_sql(query_str)
query_str.split.map { |par| one_param_sql(par) }.join(" AND ")
end
def self.one_param_sql(query_str)
escaped_query_str = sanitize("%#{query_str}%")
"(" + %w(name address_city pesel).map { |column| "customers.#{column} like #{escaped_query_str}" }.join(" OR ") + ")"
end
1 Like
…wiedziałem, że mój kod, to jakaś rzeźba.
Dzięki!