My question is regarding the way Rails handles queries in Rails 2.3.
I am currently looking at some legacy code and wanted to try see if there was a better way to go about this rather than using the collection that the previous programmer used.
As a Rails 3 minded person I think there should be a better way to do this. To me this seems like it would be a costly operation to run, but maybe the Rails way of doing it uses the same method so calling it would only be a convenience
def self.entity_assigned(entities)
return nil if entities.size == 0
conditions = "#{EntityUser.table_name}.entity_id IN (#{entities.collect{|x| x.id}.join(',')})"
find :all,
:include => [:entity_users => :entity],
:conditions => conditions
end
If someone can let me know if there is a better way to do this or if I should continue with the current way.
Related
I am using ruby 1.8.7 and rails 2.3.2
The following code is prone to sql injection
params[:id] = "1) OR 1=1--"
User.delete_all("id = #{params[:id]}")
My question is by doing the following will be the best solution to avoid sql injection or not. If not then what is the best way to do so?
User.delete_all("id = #{params[:id].to_i}")
What about:
User.where(id: params[:id]).delete_all
Ok sorry for Rails 2.x its:
User.delete_all(["id = ?", params[:id]])
Check doc
Btw, be sure you want to use delete_all instead of destroy_all, the former doesn't trigger callbacks.
You can use this also
User.delete(params[:id])
The other answers answer this well for Rails and it'll work fine if you follow their suggestions. In a more generic setting when you have to handle this yourself you can typically use a regular expression to extract a value that's in an expected format. This is really simple with an integer id. Think of it like this:
if params[:id] =~ /(\d+)/
safe_id = $1.to_i
# do something with safe_id now
end
That gets a little more complicated when you're handling strings and arbitrary data. If you have to handle such data then you can use the quoting methods available for the database adapters. In Rails this is ultimately rolled into a consistent interface:
safe_string = ActiveRecord::Base.connection.quote(unsafe_string)
For most database systems this will handle single quotes and backslashes in a special manner.
If you're outside of Rails you will have to use the quoting methods specific to your database adapter, but usage is quite similar.
The takeaway:
If your data has a particular format, enforce the format with a regular expression
Otherwise, use your database adapter's quoting function to make the data "safe" for use in a query
Rails will handle most of this for you if you properly use the various methods and "conditions"
Use the rails methods to pass your where options. You can always hardcode them, as in the example that you give, but the usual way would be something like:
User.where(:id => params[:id]).delete_all
User.where("id = ?", params[:id]).delete_all
User.where("id = :id", :id => params[:id]).delete_all
They are well tested and in case a new vulnerability is detected, an update will fix the problem and your code will not need to be changed.
By the way, if you just want to delete 1 record based on its id, what I would do is:
User.find(params[:id]).destroy
I am using the Bullet gem to assist me in finding my n + 1 errors for my ActiveRecord queries. I currently am passing in:
#user = User.includes(:routines => {:lifts => [:exercise, :infos]}).find(current_user.id)
To me this means I am loading the current user, his routines, those routines' lifts, and those lifts' exercise and infos (which are sets).
Is my assumption true?
The Bullet gem is giving me two errors in which it is claiming I need to:
Lift => [:routine] so it says add ".include => [:routine]"
AND
Lift => [:infos] so it says add ".include => [:infos]"
Would somebody be able to explain this to me?
Thank you!
You definitely are on the right path. I highly recommend brushing up on this via http://guides.rubyonrails.org/
Your setup supports pre-loading when you access data like this:
routines = #user.routines
lifts = #user.routines.map(&:lifts)
Can you please describe how you are attempting to access this data? It appears that you may be trying to access routine via:
lift.routine
How are you accessing lift?
You might want to make sure that you use :inverse_of when specifying your associations.
Does Routine have many :lifts?
Is there a way to write it in a more 'Rails-friendly' way?
i.e. if I were searching for anyone of those attributes, I would just do Feedback.where(:poster_id => 3) and it would be good.
But how do I achieve the OR in the Rails 3 friendly syntax (as opposed to the SQL friendly one).
Also, if it is better to use my original one over the desired one, why is it better?
Thanks.
Edit 1: Btw, if I do Feedback.where(:poster_id => 3, :receiver_id => 3) that returns the result for the AND operation, which is the exact opposite of what I want. So I feel so close, just not quite there.
You can do this by putting SQL fragments in the where() arguments. For more information you can look at the ActiveRecord querying guide.
Feedback.where("poster_id = ? OR receiver_id = ?", 1, 3)
You can do this without SQL fragments as described in this SO post:
t = Feedback.arel_table
Feedback.where(t[:poster_id].eq(1).or(t[:receiver_id].eq(2)))
I'm doing queries like this in Rails 3.x
Speaker.where("name like '%yson%'")
but I'd love to avoid the DB specific code. What's the right way to do this?
If there's a way to do this in Rails 2.x too, that would help too.
In Rails 3 or greater
Speaker.where("name LIKE ?", "%yson%")
In Rails 2
Speaker.all(:conditions => ["name LIKE ?", "%yson%"])
Avoid to directly interpolate strings because the value won't be escaped and you are vulnerable to SQL injection attacks.
You can use .matches for it.
> t[:name].matches('%lore').to_sql
=> "\"products\".\"name\" LIKE '%lore'"
Actual usage in a query would be:
Speaker.where(Speaker.arel_table[:name].matches('%lore'))
Use a search engine like solr or sphinx to create indexes for the columns you would be performing like queries on. Like queries always result in a full table scan when you look at the explain plan so you really should almost never use them in a production site.
Not by default in Rails, since there are so many DB options (MySQL, Postgresql, MongoDB, CouchDB...), but you can check out gems like MetaWhere, where you can do things like:
Article.where(:title.matches => 'Hello%', :created_at.gt => 3.days.ago)
=> SELECT "articles".* FROM "articles" WHERE ("articles"."title" LIKE 'Hello%')
AND ("articles"."created_at" > '2010-04-12 18:39:32.592087')
In general though you'll probably have to have some DB specific code, or refactor your code (i.e redefine the .matches operator on symbols in MetaWhere) to work with a different database. Hopefully you won't be changing your database that often, but if you are you should have a centralized location where you define these operators for re-use. Keep in mind that an operator or function defined in one database might not be available in another, in which case having this generalized operation is moot since you won't be able to perform the search anyways.
In my posts model, I have a named scope:
named_scope :random, :order => "Random()"
I'd like to give users the ability to get posts in a random order by sending a GET request with params[:scope] = 'random'.
Short of eval("Post.#{params[:scope]}"), how can I do this?
I would suggest my very awesome acts_as_filter plugin designed for user-driven filtering of results via named_scopes.
http://github.com/tobyhede/acts_as_filter/tree/master
Eval is fine to use - but make sure you validate against accepted/expected values (I often just plug some values into an array and test accepted_values.include?(parameter))
eval is a pretty bad idea. However, #send is perfect for this - it's inherently safer, and faster than eval (as I understand it).
Product.send(params[:scope])
That should do it :)
I came across it in a search. searchlogic is perfect for this.
I would stay away from eval since you're dealing with data that comes from the user. Maybe just use a simple case statement? This way you'll be able to validate what the data they're giving you.
For the example you give, I'd be explicit, and chain scopes together to build the query you want:
scope = Post
scope = scope.random if params[:scope] == 'random'
#posts = scope.find(:all, ...) # or paginate or whatever you need to do
If params[:scope] isn't 'random', this is the same as calling Post.find(), otherwise it's doing Post.random.find()
From one of the other answers, it looks like find_by_filter would do pretty much the same thing for you.
Using this pattern, you can also combine multiple scopes into the query if you needed to support things that weren't mutually exclusive
e.g.
scope = scope.only_monsters if params[:just_monsters] == 1
scope = scope.limit(params[:limit].to_i) unless params[:limit].to_i.zero?
So GETting /posts?scope=random&just_monsters=1&limit=5 will give you:
Post.random.just_monsters.limit(5).find(:all, ...)