Having a problem with my Model query - ruby-on-rails-3

Pls i have the following code in my model
Letter.count(:id, :conditions => ["language_id = #{lang} AND :created_at => '#{start_date.to_date.strftime('%Y-%m-%d')}'..'#{end_date.to_date.strftime('%Y-%m-%d')}' " ])
I am trying to get the count of letters.id of different letters between the given dates.
This is the error an having...
Please those anyone know what am doing wrong...Thanks
SQLite3::SQLException: near ">": syntax error: SELECT COUNT("letters"."id") FROM "letters" WHERE ("letters".language_id = 1) AND (language_id = 1 AND :created_at => '2011-05-01'..'2011-05-08

This can be much simplified. A couple points:
You don't use the :created_at => ... format within a string
You need to use between ? and ? for dates.
You don't need to manually strftime your dates, Rails will handle this automatically.
In Rails 3, the preferred way is to use where(...) instead of a :conditions Hash for your count(...).
You should probably use Rails' safe interpolation for your language_id field too
Letter.where("language_id = ? AND created_at between ? and ?", lang, start_date.to_date, end_date.to_date).count

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)

Rails 4 LIKE query - ActiveRecord adds quotes

I am trying to do a like query like so
def self.search(search, page = 1 )
paginate :per_page => 5, :page => page,
:conditions => ["name LIKE '%?%' OR postal_code like '%?%'", search, search], order => 'name'
end
But when it is run something is adding quotes which causes the sql statement to come out like so
SELECT COUNT(*)
FROM "schools"
WHERE (name LIKE '%'havard'%' OR postal_code like '%'havard'%')):
So you can see my problem.
I am using Rails 4 and Postgres 9 both of which I have never used so not sure if its and an activerecord thing or possibly a postgres thing.
How can I set this up so I have like '%my_search%' in the end query?
Your placeholder is replaced by a string and you're not handling it right.
Replace
"name LIKE '%?%' OR postal_code LIKE '%?%'", search, search
with
"name LIKE ? OR postal_code LIKE ?", "%#{search}%", "%#{search}%"
Instead of using the conditions syntax from Rails 2, use Rails 4's where method instead:
def self.search(search, page = 1 )
wildcard_search = "%#{search}%"
where("name ILIKE :search OR postal_code LIKE :search", search: wildcard_search)
.page(page)
.per_page(5)
end
NOTE: the above uses parameter syntax instead of ? placeholder: these both should generate the same sql.
def self.search(search, page = 1 )
wildcard_search = "%#{search}%"
where("name ILIKE ? OR postal_code LIKE ?", wildcard_search, wildcard_search)
.page(page)
.per_page(5)
end
NOTE: using ILIKE for the name - postgres case insensitive version of LIKE
While string interpolation will work, as your question specifies rails 4, you could be using Arel for this and keeping your app database agnostic.
def self.search(query, page=1)
query = "%#{query}%"
name_match = arel_table[:name].matches(query)
postal_match = arel_table[:postal_code].matches(query)
where(name_match.or(postal_match)).page(page).per_page(5)
end
ActiveRecord is clever enough to know that the parameter referred to by the ? is a string, and so it encloses it in single quotes. You could as one post suggests use Ruby string interpolation to pad the string with the required % symbols. However, this might expose you to SQL-injection (which is bad). I would suggest you use the SQL CONCAT() function to prepare the string like so:
"name LIKE CONCAT('%',?,'%') OR postal_code LIKE CONCAT('%',?,'%')", search, search)
If someone is using column names like "key" or "value", then you still see the same error that your mysql query syntax is bad. This should fix:
.where("`key` LIKE ?", "%#{key}%")
Try
def self.search(search, page = 1 )
paginate :per_page => 5, :page => page,
:conditions => ["name LIKE ? OR postal_code like ?", "%#{search}%","%#{search}%"], order => 'name'
end
See the docs on AREL conditions for more info.
.find(:all, where: "value LIKE product_%", params: { limit: 20, page: 1 })

Active Record: query based on another query in an association

I'm trying to do a complex search based on an query in an association.
Event belongs_to :user / User has_many :events
Along the literal lines of:
query = User.where(:name => 'Bob')
query = query.joins(:events).where('COUNT(events.start_at > #{Time.now}) = 0')
I have tried several approaches but none seem to work. Help greatly appreciated.
Thanks in advance.
You need to use scopes to allow this kind of chaining:
In User model:
scope :with_name, lambda { |name| where(:name => name) }
In code:
query = User.with_name('Bob')
query = query.joins(:events).where('COUNT(events.start_at > #{Time.now}) = 0')
I noticed a few issues with the way your query is written but I don't think fixing them will solve your problem. All the same, hopefully knowing this stuff will get you closer!
First, you need double quotes in order to get Ruby to respect your #{} interpolation:
Open IRB and try:
1.9.3p194 :001 > 'COUNT(events.start_at > #{Time.now}) = 0'
Which will output:
"COUNT(events.start_at > \#{Time.now}) = 0"
Notice that nothing changed, because we have single quotes.
Now lets try with double quotes, and we're a bit closer:
1.9.3p194 :002 > "COUNT(events.start_at > #{Time.now}) = 0"
This time we see:
"COUNT(events.start_at > 2012-09-24 14:47:52 -0700) = 0"
But that still won't work, because SQL wants quotes around your timestamp for comparison.
We could handle this manually, like this:
1.9.3p194 :003 > "COUNT(events.start_at > '#{Time.now}') = 0"
Which generates:
"COUNT(events.start_at > '2012-09-24 14:49:31 -0700') = 0"
But that may not work on all SQLs - I know that Postgres wants single quotes around literals, but MySQL may prefer double quotes, or may not matter.
The best way to handle this is to let Rails figure it out. The way we do that is to use the ? operator inside of our SQL query strings:
where('COUNT(events.start_at > ?) = 0', Time.now)
And the output Rails will generate, and send to your SQL database, will look like this:
"WHERE COUNT(events.start_at > '2012-09-24 14:49:31 -0700') = 0"
Hope that helps!
I think this is what you are looking for all in one line:
Event.joins(:user).where(:users => {:name => 'Bob'}, :events => {:start_at => Time.at(0)..Time.now })
You cannot use COUNT in a WHERE clause afiak. You'll want to group your results and use conditions to return only the results you want.
query = User.where(:name => 'Bob')
query = query.joins("LEFT JOIN (SELECT events.id FROM events WHERE events.start_at > '#{Time.now.to_s(:db)}') AS events ON events.user_id=users.id").group("users.id").where("events.id IS NULL")

Sum column values under condition

Suppose this:
Works table
Name
created_at
Size
I want to list all works and in the end sum up all Size values, but under one condition. created_at must be < 1 year ago.
I know
#works.sum(&:size)
works but it doesn't filter out the 'created_at' part.
Then i got to this
#works.sum(&:size, :conditions => ['created_at > ?', 1.year.ago])
But keep getting a compile error about expecting a ) instead of a ,
Help please? Any better way to accomplish this?
you can use scope to keep the model clean
#controller
class WorksController < ApplicationsController
def index
#works.recent.sum(&:size)
end
end
#model
class Work
scope :recent, ->{ where('created_at > ?', 1.year.ago) }
end
This worked for me:
#works.where('created_at > ?', 1_year_ago).sum(:size)

Refactoring SQL

I must connect from my Rails app to a remote database on a Java application.
I have a query like this:
find_by_sql("select c_templateid, c_templateinfoid, c_startdate, c_enddate, c_active,
campaign, shortcode, prefix, c_descriptivename, c_description, c_templatename
from (active_services aser join activity a on aser.c_primaryprefixid =
a.c_primaryprefixid ) join matrix_templateinfo using(c_templateinfoid)
where campaign is not null)")
I need to refactor it to the AR#find() method, because I want later add complex :conditions on it. I dont want convert them into string to append then later to the find_by_sql method.
find(:all,
:select => "c_templateid, c_templateinfoid, c_startdate, c_enddate, c_active,
campaign, shortcode, prefix, c_descriptivename, c_description, c_templatename",
# :joins => WHAT I SHOULD DO HERE ?
:conditions => "campaign is not null"
)
You can also specify complex joins in :joins
:joins => "matrix_templateinfo ON <conditions go here> AND campaing IS NOT NULL"
but you should really start padronizing your field names if you're using rails :]