Question

I have the following query:

select * from email_contacts
where extract(month from created_at) = 4
and call_type = 'Membership NPS'
and email NOT IN
(
    select invite_email from nps_responses
    where extract(month from fs_created_at) = 4
    and invite_email is not null
)

Then I have a rails 4 app that has two corresponding models, EmailContact and NpsResponse.

How can I convert this query to run within my controller method in Rails? I need to select all email_contacts with the above criteria.

Was it helpful?

Solution

This might not be the perfect "Rails way" solution, but you could to do it by using find_by_sql. Using find_by_sql will still retrieve instantiated objects for your model object:

EmailContact.find_by_sql(your_sql_statement)

Use Bind Variables

To prevent SQL injection, you should use bind variables with the find_by_sql method (bind variables are placeholder parameters represented by the ? character. The find_by_sql method will bind the parameter values before executing the statement.) This is highly recommended from a security perspective.

Here's an example of using your SQL statement with bind variables in the find_by_sql method:

EmailContact.find_by_sql("select * from email_contacts
          where extract(month from created_at) = ?
          and call_type = ?
          and email NOT IN
          (
              select invite_email from nps_responses
              where extract(month from fs_created_at) = ?
              and invite_email is not null
          )", 4, 'Membership NPS', 4)

Things to notice:

  • The first parameter to the find_by_sql method is the SQL statement as a string value. In this particular SQL string, there are three ? characters as the placeholder variables.
  • The next three parameters in find_by_sql are the values to be bound to the ? character in the SQL statement.
  • The order of the parameters are important - the first bind variable parameter will bind to the first ? character, etc.
  • You can have a variable amount of bind variable parameters, as long as the number of ? matches the number of bind variable parameters.

For some better code organization, you can have some sort of helper method to construct the SQL statement with the bind variables. Kind of like this:

def email_contacts_SQL
   sql = "select * from email_contacts
          where extract(month from created_at) = ?
          and call_type = ?
          and email NOT IN
          (
              select invite_email from nps_responses
              where extract(month from fs_created_at) = ?
              and invite_email is not null
          )"
end

And then use find_by_sql with your helper method:

EmailContact.find_by_sql(email_contacts_SQL, 4, 'Membership NPS', 4)

The official Rails API doc has more examples of how to use find_by_sql.

Warning: If you use find_by_sql, you lose database agnostic conversions provided by ActiveRecord. The find_by_sql method executes the SQL statement as is.

OTHER TIPS

Something close to what you would write in ActiveRecord, this uses bind variables and protects against sql injection

EmailContact.where("month from created_at ?", some_value).
  where(:call_type => 'Membership NPS').
  where("email NOT IN (
              select invite_email from nps_responses
              where extract(month from fs_created_at) = ?
              and invite_email is not null
          )", some_value).all
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top