I'm trying to implement auto complete for Rails. I have something like the following in my code -
Location.where("name like ?", "%#{params[:location]}%")
I'm afraid this would lead to SQL injection. Something like the following -
SELECT * FROM Locations WHERE (name LIKE '%green%') OR 1=1--%'
When params[:location] is something like this green%') OR 1=1--
Is there any way, I can avoid SQLi for substring based search in Rails?
Dave is right. Try it and see what it outputs. It wraps the whole thing up as part of the condition. In my Courses model.
> x = "'') OR SELECT * FROM Users"
=> "'') OR SELECT * FROM Users"
> Course.where(["name like ?", "%#{x}%"])
Course Load (38.7ms) SELECT "courses".* FROM "courses" WHERE
(name like '%'''') OR SELECT * FROM Users%')
=> []
If you're using Postgres, I would suggest using trigram support in Postgres to do this.
Throw this in a migration to get things set up:
CREATE EXTENSION pg_trgm;
then run your query like this:
Location.select('*', "similarity(search_term, #{ActiveRecord::Base.sanitize(search_term)}) AS similarity").order('similarity DESC, search_term')
or, just do what you're doing but wrap the param in #{ActiveRecord::Base.sanitize(search_term)}
Related
I have some sql that I am trying to convert to ActiveRecord
The sql looks like
select type.name, sum(order.sub_total)
from orders order
join order_types type on type.id = order_type_id
group by type.name
I have this mostly working except that I cannot figure out how to select type.name
Order.
select("sum(sub_total)").
joins(:order_type).
group("order_type_id`")
I'm thinking it should be something like:
Order.
select("order_type.name, sum(sub_total)").
joins(:order_type).
group("order_type_id`")
but it's not aware of what order_type.name is in this context so this fails.
Anyone know how I can do this in a way that still feels "railsy"
This worked for me:
Order.
select("order_types.name, sum(orders.sub_total).
joins(:order_type).
group("order_types.name")
You can do this:
grouped_orders = Order.select("order_types.name name, sum(orders.sub_total) sub_total")
.joins(:order_type)
.group("order_types.name")
And then, you can list the order names like this:
grouped_orders.map(&:name)
I have a search bar which works fine but it produces a duplicate every time it shows the correct result.
if params[:nav_search]
#art = Art.where(["style_number LIKE ? OR name LIKE ?", "%#{params[:nav_search]}%", "%#{params[:nav_search]}%"]).page(params[:page]).per_page(18)
end
Any clue where I'm going wrong?
Try to the following
#art = Art.where(["style_number LIKE ? OR name LIKE ?", "%#{params[:nav_search]}%", "%#{params[:nav_search]}%"]).distinct.page(params[:page]).per_page(18)
To retrieve objects from the database, Active Record provides several finder methods. Each finder method allows you to pass arguments into it to perform certain queries on your database without writing raw SQL.
You can see this Rails Guide for very well understand
include .uniq
try this,
if params[:nav_search]
#art = Art.where(["style_number LIKE ? OR name LIKE ?", "%#{params[:nav_search]}%", "%#{params[:nav_search]}%"]).uniq.page(params[:page]).per_page(18)
end
Use uniq or distinct to avoid duplicate records. In your case, you should use distinct:
if params[:nav_search]
#art = Art.where(["style_number LIKE ? OR name LIKE ?", "%#{params[:nav_search]}%", "%#{params[:nav_search]}%"]).distinct.page(params[:page]).per_page(18)
end
I am trying to use distinct on in rails with a scope, I've created a method in my model like this:
def self.fetch_most_recent_by_user(scope)
scope.where(guid: scope.except(:select).select("DISTINCT ON (eld_logs.user_id) user_id, eld_logs.guid").order("user_id, eld_logs.created_at desc").map(&:guid))
end
When I execute this I get and error like:
TestModel.fetch_most_recent_by_user(TestModel.includes(:user))
ERROR: syntax error at or near "DISTINCT"
LINE 1: SELECT guid, DISTINCT ON (user_id) user_id...
On searching on DISTINCT ON I found out that it should be the first element in a select statement for postgres to make it work.
I want to prepend the DISTINCT ON in the select statement. I have tried clearing the old select statements using except(:select) which I got from here, but it doesn't work because the includes(:user) prepends users attributes first while doing a left join.
I am using Rails 4.0.13 and Postgres 9.4.12. Any help is appreciated.
I found that if the includes was meddling with the distinct my sub query, because which DISTINCT ON failed. I modified my method to this and it works:
def self.fetch_most_recent_eld_log_by_user(scope, include_associations = { })
scope.where(guid: scope.except(:select).select("DISTINCT ON (eld_logs.user_id) eld_logs.user_id, eld_logs.guid").order("eld_logs.user_id, eld_logs.created_at desc").map(&:guid))
.includes(include_associations)
end
Still it'll be good if someone can provide a way to prepend something in the select statement of active record scope.
I have a sql like this:
SELECT TOP 1 field_name * FROM table_name
and I want to covert it into activerecord in rails.
That code doesn't do the job?
YourModel.select(:field_name).first
or
YourModel.select(:field_name).order('id desc').first
Just use limit(1) or first:
Model.select(:field_name).limit(1)
pick (Rails 6+)
Since Rails 6, you can use the following:
YourModel.order(id: :desc).pick(:field_name)
pick is even more efficient than
YourModel.select(:field_name).order('id desc').first
since, it will only load the actual value, not the entire record object.
For more details, please, follow this link to docs.
Also, there is a reference to the corresponding PR.
In Rails 2.3.8 there is a class method ActiveRecord::Base.count_by_sql which allows to perform custom SELECT count(*) .... Is it save to execute customized SELECT sum(...) ... query with this method? If not, what should be done to execute such a query? Is ActiveRecord::Base.connection.execute the only option?
Thanks.
EDIT: Query I want to perform has another query nested. That's why I believe methods from ActiveRecord::Calculations are not sufficient.
Check ActiveRecord::Calculations. The API docs are here.
You can do something like this (assuming you have User model):
User.maximum(:updated_at)
# Generates:
# SELECT max(`users`.updated_at) AS max_updated_at FROM `users`
select_value from ActiveRecord::ConnectionAdapters::DatabaseStatements module is the answer. It returns the value present in the first column of the first row returned by query. select_value returns String, so conversion may be necessary.
Example:
ActiveRecord::Base.connection.select_value("Some complicated sql")