When I modify an ActiveRecord object during a session, I cannot seem to retrieve this modified instance in a method call. A simplified example follows:
Assume we have a model with only two objects: Project and Task linked with a 1-n relationship. Both objects can be active, but tasks require their parent project to be active before activating. There are two ways to activate: globally through the Project (which activates all tasks) or individually through a Task.
With the following straightforward implementation, an error occurs:
class Project < ActiveRecord::Base
# Relations
has_many :tasks
def activate
self.transaction do
self.active = true
tasks.each {|task| task.activate}
end
end
end
class Task < ActiveRecord::Base
# Relations
belongs_to :project
def activate
raise ArgumentError, "Cannot activate a task of an inactive project" unless project.active?
self.active = true
end
end
Indeed, the console will report
>> project = Project.first
=> #<Project id: 1, name: "Test project", active: false>
>> project.activate
ArgumentError: Cannot activate a task of an inactive project
from /Rails/cache_issue/app/models/task.rb:7:in `activate'
from /Rails/cache_issue/app/models/project.rb:9:in `activate'
The problem is that the Project object instance modified in the Project#activate method is not the same that ActiveRecord loads when accessing the Task#project relationship in the Task#activate method. When debugging, both objects are the “same” ActiveRecord record, but not the same Ruby object instance.
>> project = Project.first
=> #<Project id: 1, name: "Test project", active: false>
>> project.activate
"Project#activate: self.id = 1, self.object_id = 2176477060"
" Task#activate: project.id = 1, project.object_id = 2176246440"
ArgumentError: Cannot activate a task of an inactive project
from /Rails/cache_issue/app/models/task.rb:8:in `activate'
from /Rails/cache_issue/app/models/project.rb:10:in `activate'
In other ORM systems, fetching a model instance by database identifier always looks in “cache”, at least during a transaction and even during a session. I have tried to eager load the relations, but that does not change the issue since I could still be using another Project instance than the one ActiveRecord decided to link to the Task object.
Is there any technique (or gem or third-party) to get this simple process to work? That is that every reference to the same ActiveRecord record during a session/thread always refers to the same Ruby object instance?
Thanks,
-Jason
Here are a couple of things you can try.
redefining Project#activate so that it saves the Project before any of the tasks are activated.
Essentially project is being loaded from that database by each as it check the activated status of the associated project. Saving the project first should fix that.
Use autosave and set the tasks active status directly.
N.B. Requires Rails 2.3. Also, the tasks of a project will not be activated until the project is saved.
Have Task#activate accept a boolean argument indicating whether or not to check if the associated project is activated. Essentially mirroring ActiveRecord::Base#save.