Scope ...split parametru SQL

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 :wink: , ż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. :wink:

Dzięki!