Monday, June 21, 2010

Loading a readonly ActiveRecord object

I am working on a story, with my pair Jaju, which generates reports from database. The database has questions and answers. We want to generate report with all questions and answers in specific range. All we wanted to do is to load some data in readonly mode and had no intentions of updating the database. It turned out to be not that straightforward though.

The Question model has :has_many answers and Answer model :belongs_to question.

To begin with, the simplest thing to do is -

questions = Question.all(:include => :answers , :conditions => ["answer.created_date > ?", Date.today.to_formatted_s(:db)])

All well and good. One query which joins question and answer table is fired and we get questions with answers created after yesterday. But now, there is a new problem - our BA (Business Analyst) says - "Hey, This is not what I want. I want all the questions in the report, only the answers should get filtered if they are not created after given date. So even if question doesn't have answer in the given time range, I need the question in the report without the answer."

Ohh..the code that we have written, filters the questions if they do not have answers created after given date, but we can change it. Here is the next version of the code.

1| results = Question.all(:include => :answers)
2| results.each { |question|
3|   question.answers = question.answers.select { |answer|
4|       answer.created_date > Time.parse(Date.today.to_formatted_s(:db))
5|   }
6| }
Alright ! This is exactly what we want. We get all the questions, with answers created after Date.today. But wait, the test log has following query -

UPDATE `answer` SET question_id = NULL WHERE (question_id = 1747 AND answer_id IN (1099))

Damn !! Active record is setting question_id to null in answer table. This is dangerous !

We realised that this is happening because we are filtering the answers in the question object and reassigning it to question.answers (line 3 in the above code). question.answer is an object of AssociationProxy and when we reassign question.answer on line 3, it updates that in database.

We tried to find out whether we can load the complete active record object in the readonly mode. This can be done by -

results = Question.all(:include => :answers, :readonly => true)

But this doesn't fix the issue. It still updates the answer table. The :readonly flag is only applicable to the parent object. So any changes on the question object will not be persisted. However, changes in question.answers are still reflected in database.

The root of the problem is that we are assigning something to an association proxy object. So we need to avoid that part.
While looking at the activerecord code, we realised that activerecord maintains a hash of all the attributes and it is possible to add a new attribute to the hash (thanks to my pair). So we changed the code like below.

1| results = Question.all(:include => :answers)
2| results.each { |question|
3|   question["filtered_answers"] = question.answers.select { |answer|
4|       answer.created_date > Time.parse(Date.today.to_formatted_s(:db))
5|   }
6| }

So rather than changing the association proxy object, we are adding a new entry in the hash. Obviously now it does not update the database as well. Once I use the above code, I can refer to filtered questions as 'question.filtered_answers'

This is the work around that we used because we couldn't figure out a way to load the complete activerecord object hierarchy in readonly mode or 'detach' the activerecord object from session. But anyway, our code works and we got want we want without unnecessary database updates.

2 comments:

deepakporur said...

nice blog
http://www.greenstechnologys.com/java-training-course-content.html

Anonymous said...

nice article
Java training in Bangalore offered by Digitech Academy is the best in itself more visit:


java training in Bangalore

best java training institutes in Bangalore

best training institute for java in bangalore

java training in yelahanka