15 August 2009

Refactoring to REST : why context matters

The wrong path


The app I refactored licenses pictures. The licensee creates an order, selects pictures, finally submits the order, which is then reviewed by Mr licensor. The cart relied on on a current_order the same way current_user works (with a session id), to add or remove a picture from it.

A few months later we decided to extend the app to have automated invoices as well as payments online. Naturally I created an "Invoice" and a "Payment" model which both belonged to an "Order" with a "one to one relationship".

So, to access payments and invoices, I also relied on current_order.



That made sense at the begining : as soon as I accessed an Order#id it would check if the current_order was the same as the one found with params[:id] and if not, I updated it along with the cart.

#orders_controller.rb
def set_current_order
self.current_order==@order || self.current_cart= (self.current_order= @order).assets )
end


But what if i wanted to access a payment directly, without first going through the orders controller (e.g. in some email inviting a customer to pay) ? I would have to make sure the payment I'm accessing belongs to the user and then update current_order with @payment.order so I don't endup having any mismatch with a trailing current_order.

Not unsolvable but : ...

Bad guess !!! I should only access a payment through the order it belongs to. What I'm doing wrong here is setting the context (an order) after finding the object it lives in (it's payment).

The Importance of Context


Rule of thumb : get the context before the object it lives in !!!

Why you ask ? Because I could easily end up having a Payment view linking to an order it doesn't belong to.

That meant : instead of having links like this
link_to "payment", current_order.payment

...they should all be like this :
link_to "payment", [@order, :payment]


That way you have a consistent before_filter for every singleton (payment, invoice, items etc) belonging to an order.


Although the idea of having a single point of access through current_order seemed a good starting point, not translating it with the conventions REST resources suggest, had me tangled up in inconsistencies.

It might seem obvious to many of you, but with the :shallow=>true option you get in your routes, which enables access to a resource without its parent, one might forget why, in many cases, the context of a resource is more than relevant : necessary.


Next I will talk of form_for which doesn't play very well with a singleton resource.

Charly

04 August 2009

Tabbed Navigation for Rails

When I start a new rails app and write a tabbed menu, I always postpone the moment I'll determine how it's going to set the current tab. And when it comes to it, I usually do quick and dirty stuff with the controller name, the @category.name or whatever. But last time, facing a step by step form you could navigate through with tabs, I thought "enough of it I need a solution once and for all".

Sooo, looking at plugins first tabnav seemed to be the reference. But I wanted something i could easily tweek and understand, and this was just overkill for me needs.

Then I looked at the free for all tab helper which has some very interesting solutions, the most elegant being the one relying only on css and the body tag (no logic involved). But that wasn't flexible enough, they were all making the assumption the tabs would switch controllers.

So having to get my hands dirty I first refreshed my memory with my favourite source of hints and came up with what I believe is a very elegant and flexible solution.





This is minimalistic but you can see that it is very easily extendable. The main idea is that you are sendind to the tab class a matcher that is going to set the current tab.

My matcher was a bit complex as you can see below(it also sets the partial to render), but keep in mind that you could do exactly the same like this :

tab_for :controller => controller_name do |t|
...

and it works with no further logic.




What I appreciate most is that the logic for matching the tab is kept seperate ( it only relies on the params inside the links ) and the "tab_for do" syntax is very railsish & familiar.