ActiveRecord
More ActiveRecord Funkiness
Submitted by mmorsi on Mon, 2009-10-12 21:46Another caveat to look out for when using activerecord, especially for those of you who are using the nested set module and have optimistic locking turned on (which I'm guessing by the timestamp on that patch is making its ways to the Linux repos just about now).
It turns out when you go to destroy a model object in a nested set, it first updates the lft and rgt attributes before running the correct delete query against the db. eg
UPDATE "pools" SET lft = CASE WHEN lft > 13 THEN (lft - 2) ELSE lft END, rgt = CASE WHEN rgt > 13 THEN (rgt - 2 ELSE rgt END WHERE (1 = 1)
....
DELETE FROM "pools" WHERE "id" = 8 AND "lock_version" = 0
....
Unfortunately when optimistic locking is enabled (as it seems to be by default now), activerecord will detect the record has already been updated before it attempts to destroy it and will throw a "StaleObjectError" reporting "Attempted to delete a stale object". The object being used to perform the update, no longer reflects the current db state (granted it's only "lock_version", the db field used to perform lock checks, that is inconsistent, but that is enough to throw it off).
The only work around which I know of right now, save fixing ActiveRecord itself, is to use delete instead of destroy, which obviously is not optimal and doesn't work for all cases. I need to look into this issue some more myself, and will follow up with a better fix if I find it. Until then this also may help
Bit of weirdness with ActiveRecord
Submitted by mmorsi on Wed, 2009-09-30 20:26Figure I'd pass on a bit of weirdness concerning Ruby's ActiveRecord. Apparently when two models class are associated via rails the association takes place through a custom ActiveRecord association class, eg if a host model has many instances of the nic model, host.nics will be an instance of ActiveRecord::Associations::AssociationProxy and _not_ a mere array of nics. AssociationProxy acts very similarily to an array, providing the expected functionality to get/set associated classes.
All of this is as expected, the weirdness arising from the fact a call to host.nics.class would return the 'Array' class and not 'AssociationProxy'. This is because AssociationProxy hijacks many methods and uses them for its own purposes, including the 'class' method.
To access an array from an instance of AssociationProxy, merely call the 'all' method, eg
host.nics.all.find { |nic| some_test }
Trying to call the 'find' method on nics directly will result in an ActiveRecord error: "Couldn't find Nic without an ID" as the AssociationProxy find method is called instead of the one you want.
Difference between delete and destroy in ActiveRecord
Submitted by mmorsi on Wed, 2009-08-26 16:57This is well documented, but I keep running into the same issue and never learn from my mistakes. ;-)
When you invoke 'destroy' or 'destroy_all' on an ActiveRecord object, the ActiveRecord 'destruction' process is initiated, it analyzes the class you're deleting, it determines what it should do for dependencies, runs through validations, etc.
When you invoke 'delete' or 'delete_all' on an object, ActiveRecord merely tries to run the 'DELETE FROM tablename WHERE conditions' query against the db, performing no other ActiveRecord-level tasks.
This also holds true for the :dependent option passed into the ActiveRecord::Association methods (belongs_to, has_one, has_many, has_and_belongs_to_many)
Thus if you have a circular dependency between two model classes, and you want to delete both related records when one is deleted, one of the :dependent clauses _must_ be :destroy and the other :delete. if both are set to :destroy you'll have an infinite loop resulting in a "SystemStackError: stack level too deep" exception.
For example
class Person < ActiveRecord::Base
has_one :address, :dependent => :destroy
validate_presence_of :address
end
class Address < ActiveRecord::Base
belongs_to :person, :dependent => :delete
validates_presence_of :person
end
With this scenario, you are free to add a foreign key constraint to the person_id field of the addresses table and everything will still behave as expected. Best of luck.





