Using update_columns in Rails 3? - ruby-on-rails

Is there a shorter way for this in Rails 3?
user.update_column(:attribute1, value1)
user.update_column(:attribute2, value2)
user.update_column(:attribute3, value3)
user.update_column(:attribute4, value4)
I tried update_columns but it's only available in Rails 4.
Thanks for any help.

Here's a workaround for Rails 3.x:
User.where(id: user.id).update_all(attribute1: value1, attribute2: value2, ...)

If you need speed you can also call execute directly on AR connection. I used something like this to import large amount of data.
connection = ActiveRecord::Base.connection
email = connection.quote(email)
zip = connection.quote(zip)
connection.execute(%{UPDATE "users" SET "email" = #{email}, "zip" = #{zip} WHERE "users"."id" = #{user.id}})
Note that validations and callbacks will not be run.

another approach you can take which accomplishes the same thing is this:
user.assign_attributes(
first_name: 'foo',
last_name: 'bar'
)
user.save(validate: false)
NOTE: You don't have to use the validate false. However, the #update_column method does skip validations and callbacks. So if that is what you are looking for, use the validate false.

Related

How to sanitize raw SQL in Rails 4

In Rails 3 I could use sanitize_sql_array to sanitize raw SQL for those occassional moments where a raw SQL query is needed. But this appears to have been removed in Rails 4, or not so much removed, but moved to ActiveRecord::Sanitization. However, I can not figure out how to call sanitize_sql_array now, so what's the best way to sanitize raw SQL in Rails 4?
I want to clarify that I am talking about a full raw SQL query here, not using Rail's models. I'm aware that this is not best practice, this is just what I have to do for this specific query since it can't be represented by Rails's nice ActiveRecord interface (Trust me, I've tried).
Here is a sample call, which is obviously simpler than what my query actually looks like:
query = "SELECT * FROM users
LEFT OUTER JOIN posts ON users.id=posts.user_id
AND posts.topic_id = '#{topic.id}'"
# ^- Obviously bad and very vulnerable, this is what we're trying to fix
ActiveRecord::Base.connection.select_all(query)
If you really need to write raw SQL you can use quote to sanitize it:
conn = ActiveRecord::Base.connection
name = conn.quote("John O'Neil")
title = conn.quote(nil)
query = "INSERT INTO users (name,title) VALUES (#{name}, #{title})"
conn.execute(query)
From the Active Record docs, the best way to sanitize a SQL query is to avoid to build our own conditions as pure strings, in other words, inserts the parameters directly into the query, like this:
User.find_by("user_name = '#{user_name}' AND password = '#{password}'")
and instead use array or hash conditions.
Array conditions:
Client.where("orders_count = ? AND locked = ?", params[:orders], false)
Hash conditions:
Client.where(is_active: true)
A clarifying example:
class User < ActiveRecord::Base
# UNSAFE - susceptible to SQL-injection attacks
def self.authenticate_unsafely(user_name, password)
where("user_name = '#{user_name}' AND password = '#{password}'").first
end
# SAFE
def self.authenticate_safely(user_name, password)
where("user_name = ? AND password = ?", user_name, password).first
end
# SAFE
def self.authenticate_safely_simply(user_name, password)
where(user_name: user_name, password: password).first
end
end
Here are some references:
http://api.rubyonrails.org/classes/ActiveRecord/Base.html#method-c-sanitize_sql_array
http://guides.rubyonrails.org/active_record_querying.html
The quote method and other ActiveRecord::Base sanitization methods have been deprecated, and were never part of the public API.
https://github.com/rails/rails/issues/28947
The official sanitization methods are
http://api.rubyonrails.org/classes/ActiveRecord/Sanitization/ClassMethods.html

Active Record: AND not working?

I have this Code in Rails 4:
#dominik += Provision.where(teilhaber: 'DW' AND weberkunde: = false).sum(:betrag)*0.9
How can I put this right so that the AND (i mean both conditions shall be true (teilhaber: 'DW' and weberkunde: false) functions correctly.
this does not work either:
#dominik += Provision.where(teilhaber: 'DW').where(weberkunde: = false).sum(:betrag)*0.9 #and weberkunde: false
As you're using a hash
.where(teilhaber: 'DW', weberkunde: false)
Rails has an excellent guide for querying active record:
http://guides.rubyonrails.org/active_record_querying.html
In your case, the syntax for your where query is just a bit off. You have a few different options with the where method.
To use hash conditions:
Provision.where(teilhaber: 'DM', weberkund: false)
Or, you can also use a a SQL-esque syntax.
Provision.where("teilhaber = ? AND weberkund = ?", 'DM', 'false')

How to use ST_GeoHash and ST_MakePoint postgis functions in rails before save?

How to build in rails postgis point, then geohash and save them into database before send response to client? I would like to make it through ST_MakePoint and ST_GeoHash function, I prefer to avoid execute SQL and extracting data by [0]["st_makepoint"], if it is possible how to insert this functions to execute them automatically when inserting all attributes? I've installed squeel gem, maybe can I merge this functions to the query?
My current rails code:
before_save :set_geopoint
def set_geopoint
#attributes -> {"latitude" => 51.90,"longitude" => 16.42,"geopoint" => nil}
#self.geopoint = "ST_MakePoint(#{latitude}, #{longitude})")" #not working
#self.geopoint = ActiveRecord::Base.connection.execute("SELECT ST_MakePoint(#{latitude}, #{longitude})")[0]["st_makepoint"]
#self.geohash = "ST_GeoHash(#{self.geopoint})"
#self.geohash = ActiveRecord::Base.connection.execute("SELECT ST_GeoHash(ST_SetSRID(#{self.geopoint},4326),5);").first["st_geohash"]
end
I did it through SQL before trigger function but I'm still looking for rails approach.
CREATE FUNCTION geopoint_trigger() RETURNS TRIGGER AS $$
BEGIN
INSERT INTO posts( geopoint ) VALUES( ST_GeomFromText('POINT(' || NEW.latitude || ' ' || NEW.longitude || ')') );
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
To generate a point that you can save in a postgis enabled database, you need to generate that point with a factory. What you're doing here:
self.geopoint = "ST_MakePoint(#{latitude}, #{longitude})")" #not working
is just setting self.geopoint to a string and trying to save it. As your database geopoint field is (I assume) set to accept a point, it fails.
I suggest you use the rgeo gem to add geo factories to your models. Add it to your Gemfile.
https://github.com/rgeo/rgeo
You need to make sure that in your migration, you are using a point as column type like this:
t.point :geopoint, geographic: true
In your Post model, you then need to specify a factory like this:
RGEO_FACTORY = RGeo::Geographic.spherical_factory(srid: 4326)
And you also need to tell rgeo what factory to use on your geopoint column.
set_rgeo_factory_for_column :geopoint, RGEO_FACTORY
Now in your before_save, simply do:
self.geopoint = RGEO_FACTORY.point(latitude, longitude)
and it should work...
EDIT
If you want to use Postgis functions in your Rails models to get the GeoHash for instance, then you could do something like this:
post_geohash = Post.select("ST_GeoHash(geopoint) as geohash").where(id: some_post_id).geohash
You could also create an instance method that does that on your Post model:
def geohash
Post.select("ST_GeoHash(geopoint) as geohash").where(id: id).geohash
end
Not sure this works as it's not tested but you get the idea.

How to update a column without loading the object in ActiveRecord

Foo.where(:some_id => 1).update_all(:some_columnn => "1")
Is this the right way to update Foo? I don't want to do a find and update the object.
As of Rails 4, the conditions are no longer supplied on the update_all method, but are instead specified on the preceding collection. For example,
# updates everything, as usual
Foo.update_all(some_column: '1')
# update only the specified rows
Foo.where(some_id: 1).update_all(some_column: '1')
Yes it is the right way, but remember, no callbacks or validations will be executed.
BTW, update_all accepts conditions also. Like this
Foo.update_all({:some_columnn => "1"}, {:some_id => 1})
It is the right approach if you don't want to instantiate an object, but keep in mind that this also means it won't perform any of your models validations or callbacks - it goes straight to a SQL update command.
Further information
You can use conditions,according to the api of update_all
update_all(updates, conditions = nil, options = {})
So you can do:
Foo.update_all(:some_column => '1', :some_id => 1)

How to update a single attribute without touching the updated_at attribute?

How can I achieve this?
tried to create 2 methods, called
def disable_timestamps
ActiveRecord::Base.record_timestamps = false
end
def enable_timestamps
ActiveRecord::Base.record_timestamps = true
end
and the update method itself:
def increment_pagehit
update_attribute(:pagehit, pagehit+1)
end
turn timestamps on and off using callbacks like:
before_update :disable_timestamps, :only => :increment_pagehit
after_update :enable_timestamps, :only => :increment_pagehit
but it's not updating anything, even the desired attribute (pagehit).
Any advice? I don't want to have to create another table just to count the pagehits.
As an alternative to update_attribute, In Rails 3.1+ you can use update_column.
update_attribute skips validations, but will touch updated_at and execute callbacks.
update_column skips validations, does not touch updated_at, and does not execute callbacks.
Thus, update_column is a great choice if you don't want to affect updated_at and don't need callbacks.
See http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html for more information.
Also note that update_column will update the value of the attribute in the in-memory model and it won't be marked as dirty. For example:
p = Person.new(:name => "Nathan")
p.save
p.update_column(:name, "Andrew")
p.name == "Andrew" # True
p.name_changed? # False
If all you're wanting to do is increment a counter, I'd use the increment_counter method instead:
ModelName.increment_counter :pagehit, id
Is there a way to avoid automatically updating Rails timestamp fields?
Or closer to your question:
http://blog.bigbinary.com/2009/01/21/override-automatic-timestamp-in-activerecord-rails.html
it is not a good idea to do this:
self.class.update_all({ pagehit: pagehit+1 }, { id: id })
it should be
self.class.update_all("pagehit = pagehit + 1", { id: id })
the reason is if two requests are parallel, on the first version both will update the pagehits with the same number, as it uses the number saved in the Ruby memory. The second option uses the sql server to increase the number by 1, in case two of these queries come at the same time, the server will process them one after the other, and will end up with the correct number of pagehits.
To avoid Monkeypatchingtroubles you could also use ModelName.update_all for this purpose:
def increment_pagehit
self.class.update_all({ pagehit: pagehit+1 }, { id: id })
end
This also does not touch the timestamps on the record.
You also have decrement and increment (and their bang versions) which do not alter updated_at, do not go trigger validation callbacks and are obviously handy for counters / integers.
If precision is not really that important, and you don't expect the code to run many times, you can try altering the saved in the database updated_at value, like so:
u = User.first
u.name = "Alex 2" # make some changes...
u.updated_at = u.updated_at + 0.000001.second # alter updated_at
u.save
so that Rails will actually try to save the same value, and not replace it with Time.now.

Resources