Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

Rails 3.2.7 - update_column breaks Hash-serialized attributes #7190

Closed
StanBright opened this Issue Jul 29, 2012 · 21 comments

Comments

Projects
None yet
8 participants

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.

Member

steveklabnik commented Jul 29, 2012

... 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?

Owner

rafaelfranca commented Jul 29, 2012

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

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

simonoff commented Aug 7, 2012

@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
Owner

rafaelfranca commented Aug 7, 2012

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

simonoff commented Aug 7, 2012

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

Owner

rafaelfranca commented Aug 7, 2012

It works if you quote the value.

simonoff commented Aug 7, 2012

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

Owner

rafaelfranca commented Aug 7, 2012

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

simonoff commented Aug 7, 2012

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
Owner

rafaelfranca commented Aug 7, 2012

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 commented Aug 7, 2012

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.

Owner

rafaelfranca commented Aug 7, 2012

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 commented Aug 7, 2012

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?

Owner

rafaelfranca commented Aug 7, 2012

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.

Owner

rafaelfranca commented Aug 7, 2012

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

simonoff commented Aug 7, 2012

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

espen commented Jul 15, 2013

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

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 commented Dec 7, 2013

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

Member

robin850 commented Dec 8, 2013

@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