rails 3 where statement - ruby-on-rails-3

In rails 3, the where statement of active record returns an active record object. i.e it uses lazy loading like
cars = Car.where(:colour => 'black') # No Query
cars.each {|c| puts c.name } # Fires "select * from cars where ..."
but when I fires,
cars = Car.where(:colour => 'black')
in console, it returns the result without this lazy loading why ?

Your console implicitly calls inspect on the result of your expression, which triggers the query.
You can avoid the inspection by appending a semicolon:
cars = Car.where(:colour => 'black');

Related

SQL injections in Rails 4 issue

I'm trying to learn about SQL injections and have tried to implement these, but when I put this code in my controller:
params[:username] = "johndoe') OR admin = 't' --"
#user_query = User.find(:first, :conditions => "username = '#{params[:username]}'")
I get the following error:
Couldn't find all Users with 'id': (first, {:conditions=>"username = 'johndoe') OR admin = 't' --'"}) (found 0 results, but was looking for 2)
I have created a User Model with the username "johndoe", but I am still getting no proper response. BTW I am using Rails 4.
You're using an ancient Rails syntax. Don't use
find(:first, :condition => <condition>) ...
Instead use
User.where(<condtion>).first
find accepts a list of IDs to lookup records for. You're giving it an ID of :first and an ID of condition: ..., which aren't going to match any records.
User.where(attr1: value, attr2: value2)
or for single items
User.find_by(attr1: value, attr2: value)
Bear in mind that while doing all this, it would be valuable to check what the actual sql statement is by adding "to_sql" to the end of the query method (From what I remember, find_by just does a LIMIT by 1)

How to insert custom value after validation in rails model

This has been really difficult to find information on. The crux of it all is that I've got a Rails 3.2 app that accesses a MySQL database table with a column of type POINT. Without non-native code, rails doesn't know how to interpret this, which is fine because I only use it in internal DB queries.
The problem, however, is that it gets cast as an integer, and forced to null if blank. MySQL doesn't allow null for this field because there's an index on it, and integers are invalid, so this effectively means that I can't create new records through rails.
I've been searching for a way to change the value just before insertion into the db, but I'm just not up enough on my rails lit to pull it off. So far I've tried the following:
...
after_validation :set_geopoint_blank
def set_geopoint_blank
raw_write_attribute(:geopoint, '') if geopoint.blank?
#this results in NULL value in INSERT statement
end
---------------------------
#thing_controller.rb
...
def create
#thing = Thing.new
#thing.geopoint = 'GeomFromText("POINT(' + lat + ' ' + lng + ')")'
#thing.save
# This also results in NULL and an error
end
---------------------------
#thing_controller.rb
...
def create
#thing = Thing.new
#thing.geopoint = '1'
#thing.save
# This results in `1` being inserted, but fails because that's invalid spatial data.
end
To me, the ideal would be to be able to force rails to put the string 'GeomFromText(...)' into the insert statement that it creates, but I don't know how to do that.
Awaiting the thoughts and opinions of the all-knowing community....
Ok, I ended up using the first link in steve klein's comment to just insert raw sql. Here's what my code looks like in the end:
def create
# Create a Thing instance and assign it the POSTed values
#thing = Thing.new
#thing.assign_attributes(params[:thing], :as => :admin)
# Check to see if all the passed values are valid
if #thing.valid?
# If so, start a DB transaction
ActiveRecord::Base.transaction do
# Insert the minimum data, plus the geopoint
sql = 'INSERT INTO `things`
(`thing_name`,`thing_location`,`geopoint`)
values (
"tmp_insert",
"tmp_location",
GeomFromText("POINT(' + params[:thing][:lat].to_f.to_s + ' ' + params[:thing][:lng].to_f.to_s + ')")
)'
id = ActiveRecord::Base.connection.insert(sql)
# Then load in the newly-created Thing instance and update it's values with the passed values
#real_thing = Thing.find(id)
#real_thing.update_attributes(b, :as => :admin)
end
# Notify the user of success
flash[:message] = { :header => 'Thing successfully created!' }
redirect_to edit_admin_thing_path(#real_thing)
else
# If passed values not valid, alert and re-render form
flash[:error] = { :header => 'Oops! You\'ve got some errors:', :body => #thing.errors.full_messages.join("</p><p>").html_safe }
render 'admin/things/new'
end
end
Not beautiful, but it works.

Rails raw SQL example

How can I convert this code to raw sql and use in rails? Because When I deploy this code in heroku,there is a request timeout error.I think this will be faster if I use raw sql.
#payments = PaymentDetail.joins(:project).order('payment_details.created_at desc')
#payment_errors = PaymentError.joins(:project).order('payment_errors.created_at desc')
#all_payments = (#payments + #payment_errors)
You can do this:
sql = "Select * from ... your sql query here"
records_array = ActiveRecord::Base.connection.execute(sql)
records_array would then be the result of your sql query in an array which you can iterate through.
I know this is old... But I was having the same problem today and found a solution:
Model.find_by_sql
If you want to instantiate the results:
Client.find_by_sql("
SELECT * FROM clients
INNER JOIN orders ON clients.id = orders.client_id
ORDER BY clients.created_at desc
")
# => [<Client id: 1, first_name: "Lucas" >, <Client id: 2, first_name: "Jan">...]
Model.connection.select_all('sql').to_hash
If you just want a hash of values:
Client.connection.select_all("SELECT first_name, created_at FROM clients
WHERE id = '1'").to_hash
# => [
{"first_name"=>"Rafael", "created_at"=>"2012-11-10 23:23:45.281189"},
{"first_name"=>"Eileen", "created_at"=>"2013-12-09 11:22:35.221282"}
]
Result object:
select_all returns a result object. You can do magic things with it.
result = Post.connection.select_all('SELECT id, title, body FROM posts')
# Get the column names of the result:
result.columns
# => ["id", "title", "body"]
# Get the record values of the result:
result.rows
# => [[1, "title_1", "body_1"],
[2, "title_2", "body_2"],
...
]
# Get an array of hashes representing the result (column => value):
result.to_hash
# => [{"id" => 1, "title" => "title_1", "body" => "body_1"},
{"id" => 2, "title" => "title_2", "body" => "body_2"},
...
]
# ActiveRecord::Result also includes Enumerable.
result.each do |row|
puts row['title'] + " " + row['body']
end
Sources:
ActiveRecord - Findinig by
SQL.
Ruby on Rails - Active Record Result
.
You can execute raw query using ActiveRecord. And I will suggest to go with SQL block
query = <<-SQL
SELECT *
FROM payment_details
INNER JOIN projects
ON projects.id = payment_details.project_id
ORDER BY payment_details.created_at DESC
SQL
result = ActiveRecord::Base.connection.execute(query)
You can do direct SQL to have a single query for both tables. I'll provide a sanitized query example to hopefully keep people from putting variables directly into the string itself (SQL injection danger), even though this example didn't specify the need for it:
#results = []
ActiveRecord::Base.connection.select_all(
ActiveRecord::Base.send(:sanitize_sql_array,
["... your SQL query goes here and ?, ?, ? are replaced...;", a, b, c])
).each do |record|
# instead of an array of hashes, you could put in a custom object with attributes
#results << {col_a_name: record["col_a_name"], col_b_name: record["col_b_name"], ...}
end
Edit: as Huy said, a simple way is ActiveRecord::Base.connection.execute("..."). Another way is ActiveRecord::Base.connection.exec_query('...').rows. And you can use native prepared statements, e.g. if using postgres, prepared statement can be done with raw_connection, prepare, and exec_prepared as described in https://stackoverflow.com/a/13806512/178651
You can also put raw SQL fragments into ActiveRecord relational queries: http://guides.rubyonrails.org/active_record_querying.html
and in associations, scopes, etc. You could probably construct the same SQL with ActiveRecord relational queries and can do cool things with ARel as Ernie mentions in http://erniemiller.org/2010/03/28/advanced-activerecord-3-queries-with-arel/. And, of course there are other ORMs, gems, etc.
If this is going to be used a lot and adding indices won't cause other performance/resource issues, consider adding an index in the DB for payment_details.created_at and for payment_errors.created_at.
If lots of records and not all records need to show up at once, consider using pagination:
https://www.ruby-toolbox.com/categories/pagination
https://github.com/mislav/will_paginate
If you need to paginate, consider creating a view in the DB first called payment_records which combines the payment_details and payment_errors tables, then have a model for the view (which will be read-only). Some DBs support materialized views, which might be a good idea for performance.
Also consider hardware or VM specs on Rails server and DB server, config, disk space, network speed/latency/etc., proximity, etc. And consider putting DB on different server/VM than the Rails app if you haven't, etc.
I want to work with exec_query of the ActiveRecord class, because it returns the mapping of the query transforming into object, so it gets very practical and productive to iterate with the objects when the subject is Raw SQL.
Example:
values = ActiveRecord::Base.connection.exec_query("select * from clients")
p values
and return this complete query:
[{"id": 1, "name": "user 1"}, {"id": 2, "name": "user 2"}, {"id": 3, "name": "user 3"}]
To get only list of values
p values.rows
[[1, "user 1"], [2, "user 2"], [3, "user 3"]]
To get only fields columns
p values.columns
["id", "name"]
You can also mix raw SQL with ActiveRecord conditions, for example if you want to call a function in a condition:
my_instances = MyModel.where.not(attribute_a: nil) \
.where('crc32(attribute_b) = ?', slot) \
.select(:id)

Find all matching criteria where conditions not in array

I am trying to return a variable with values of an array of users. There are a few conditions that must be met. The user must have public_find set to true, it must not be the current_user (session variable of the user currently logged in), and it must not already be part of a friendship. The first and second conditions work perfectly. However, I am having an issue with the third part where the current_users.friendships needs to be an array of the ID values of the users where the association already exists. Any thoughts?
#users = User.find(:all, :conditions => ['
public_find=true AND
id <> ? AND
id NOT IN (?)',
current_user.id, current_user.friendships])
Edit:
I've figured out that I was missing pluck from the list. This works good now. However, if someone does not yet have a friend then current_user.friendships.pluck(:friend_id) will return NULL. I know that it is bad practice and unexpected results returned when using NOT IN and NULL. However, how do you create a condition where you can set the value to something realistic like [0] or [1] if the array returned is empty?
#users = User.find(:all, :conditions => ['
public_find=true AND
id <> ? AND
id NOT IN (?)',
current_user.id, current_user.friendships.pluck(:friend_id) || [0]])
EDIT AGAIN:
I got it working. However, now I want to know if this is best practice to have a statement like this. It basically is doing a check to see if the current_user.friendships.pluck(:friend_id) is empty or not. If it is then return [0]. Otherwise return an array of the user ids (foreign keyed as friend_id).
#users = User.find(:all, :conditions => ['
public_find=true AND
id <> ? AND
id NOT IN (?)',
current_user.id,
(current_user.friendships.pluck(:friend_id).empty? ? [0] : current_user.friendships.pluck(:friend_id))])
You can write this a little nicer ..
Show all users where public_find is true and also exclude the currently logged in user or any of their friends
ids = current_user.friendships.map(&:friend_id).concat([current_user.id])
#users = User.where(:public_find => true).where('id not in ?', ids)
I would use an arel table for this (which guarantees the code will work on any database):
t, f = User.arel_table, current_user.friendships
query = t[:public_find].eq(true).and(t[:id].not_eq(current_user.id))
query = query.and(t[:id].not_in(f.pluck(:friend_id))) unless f.empty?
#users = User.where(query)
Generated SQL for current_user = 3 and a single friendship with a user with id = 1:
SELECT "users".* FROM "users"
WHERE ("users"."public_find" = 't' AND "users"."id" != 3 AND "users"."id" NOT IN (1))
If current_user.friendships is nil, the unless f.empty? clause will prevent that condition from being applied at all, so it will not appear in the SQL:
SELECT "users".* FROM "users"
WHERE ("users"."public_find" = 't' AND "users"."id" != 3)
Also, note that because this code uses where instead of find, the final result is an ActiveRecord::Relation rather than an array of results. This means that you can further chain conditions onto it, e.g. to order the results by updated_at, change the last line to:
#users = User.where(query).order(:created_at)

rails/activerecord: how to get SQL COUNT and SUM to work with Postgres (at heroku)

My rails 3 app needs to use a SELECT DISTINCT which (as far as I know) cannot be done with activerecord queries. So I have been executing direct SQL and it is running fine locally on sqllite -- But it is failing at Heroku (postgres).
In my local (sqllite) app, this works fine:
r = ActiveRecord::Base.connection.execute("my query string")
But on heroku, using ActiveRecord::Base.connection.execute ALWAYS returns an empty dataset
#<PGresult:0x0000000xxxxxxxxx>
even for very simple queries such as
r = ActiveRecord::Base.connection.execute("SELECT numeric_score FROM beeps WHERE store_id = '132' AND survey_num = '2'")
So I'm using heroku console to debug some very basic SQL queries to try to understand how to re-format my SQL to work at Heroku/Postgres.
SELECT column_name WORKS: the heroku console, selecting postgres records is no problem, for example this works fine:
n = Beep.find_by_sql("SELECT numeric_score FROM beeps WHERE store_id = '132' AND survey_num = '2'")
gives the three values expected:
[#<Beep numeric_score: 10>, #<Beep numeric_score: 9>, #<Beep numeric_score: 8>]
But SELECT COUNT fails?? When I try to COUNT them in the SQL
n = Beep.find_by_sql("SELECT COUNT(*) FROM beeps WHERE store_id = '132' AND survey_num = '2'")
it fails, giving:
[#<Beep >]
And SELECT SUM(column) fails too?? When I try to SUM them
n = Beep.find_by_sql("SELECT SUM(numeric_score) FROM beeps WHERE store_id = '132' AND survey_num = '2'")
it also fails, giving:
[#<Beep >]
How do I execute direct SQL with Postgres... SUM(columnname) and COUNT(*) should work, right?
There's a couple of things here.
Firstly, find_by_sql will return initialised objects based on the data coming back, which is why you're not seeing anything coming back from your counts.
In order to do this with AR you can do:
Beep.where(:store_id => 123).where(:survey_num => 2).count
=> 5
This will return a number. It's the same with sums:
Beep.where(:store_id => 123).where(:survey_num => 2).sum(:numeric_score)
=> 5
You can also use distinction with AR, but it's not as clean:
Beep.select("DISTINCT *").where(:store_id => 123).where(:survey_num => 2)
=> [<Beep>, <Beep>...etc]
In order to query the db directly, this is still possible, and you were almost there:
conn = ActiveRecord::Base.connection
sql = "SELECT DISTINCT * FROM beeps WHERE store_ID = 123"
res = conn.execute sql
# res is now a PGResult object
res.each do |row|
puts row["id"]
puts row["numeric_score"]
end
There's a uniq method that you can add on to your ActiveRecord relation that adds DISTINCT to the SELECT.
Beep.where(:store_id => 123).where(:survey_num => 2).uniq
See also: http://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-uniq