Rails 2.1 will do partial SQL updates. This will improve performance, but it has the potential to create invalid records. Two update queries can each change an attribute that in combination makes a record invalid. Prior to Rails 2.1, the second update would change all the attributes, preventing this issue.
Here's an arbitrary example. Let's say we have an Employee class. Employees must have a salary of 1, 2, or 3. Managers must have a salary of 3, 4, or 5.
Here's the schema and the Employee class.
create_table :employees do |t| t.boolean :manager t.integer :salary end class Employee < ActiveRecord::Base validate :validate_salary protected def validate_salary valid_salary_range = manager? ? (3..6) : (1..3) unless valid_salary_range.include?(salary) errors.add_to_base("Salary is not valid.") end end end
Let's create an employee who is not a manager and has a salary of 3.
employee = Employee.create!(:manager => false, :salary => 3)
Web requests come in to separate processes to update the employee. Process 1 and process 2 each fetch the record before either process does an update.
update1 = Employee.find(employee.id) update2 = Employee.find(employee.id)
The first process makes the employee a manager. The salary stays at 3, which is valid for a manager.
update1.manager = true update1.save! #=> true
The second process lowers the employee's salary to 2, which is valid for a non-manager.
update2.salary = 2 update2.save! #=> true
The two updates in conjunction make the record invalid.
Employee.find(employee.id).valid? #=> false
Here is the SQL from the updates.
UPDATE `employees` SET `manager` = 1 WHERE `id` = 1 UPDATE `employees` SET `salary` = 2 WHERE `id` = 1
Prior to Rails 2.1, the SQL for the updates would have been:
UPDATE `employees` SET `salary` = 3, `manager` = 1 WHERE `id` = 1 UPDATE `employees` SET `salary` = 2, `manager` = 0 WHERE `id` = 1
This is only going to happen if two updates occur close to the same time, so it's more likely on high traffic sites. It also will only happen if a validation depends on the state of two or more attributes.
To prevent this problem, you can either use locking, or set partial_updates to false for the model. In this example, it would be Employee.partial_updates = false.