Inconsistency between association and send(method)

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

#{conflicting_value}

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

#{conflicting_value}

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

Phillip K. wrote:

In the Person.persist method, I figured I should include the ZipCode
when finding the conflicting_person:

I guess I figured wrong. I did a little experimenting in script/console
and discovered that if you load an association, then change the
foreign_key, the association is not refreshed until you save and reload
the object. For instance:

p = Person.find(2)
=>
p.zip_code_id
=> 27805
p.zip_code.city
=> ‘Springfield’
p.zip_code_id = 27754
=> 27754
p.zip_code.city
=> ‘Springfield’
p.save
=> true
p.zip_code_city
=> ‘Springfield’
p.reload
=>
p.zip_code.city
=> ‘Carlinville’

The first reference to an attribute of the zip_code object
(zip_code.city) loads the association into memory. On the other hand, if
I were to change the foreign_key first, it would be okay:

p = Person.find(2)
=>
p.zip_code_id
=> 27805
p.zip_code_id = 27754
=> 27754
p.zip_code.city
=> ‘Carlinville’

The end result is that I fixed my original problem by not including
zip_code when finding person and conflicting_person in Person.persist.
I do, however, have another problem, but I will put that in another
thread.

Peace,
Phillip