Friday, June 19, 2009

Rails Bug in *_changed?

UPDATE: The latest rails has fixed this problem but this problem was still present in Rails 2.2.2.

The new dirty methods in Rails show this interesting result:

u = Unit.find(123)
u.position #=> 14
u.position = "14"
u.position_changed? #=> false
u = Unit.find(42)
u.position #=> 0
u.position = "0"
u.position_changed? #=> true
view raw gistfile1.rb hosted with ❤ by GitHub


The above code has position as a nullable integer column. When parameters come in a post via the parameters hash position comes across as a string. For 14 you get the Rails goodness and assigning "14" is not seen as a change - but why not the same thing for 0?

The problem is in the following offending code in vendor/rails/activerecord/lib/active_record/dirty.rb file. I have commented out the problem line (look for the no!!!!...) and added what I believe to be the correct code below it:

def field_changed?(attr, old, value)
if column = column_for_attribute(attr)
if column.type == :integer && column.null && (old.nil? || old == 0)
# For nullable integer columns, NULL gets stored in database for blank (i.e. '') values.
# Hence we don't record it as a change if the value changes from nil to ''.
# If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
# be typecast back to 0 (''.to_i => 0)
value = nil if value.blank?
# else no!!!!!!!!!!! not the else branch
# value = column.type_cast(value)
end
value = column.type_cast(value)
end
old != value
end
view raw gistfile1.rb hosted with ❤ by GitHub