Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Rails 3.2.7 - update_column breaks Hash-serialized attributes #7190

Closed
StanBright opened this Issue · 21 comments

8 participants

@StanBright

I just upgraded to Rails 3.2.7 and found an interesting bug (maybe).

As you know, there is a deprecation warning (since v3.2.7) for using "update_attribute". So I changed that to update_column everywhere my tests yielded.

For some of my model attributes I'm using "serialize ATTR_NAME". Hashes are serialized in those attributes. The problem is that if I switch to update_column from update_attribute - Hashes are serialized into Arrays.

So, my model ends up with "attribute == [key, value]" instead of "attribute == {key => value}" after update_column on an serialized attribute.

I just wrote a work-around for that case:

model.send "#{attr}=", hash
model.save!

... however it would be nice if we fix update_column.

@steveklabnik
Collaborator

... as it turns out, we're actually getting rid of update_column too.... a0b85b Sorry for the bit of interface churn.

Can you see if that behavior happens there, too?

@rafaelfranca

update_column will never works with serialized attributes because it write directly at the database. Please use update_attributes.

@StanBright

@rafaelfranca sure, I'll use update_attributes. It looks like I was just mislead by the deprecation warning.

@simonoff

@rafaelfranca As I understand update_column trying to save hash as YAML or something like this.

{'key1' => 'value1', 'key2' => 'value2', 'cells' => 100, 'duplicates' => 0, 'conflicts' => 0}
ActiveRecord::StatementInvalid:
       PG::Error: ERROR:  syntax error at or near "'---
       - key2
       - value2
       '"
       LINE 4: ','---
                 ^
       : UPDATE "table" SET "file_processing_info" = '---
       - key1
       - value1
       ','---
       - key2
       - value2
       ','---
       - cells
       - 100
       ','---
       - duplicates
       - 0
       ','---
       - conflicts
       - 0
       ' WHERE "table"."id" = 67
@rafaelfranca
Owner

@simonoff update_column doesn't work with serialized fields. Please use update_attributes.

@simonoff

@rafaelfranca I know. But update_column must save hash or array as string, if it only for direct update of column.

@rafaelfranca
Owner

It works if you quote the value.

@simonoff

@rafaelfranca But it is a bug. type_cast_attribute_for_write checks string if it number. Why not checking if it array or hash?

@rafaelfranca
Owner

Please, show the code that you are writing and getting this error.

@simonoff
u = User.last
  User Load (69.2ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT 1
=> #<User id: 1, email: "wow124@example.com", encrypted_password: "$2a$10$9QYzcUCGCMPrzREAUCMFFu7IxECTGOkb0kFeqqJfBuXb...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, confirmation_token: "66mR7oTApphCGMob4CqQ", confirmed_at: "2012-08-07 14:12:02", confirmation_sent_at: "2012-08-07 14:12:02", first_name: "BlaBlaBla", last_name: "User", cell_phone_number: "+380 66 5723473", street: nil, city: nil, country: nil, bank_swift_code: nil, iban_account_number: nil, created_at: "2012-08-07 14:12:02", updated_at: "2012-08-07 14:12:02", provider: nil, uid: nil, campaign_participator: false, unconfirmed_email: nil>
rails 3.2.7: >> u.update_column :email, {:key => 1, :value => 2}
  SQL (0.5ms)  UPDATE "users" SET "email" = '---
- :key
- 1
','---
- :value
- 2
' WHERE "users"."id" = 1
ActiveRecord::StatementInvalid: PG::Error: ERROR:  syntax error at or near "'---
- :value
- 2
'"
LINE 4: ','---
          ^
: UPDATE "users" SET "email" = '---
- :key
- 1
','---
- :value
- 2
' WHERE "users"."id" = 1
@rafaelfranca
Owner

So update_column doesn't work in this way. It is intended to be a low level method, so it doesn't accepts a Hash or Array, only raw values like strings and number.

If you want to make this work you should use the serialize feature.

@simonoff

Why it not raise error on broken value type?! Why not checking what value must be String or Fixnum?
I know what I can use serialize and I use it. But then I tried to update serialized field with this method I got error.
So I started looking deeply.
In any case this is a bug in type_cast_attribute_for_write method.

@rafaelfranca
Owner

Why is this a bug? It is raising an error. I think it is fine as it is. You should not use update_column to update serialized fields because it is low level. It should not deal with Hash and Array.

@simonoff

It's raising error on broken SQL. But must raise error on broken type.
OK, can I make a patch for this bug and documentation miss-understanding?

@rafaelfranca
Owner

I think that raise an error for broken SQL is fine since this method is low level. We don't check your SQL before it is sent to the database, so we don't need to check in this case too.

@rafaelfranca
Owner

Also if you want to change the documentation you can push directly to https://github.com/lifo/docrails

@simonoff

@rafaelfranca ok if it a low-level method then you need remove checking for number.

@espen

If you are saving to a hstore column you can do this with the pg-hstore gem:

def update_hstore_column(column, attributes)
  self.update_column column, PgHstore.dump( (self[column].nil? ? {} : self[column]).merge(attributes), true)
end
@gbonline

Hi, a solution to use the update_column method instead of update_attribute to avoid the callbacks is to cast the hash with to_yaml . Tested on Rails 3.2.13

u.update_column :email, {:key => 1, :value => 2}.to_yaml
@axsuul

Is there any way to update a serialized attribute like this without invoking callbacks? update_attributes invokes callbacks

@robin850
Collaborator

@axsuul : please ask such questions on the rubyonrails-talk mailing list

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.