Escaping values in Rails (similar to mysql_real_escape_string()) - ruby-on-rails

I know about prepared statements, but if I'm using raw SQL, does ActiveRecord have a way to manually escape values?
Something like this would be nice:
self.escape("O'Malley") # O\'Malley

You can do:
Dude.sanitize("O'Malley")
or
Dude.connection.quote("O'Malley")
both with the same result: => "'O''Malley'"

A quick dive into the ActiveRecord source reveals its method "sanitize_sql_array" for sanitizing the [string, bind_variable[, bind_variable]] type of sql statement
You could call it directly:
sql = ActiveRecord::Base.send(:sanitize_sql_array, ["insert into foo (bar, baz) values (?, ?), (?, ?)", 'a', 'b', 'c', 'd'])
res = ActiveRecord::Base.connection.execute(sql)

You can easily use the mysql2 gem to do this:
irb(main):002:0> require 'rubygems'
=> true
irb(main):003:0> require 'mysql2'
=> true
irb(main):004:0> Mysql2::Client.escape("O'Malley") # => "O\\'Malley"
=> "O\\'Malley"
Or if using the earlier mysql (not mysql2) gem:
irb(main):002:0> require 'rubygems'
=> true
irb(main):003:0> require 'mysql'
=> true
irb(main):004:0> Mysql.escape_string("O'Malley")
=> "O\\'Malley"
This will allow you to escape anything you want then insert to the db. You can also do this on most models in your rails application using the sanitize method. For instance say you have a model called Person. You could do.
Person.sanitize("O'Malley")
That should do the trick.

If you don't want the extra single quotes wrapping your string that occur when you use the solution posted by #konus, you can do this:
Dude.connection.quote_string("O'Malley")
This returns "O\'Malley" instead of "'O\'Malley'"

Even with Model.find_by_sql you can still use the form where question marks stand in as escaped values.
Simply pass an array where the first element is the query and succeeding elements are the values to be substituted in.
Example from the Rails API documentation:
Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]

In case somebody is looking for a more concrete example of #jemminger's solution, here it is for bulk insert:
users_places = []
users_values = []
timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S')
params[:users].each do |user|
users_places "(?,?,?,?)"
users_values << user[:name] << user[:punch_line] << timestamp << timestamp
end
bulk_insert_users_sql_arr = ["INSERT INTO users (name, punch_line, created_at, updated_at) VALUES #{users_places.join(", ")}"] + users_values
begin
sql = ActiveRecord::Base.send(:sanitize_sql_array, bulk_insert_users_sql_arr)
ActiveRecord::Base.connection.execute(sql)
rescue
"something went wrong with the bulk insert sql query"
end
Here is the reference to sanitize_sql_array method in ActiveRecord::Base, it generates the proper query string by escaping the single quotes in the strings. For example the punch_line "Don't let them get you down" will become "Don\'t let them get you down".

Related

Check if a value exists in a database and return the value if it does Rails

I am running Rails here with a User model that contains an :email_address column
I have an array - emails_to_check[email1,email2,email3] that i want to check if they exist in the User database.
I only want to return the values that DO exist in the database
Here's a simple one-liner for you. There may be more performant ways, but this is maybe the most straight-forward and idiomatic Rails.
emails_to_check = ['email1', 'email2', 'email3']
User.where(email_address: emails_to_check).pluck(:email_address)
Here is the resulting SQL query:
SELECT `users`.`email_address` FROM `users` WHERE `users`.`email_address` IN ('email1', 'email2', 'email3');
so i solved this using a rake task
task :find_users_in_array,[:emails_to_find] => :environment do |task, args|
emails = args[:emails_to_find].split
emails.each do |email|
if User.find_by(email:email)
puts "#{email}"
end
end
end
I can pass in a list using rake:find_users_in_array[email1 email2 email3]

How do I get back an ActiveRecord instead of an ActiveRecord relation?

Using Rails 5. How do I get an ActiveRecord instead of an ActiveRecord relation? I ahve the below model
class State < ActiveRecord::Base
belongs_to :country
...
def self.cached_find_us_state_by_name(name)
Rails.cache.fetch("#{name.upcase} US") do
find_us_state_by_name(name)
end
end
# Look up a US state by its full name
def self.find_us_state_by_name(name)
search_criteria = ["upper(states.name) = ? "]
search_criteria.push( "states.country_id = countries.id " )
search_criteria.push( "countries.iso = 'US' " )
results = State.joins(:country).where( search_criteria.join(' and '),
name.upcase)
end
but when I lookup an item using the methods, waht I get back is an ActiveReocrd relation ...
2.4.0 :004 > State.cached_find_us_state_by_name('Ohio')
=> #<ActiveRecord::Relation [#<State id: 3538, name: "Ohio", country_id: 233, iso: "OH">]>
This is cauisng problems later on, specifically ...
ActiveRecord::AssociationTypeMismatch: State(#70288305660760) expected, got State::ActiveRecord_Relation(#70288290686360)
from /Users/davea/.rvm/gems/ruby-2.4.0#global/gems/activerecord-5.0.1/lib/active_record/associations/association.rb:221:in `raise_on_type_mismatch!'
from /Users/davea/.rvm/gems/ruby-2.4.0#global/gems/activerecord-5.0.1/lib/active_record/associations/belongs_to_association.rb:12:in `replace'
Edit: Per the suggestion given, I changed my methods to
def self.cached_find_us_state_by_name(name)
Rails.cache.fetch("#{name.upcase} US") do
find_us_state_by_name(name)
end
end
# Look up a US state by its full name
def self.find_us_state_by_name(name)
search_criteria = ["upper(states.name) = ? "]
search_criteria.push( "states.country_id = countries.id " )
search_criteria.push( "countries.iso = 'US' " )
results = State.joins(:country).where( search_criteria.join(' and '),
name.upcase).take
end
but alas, I still get an ActiveRecord::Relation
2.4.0 :011 > State.cached_find_us_state_by_name('Ohio')
=> #<ActiveRecord::Relation [#<State id: 3538, name: "Ohio", country_id: 233, iso: "OH">]>
You can use find_by instead of where - the source code for this is simple:
def find_by(*args)
where(*args).take
end
Another way is to call .first or [0] but if your where has a lot of records this will be slow since it's loading them all into memory before selecting the first. If use limit(1) then these methods will be acceptably fast.
There are a variety of ways to get the ActiveRecord object from an ActiveRecord::Relation.The result of the call to
results = State.joins(:country).where( search_criteria.join(' and '),
name.upcase)
returns an ActiveRecord::Relation. This is always the case when you use the where clause in ActiveRecord. In order to get a specific ActiveRecord object, you could:
use the find_by method which returns the first instance in the database that it finds. Note that this does not return all of the records matching the criteria. * For example, User.find_by(first_name: "Mark") will return an ActiveRecord object containing the first record with a first name of "Mark"
Use the first method on the ActiveRecord::Relation collection. For example, User.where(first_name: "Mark").first does the same thing as the previous example.
Lastly, you could write something like this:
User.where(first_name: "Mark") do |u|
puts u.last_name
end
This allows you to iterate through the ActiveRecord::Relation collection one record at a time. So for your example, it would look something like this:
results.each do |res|
# do stuff
end
Most of ActiveRecord methods returns ActiveRecord::Relations in order to provide a fluent API ( syntactically pleasing method chaining if you will ). I think this is a design decision. There are a few method that breaks this, such as first (which really is a call to find_nth!(0) ), last to_a, to_sql and some others.
So, the way I found "around" this is to use .first where I really intended to get one and only one result back, as Mark and maxple are suggesting. I also think its probably one of the only way to do so.
This : https://github.com/rails/rails/blob/603ebae2687a0b50fcf003cb830c57f82fb795b9/activerecord/lib/active_record/querying.rb
And this (to which Querying delegates most of it's methods): https://github.com/rails/rails/blob/603ebae2687a0b50fcf003cb830c57f82fb795b9/activerecord/lib/active_record/querying.rb
are probably worth skimming through.

Converting Rails model to SQL insert Query?

Is there a way to convert a Rails model into an insert query?
For instance, if I have a model like:
m = Model.new
m.url = "url"
m.header = "header"
How can I get the corresponding SQL query ActiveRecord would generate if I did m.save?
I want to get: "INSERT INTO models(url, header) VALUES('url', 'header')" if possible.
Note: I don't want to actually save the model and get the query back (from log file, etc). I want to get the query IF I chose to save it.
On Rails 4.1, I found the below code snippet working:
record = Post.new(:title => 'Yay', :body => 'This is some insert SQL')
record.class.arel_table.create_insert
.tap { |im| im.insert(record.send(
:arel_attributes_with_values_for_create,
record.attribute_names)) }
.to_sql
Thanks to https://coderwall.com/p/obrxhq/how-to-generate-activerecord-insert-sql
Tested in Rails 3.2.13: I think I got it right this time, it definitely does not persist to the db this time. It also won't fire validations or callbacks so anything they change won't be in the results unless you've called them some other way.
Save this in lib as insert_sqlable.rb and you can then
#in your models or you can send it to ActiveRecord::Base
include InsertSqlable
Then it is model.insert_sql to see it.
#lib/insert_sqlable
module InsertSqlable
def insert_sql
values = arel_attributes_values
primary_key_value = nil
if self.class.primary_key && Hash === values
primary_key_value = values[values.keys.find { |k|
k.name == self.class.primary_key
}]
if !primary_key_value && connection.prefetch_primary_key?(self.class.table_name)
primary_key_value = connection.next_sequence_value(self.class.sequence_name)
values[self.class.arel_table[self.class.primary_key]] = primary_key_value
end
end
im = self.class.arel_table.create_insert
im.into self.class.arel_table
conn = self.class.connection
substitutes = values.sort_by { |arel_attr,_| arel_attr.name }
binds = substitutes.map do |arel_attr, value|
[self.class.columns_hash[arel_attr.name], value]
end
substitutes.each_with_index do |tuple, i|
tuple[1] = conn.substitute_at(binds[i][0], i)
end
if values.empty? # empty insert
im.values = Arel.sql(self.class.connectionconnection.empty_insert_statement_value)
else
im.insert substitutes
end
conn.to_sql(im,binds)
end
end
It turns out the code is in ActiveRecord::Relation and not ActiveRecord::Persistence. The only significant change is the last line which generates the sql instead of performing it.
If you dont want to save the model you call m.destroy when you are done with the object.
You can log the sql query by debugging it like this
Rails.logger.debug "INSERT INTO models(url, header) VALUES(#{m.url}, #{m.header}).inspect
After search a lot over the Internet and forums, I think I found a better solution for your problem: just requires two line of code.
I found a good gem that do exactly what you want, but this gem only works for Rails 3.2 and older. I talked with author and he doesn't want support this gem anymore. So I discovered by myself how to support Rails 4.0 and now I'm maintaining this gem.
Download the "models-to-sql-rails" gem here, supporting Rails 4.0 and older.
With this gem, you can easily do the following. (the examples inside values are just a joke, you will get the correct values when using it in your object).
For objects:
object.to_sql_insert
# INSERT INTO modelName (field1, field2) VALUES ('Wow, amaze gem', 'much doge')
For array of objets:
array_of_objects.to_sql_insert
# INSERT INTO modelName (field1, field2) VALUES ('Awesome doge', "im fucking cop")
# INSERT INTO modelName (field1, field2) VALUES ('much profit', 'much doge')
# (...)
Just see the Github of this project and you'll find how to install and use this wonderful gem.

Separating an Array into a comma separated string with quotes

I'm manually building an SQL query where I'm using an Array in the params hash for an SQL IN statement, like: ("WHERE my_field IN('blue','green','red')"). So I need to take the contents of the array and output them into a string where each element is single quoted and comma separated (and with no ending comma).
So if the array was: my_array = ['blue','green','red']
I'd need a string that looked like: "'blue','green','red'"
I'm pretty new to Ruby/Rails but came up with something that worked:
if !params[:colors].nil?
#categories_array = params[:colors][:categories]
#categories_string =""
for x in #categories_array
#categories_string += "'" + x + "',"
end
#categories_string.chop! #remove the last comma
end
So, I'm good but curious as to what a proper and more consise way of doing this would look like?
Use map and join:
#categories_string = #categories_array.map {|element|
"'#{element}'"
}.join(',')
This functionality is built into ActiveRecord:
Model.where(:my_field => ['blue','green','red'])
Are you going to pass this string on to a ActiveRecord find method?
If so, ActiveRecord will handle this for you automatically:
categories_array = ['foo', 'bar', 'baz']
Model.find(:all, :conditions => ["category in (?)", categories_array])
# => SELECT * FROM models WHERE (category in ('foo', 'bar', 'baz'))
Hope this helps.
If you are using the parameter hash you don't have to do any thing special:
Model.all(:conditions => {:category => #categories_array})
# => SELECT * FROM models WHERE (category in ('foo', 'bar', 'baz'))
Rails (actually ActiveSupport, part of the Rails framework) offers a very nice Array#to_sentence method.
If you are using Rails or ActiveSupport, you can call
['dog', 'cat', 'bird', 'monkey'].to_sentence # "dog, cat, bird, and monkey"

Ruby on Rails ActiveRecord find_by-sql field names

I'm doing custom find_by_sql queries which are dynamically created by user input. What is the best way to find the field names returned by find_by_sql
I've tried using columns, column_names and keys methods but none work.
Is the ActiveRecord result a hash or an array?
e.g.
#fm_reports = FmReport.find_by_sql(crosstab_query)
field_names = #fm_reports.keys (or .columns or .column_names)
Cheers, Keith
Update ::
It seems that unless you do "select * from ...." in a find_by_sql it does not return the attribute names
>> FmReport.find_by_sql("select object_class, alarm_mr from fm_reports limit 1")
=> [#<FmReport >]
>> FmReport.find_by_sql("select * from fm_reports limit 1")
=> [#<FmReport id: 7, ALARM_DN: "PLMN-PLMN/BSC-31569/TCSM-72", ALARM_OBJECT: "MELB_BSC1", ALARM_MR: "VIC_METRO", PARENT_DN: "PLMN-PLMN/BSC-31569", PARENT_CLASS: "BSC", OBJECT_CLASS: "TCSM", ALARM_NUMBER: "2955", ALARM_TIME: "21/12/2009 11:02:19", CANCEL_TIME: "21/12/2009 11:03:27", SEVERITY: "2", created_at: nil, updated_at: nil>]
#fm_reports = FmReport.find_by_sql(crosstab_query)
field_values = #fm_reports.attributes
field_values.each {|key, value| puts key}
The above line will return a hashmap of field-names and their values. They can be iterated on if req.
http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M002353
attribute_names
Maybe you're looking for #attributes?
Also, find_by_sql returns an Array so that's why there's no method called attributes. How about doing first.attributes on the result of your find?
Another question is why are you using find_by_sql at all?
Why not just use ActiveRecord's built in stuff for this?
SomeModel.find( :all, :limit => 1 ).first.attributes
I know this is old, but it might help anyone with the same question.
I usually do it like this:
#fm_reports = FmReport.find_by_sql(crosstab_query)
field_names = #fm_reports.first.attributes.keys

Resources