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.

Thursday, June 17, 2010

Public defender methods - yet another Java 7 proposal

Support of lambdas and closures is the most important language feature that is proposed to ship with Java 7. With lambdas, it will be possible to implement some interesting methods into the old collection classes. Methods like find, select, reject, forEach, include can take a lambda and perform relevant operations on the collection object. However, adding these methods into existing collection interfaces would be a problem as it will break the existing implementations. To address this issue, the proposal of defender method is suggested.

Inherently static nature of Java restricts the developer from extending the functionality without affecting the existing usages. The current implementation of interfaces doesn't allow us to add a new method to interface without breaking all its implementation. Sometimes, there is a strong case for adding new behaviour in the interface so that its applicable to all the implementations. Currently the only way to do this is to change interface into an abstract class and change the code to extend this abstract class. However, we need to change at all the places where this interface is implemented and anyway, this change is not always possible because a class might already have a superclass. The proposal of public defender method suggests a solution.

This proposal allows to provide a default implementation for interface methods. This default implementation will be a static method on helper class and it will be invoked if the class implementing this interface does not provide any custom implementation.

The proposed syntax is -

public interface List {
extension public List select(#boolean(Object)) default ListHelper.select
// other list methods
}

Here, select method from the List interface takes a lambda (which is again a proposal) as argument. The 'extension' keyword signifies an extension method. It must have a default implementation which is provided by static select method from ListHelper class. This method has the same signature as the extension method and it will receive the the extended interface object (list) as the first argument. If the class implementing this List interface does not provide implementation, the select method from the ListHelper class will be used. This method from the helper class which provides the default implementation is called as public defender method, as it defends the classes that does not implement extension method.

Obviously, the specific implementations of the interface class can provide an optimised implementation for extension method.

As mentioned in the Baptiste Wicht's blog, with this approach, it is possible to include the static methods in Collections class to the List interface. This will allow us to invoke methods like reverse() on the list object itself, rather than passing the object to the static method in Collections class. Also, it will allow us to override these methods.

In general, this proposal will allow us to add the relevant methods to interfaces and be dependant on the classes with static utility methods.
so we will be able to do -

list.select(..)

instead of

ListHelper.select(list, ..)

The helper class will still hold the implementation, but objects need not be aware of them.

Along with the ability to extend the already existing interfaces, this proposal will have more profound effect on the programming style. It will be possible to create a interface in which, all the methods are extension methods with default implementation. Then, it will be possible to use this interface as 'mixin' and use it to achieve the effect of multiple inheritence. Also, abstract classes may not be required as the same functionality can be achieved by interfaces with few defender methods.

We can compare this proposal with the extension methods introduced in C# 3.0. Although not exactly same, conceptually both are same. C# extension methods allows us to add methods in the existing classes by defining the static methods in another static class. However, The visibility of C# extension methods is much more controller as the these methods are available only in the classes that import the static class with extension method. This is different from defender methods where extension methods will be available to all the implementations of the interface.

At the moment, defender methods is only a proposal for Java 7 and just like other proposals, we are not whether it will come up with Java 7. but nevertheless, this interesting language feature will improve the extendibility of dying Java language and will allow us to extend the existing library.