Tuesday, December 9, 2014

SQL queries in Rails

Recently, I needed to write a SQL query that would make multiple selections using insensitive search. The query would be done in either raw SQL and/or using Rails helpers.

For this task, I needed to select users if they practiced certain type of law according to the search terms. As such, there are Users who have many Practices through Practice_Users, and Practices have many Users though Practice_Users. I wanted to select Users with Practices if search terms matched the Practices. However, there are several stop words to avoid: for example, if two records in the database list 'Family law' and 'Family', and I want to search for 'family', then both records should be returned.

My initial solution was the following:
scope :by_practice, -> (pract) {joins(:practice_users).where("practice_users.practice_id IN (SELECT id FROM practices where name IN (?))", pract )}

However, this solution would work if only the search terms matched the database records exactly. Thus, searching for 'family' would exclude 'Family law'.

The second solution was the following:
scope :by_practice, -> (pract) {joins(:practice_users).where("practice_users.practice_id IN (SELECT id FROM practices WHERE #{pract})")}

pract = practices.map { |e|  "name iLIKE '%#{e}%'"}.join(" or ")


However, this solution is vulnerable to SQL injection due to the use of #{} in sql string i.e., #{pract}.

The third and final solution was sort of a combination of the first two: it used ? substitution in Rails and a regex expression in Postgres:

scope :by_practice, -> (pract) {joins(:practice_users).where("practice_users.practice_id IN (SELECT id FROM practices WHERE name ~* ?)", pract)}

pract = practices.map {|e| "#{e}"}.join('|')

No comments :

Post a Comment