I’m working on a generic method to display conflicting values when there
is a concurrency violation using the optimistic locking mechanism in
Rails (lock_version and StaleObjectError). When a user submits a form
and there is a concurrency clash, I want to display the “new”,
conflicting values next to the values in the form. In my model’s persist
method, I trap the StaleObjectError and populate what I call the
conflicting object. As a simple example, consider a Person model:
def Person.persist(params)
conflicting_person = nil
person = Person.find_by_id(params[:person][:id])
begin
person.save
rescue ActiveRecord::StaleObjectError
conflicting_person = Person.find_by_id(person.id)
end
return person, conflicting_person
end
In the controller, I test the return value of conflicting_person and
decide what to do. If not nil, it gets assigned to an instance variable
and I render the form again. The partial includes code such as:
div.label do
_ ‘First Name:’
end
div.data do
f.text_field :first_name
end
_! conflicting_data(@person, @conflicting_person, :first_name)
div.clearer
and conflicting_data is a helper defined as
def conflicting_data(current_obj, conflicting_obj, attribute)
if conflicting_obj
if conflicting[attribute] != current[attribute]
conflicting_value = conflicting[attribute] # abbreviated for
simplicity
“
end
end
end
Suppose users A and B both query person John D… User A changes the
first name to ‘Johnathon’ and saves. User B changes first name to ‘Jack’
and tries to save. A message about concurrency will be displayed to User
B and next to the first name field ‘Johnathon’ will be display so the
user can see what values are different.
When working with an object’s immediate attributes, this is working out
very well. But I am having some difficulty when comparing nested
attributes. When there are concurrency clashes on Person attributes
(first_name, last_name, date_of_birth, etc), I am having no problems.
But a Person belongs to a ZipCode, and I am having problems with
conficting data in the ZipCodes. I made the following changes in an
attempt to get down one more level.
In the Person.persist method, I figured I should include the ZipCode
when finding the conflicting_person:
begin
person.save
rescue ActiveRecord::StaleObjectError
conflicting_person = Person.find_by_id(person.id, :include =>
:zip_code)
end
I also changed the finder earlier in Person.persist that finds the
person object that is going to be updated. The result is when person and
conflicting_person are returned at the end of Person.persist, both
should have associated zip_code data with them and not need to query the
database. I then changed conflicting_data to be
def conflicting_data(current_obj, conflicting_obj, attribute, nested_obj
= nil)
if conflicting_obj
current = nested_obj ? current_obj.send(nested_obj) : current_obj
conflicting = nested_obj ? conflicting_obj.send(nested_obj) :
conflicting_obj
if conflicting[attribute] != current[attribute]
conflicting_value = conflicting[attribute] # abbreviated for
simplicity
“
end
end
end
and it gets called like
conflicting_data(@person, @conflicting_person, :code, :zip_code)
This way, if there is no nested object, the immediate attribute of the
object is used, but when I pass in a nested object, the attribute of the
nested object is used. Functionally, the code works. The problem I’m
having is that when I do the two sends to get the nested objects of
current_obj and conflicting_obj, they are the same thing because the
current one ends up getting the zip code of the conflicting one. Using
the debugger, I have inspected current_obj and conflicting_obj and
verified that they have different values for zip_code_id, but when the
send methods complete, current and conflicting represent the same
ZipCode.
p current_obj.zip_code_id
=> 27754
p current_obj.send(:zip_code).id
=> 27805
p conflicting_obj.zip_code_id
=> 27805
p conflicting_obj.send(:zip_code).id
=> 27805
I would very much appreciate some insight into why this is happening.
Thanks.
Peace,
Phillip