I've recently been working on a project and came across an issue with the way field_changed? was handling incoming values. Specifically, if assigning a string value of "0" (eg, from an HTTP POST via update_attributes) to an integer column that is nullable, and with a previous value of 0, the field is marked as changed.
=> #<ActiveRecord::ConnectionAdapters::MysqlColumn:0x4edfd10 @sql_type="int(11)"
, @default=nil, @precision=nil, @primary=false, @limit=4, @name="severity", @nul
l=true, @type=:integer, @scale=nil>
>> bug = Bug.find(2)
=> #<Bug id: 2, summary: "Ticket with few changes", status: "Confirmed", severit
y: 0, ticket_type: "Defect", resolution: "Open", description: "With a descriptio
n", steps_to_reproduce: "", additional_information: "", project_id: 1, project_b
ug_id: 2, user_id: 1, owner_id: 2, created_at: "2008-08-04 14:09:56", updated_at
: "2008-08-04 14:11:17", priority: 2, comment: "ffffff">
>> bug.severity = "0"
=> "0"
>> bug.changes
=> {"severity"=>[0, 0]}
After some digging, it seems to be because in this very specific case, the incoming value is not typecast before comparison; it enters the conditional but then does nothing since the new value is not blank.
It seems that a solution is to simply move "value = column.type_cast(value)" outside of the conditional. IE,
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?
end
value = column.type_cast(value)
I haven't done a lot of testing to ensure that this does not create other problems, but in the few tests I ran it seemed to be alright. I've included the change I made in a (hopefully correct) patch file.