How to sanitize sql fragment in Rails - sql

I have to sanitize a part of sql query. I can do something like this:
class << ActiveRecord::Base
public :sanitize_sql
end
str = ActiveRecord::Base.sanitize_sql(["AND column1 = ?", "two's"], '')
But it is not safe because I expose protected method. What is a better way to do it?

You can just use:
ActiveRecord::Base::sanitize_sql(string)

ActiveRecord::Base.connection.quote does the trick in Rails 3.x

This question does not specify that the answer has to come from ActiveRecord nor does it specify for which version of Rails it should be. For that reason (and because it is one of the top and few) answers on how to sanitize parameters in Rails...
Here a solution that works with Rails 4:
In ActiveRecord::Sanitization::ClassMethods you have sanitize_sql_for_conditions and its two other aliases:
 sanitize_conditions and sanitize_sql. The three do literally the exact same thing.
sanitize_sql_for_conditions
Accepts an array, hash, or string of SQL conditions and sanitizes
them into a valid SQL fragment for a WHERE clause.
Also in ActiveRecord you have
sanitize_sql_for_assignment which
Accepts an array, hash, or string of SQL conditions and sanitizes them
into a valid SQL fragment for a SET clause.
The methods above are included in ActiveRecord::Base by default and therefore are included in any ActiveRecord model.
See docs
Also, however, in ActionController you have ActionController::Parameters which allows you to
choose which attributes should be whitelisted for mass updating and
thus prevent accidentally exposing that which shouldn't be exposed.
Provides two methods for this purpose: require and permit.
   
params = ActionController::Parameters.new(user: { name: 'Bryan', age: 21 })
req  = params.require(:user) # will throw exception if user not present
opt  = params.permit(:name)  # name parameter is optional, returns nil if not present
user = params.require(:user).permit(:name, :age) # user hash is required while `name` and `age` keys are optional
The "Parameters magic" is called Strong Parameters (docs here) and you can use that to sanitize parameters in a controller before sending it to a model.
The methods above are included by default in ActionController::Base and therefore are included in any Rails controller.
I hope that helps anyone, if only to learn and demystify Rails! :)

As of rails 5 the recomended way is to use: ActiveRecord::Base.connection.quote(string)
as stated here: https://github.com/rails/rails/issues/28947
ActiveRecord::Base::sanitize(string) is deprecated

Note that when it comes to sanitizing SQL WHERE conditions, the best solution was sanitize_sql_hash_for_conditions, because it correctly handled NULL conditions (e.g. would generate IS NULL instead of = NULL if a nil attribute was passed).
For some reason, it was deprecated in Rails 5. So I rolled a future-proofed version, see here: https://stackoverflow.com/a/53948665/165673

Related

Ruby on Rails: Anything selected with 'joins' is readonly

So we're having some difficulty with the readonly? method in activerecord classes. It appears that anything fetched with a call to joins is being set to readonly and throws this error
ActiveRecord::ReadOnlyRecord: ActiveRecord::ReadOnlyRecord
For instance:
# fine
User.first.readonly?
User.first.save
# not fine
User.joins(:memberships).first.readonly?
# false
User.joins(:memberships).first.save
# ActiveRecord::ReadOnlyRecord
According to the Rails documentation this will be set to false for "Records loaded through joins with piggy-back attributes."
As far as I'm aware, we're not including any piggy-back attributes here.
I can forcibly circumvent this restriction, but it feels like I'm hacking through the system unnecessarily.
user = User.joins(:memberships).first
User.instance_variable_set(:#readonly, false)
user.save
Does anyone know why joins is returning readonly items? Can I prevent this? (I'm not trying to select any attributes from related objects).
I'm using Ruby on Rails version 3.2.22.5, It looks like this has been a change in behavior from an earlier version of Rails 3.
Try using includes instead of joins, it's better and faster.
User.includes(:memberships).first

Rails 3: Excluding Results by Default

On my site, moderators can flag spammy comments. When these comments are flagged, they get quarantined so they no longer appear in regular views, though they can still be seen in the administrative control panel. At the moment, I exclude them from regular views like so:
#comments = Comment.where(:flagged => false)
I do this in every controller that has comments in it, of which there are many. I get the feeling that there's a cleaner way to handle this in Rails. Perhaps somewhere in the comments model I can specify that when querying for comments, only retrieve those that aren't flagged. If so, how is that done? And even if that's not possible, is there some other way to dry this code?
u can use a default scope
default_scope where(:flagged => false)
see http://apidock.com/rails/ActiveRecord/Base/default_scope/class
the default scope can be ignored using unscoped. See http://apidock.com/rails/ActiveRecord/Base/unscoped/class
i would prefer using a scope rather a default scope since i dont have to override it when all the records are needed. Depends upon the frequency of fetching all/unflagged records.
Make a scope (named 'clean' for this example):
class Comment < ActiveRecord
scope :clean, where(:flagged => false)
end
Then use:
#comments = Comment.clean
For future-proofing, you may may want to add a class method called default_view which just calls clean and use that instead. As your 'default' needs change, just modify the default_view method.

In Rails 3, is there a difference between = and assign_attributes?

Let's say you're in your user controller and you want to change the name a #user based on some params you have available to you.
I want to know if there is any difference between the following:
#user.name = params[:user][:name]
or
#user.assign_attributes({:name=> params[:user][:name]})
Thanks in advance!
A great way to figure out questions like this is to dive into the source. I found the method in activerecord/lib/active_record/attribute_assignment.rbCheck it out here.
The assign_attributes method will actually just loop through the parameters given and sends the :name= message to your model. However, because you are possibly assigning many attributes, it takes into account mass-assignment precautions. (ie. make sure that the attribute is listed as attr_accessible).
The = (e.g. #user.name = params[:user][:name]) directly calls the attribute setter with no security check. The assign_attributes checks security for the values passed in.
From the Rails API for assign_attributes:
Allows you to set all the attributes for a particular mass-assignment
security role by passing in a hash of attributes with keys matching
the attribute names (which again matches the column names) and the
role name using the :as option.
Source for assign_attributes

rails 3 default_scope(:where) and find

Find doesn't descope the default_scope anymore, what should I do now? I need to find entries that are out of the default scope on so many places and I also need the scoped arrays of entries for so many lists in my application.
Why did they changed it? :(
Take a look at this article as what has been deprecated in Rails3 here.
So if you want to use the model without the default_scope on it, then you can use the following as in the snippet below.(This is extracted from the article I mentioned)
with_scope and with_exclusive_scope
with_scope and with_exclusive_scope are now implemented on top of Relation as well. Making it possible to use any relation with them :
with_scope(where(:name => 'lifo')) do
...
end
Or even use a named scope :
with_exclusive_scope(Item.red) do
...
end

Can anyone explain how CDbCriteria->scopes works?

I've just checked the man page of CDbCriteria, but there is not enough info about it.
This property is available since v1.1.7 and I couldn't find any help for it.
Is it for dynamically changing Model->scopes "on-the-fly"?
Scopes are an easy way to create simple filters by default. With a scope you can sort your results by specific columns automatically, limit the results, apply conditions, etc. In the links provided by #ldg there's a big example of how cool they are:
$posts=Post::model()->published()->recently()->findAll();
Somebody is retrieving all the recently published posts in one single line. They are easier to maintain than inline conditions (for example Post::model()->findAll('status=1')) and are encapsulated inside each model, which means big transparency and ease of use.
Plus, you can create your own parameter based scopes like this:
public function last($amount)
{
$this->getDbCriteria()->mergeWith(array(
'order' => 't.create_time DESC',
'limit' => $amount,
));
return $this;
}
Adding something like this into a Model will let you choose the amount of objects you want to retrieve from the database (sorted by its create time).
By returning the object itself you allow method chaining.
Here's an example:
$last3posts=Post::model()->last(3)->findAll();
Gets the last 3 items. Of course you can expand the example to almost any property in the database. Cheers
Yes, scopes can be used to change the attributes of CDbCriteria with pre-built conditions and can also be passed parameters. Before 1.1.7 you could use them in a model() query and can be chained together. See:
http://www.yiiframework.com/doc/guide/1.1/en/database.ar#named-scopes
Since 1.1.7, you can also use scopes as a CDbCriteria property.
See: http://www.yiiframework.com/doc/guide/1.1/en/database.arr#relational-query-with-named-scopes