l'essentiel est invisible pour les yeux

Wednesday, April 23, 2008

DRY routing with form generated by Rails helper methods, shouldn't write routings directly in Javascript code.

The routing solution in Rails is pretty powerful, so we can modify routing easiliy. This facility may become cause of bugs. If we wanna submit data asynchronously with Javascript, then we have to write some routing paths in source of Javascript directly. It's not DRY and it doesn't work together with Rails routing solution.

So we think example code which post a comment to the article. We have two resources, articles, comments.


map.resources :articles, :has_many => [:comments]


In this case, I guess most people use helper methods which built in rails.

<% # The generated path (aka action) is same as "/articles/:article_id/comments" %>
<% remote_for_for([@article, Comment.new]) do |f| %>
<%= f.text_field :body %>
<%= submit_tag 'Post' %>
<% end %>


It's just simple, not a problem.

Imagine create a basket like shopping card, it supports drag and drop user interface. We can drag products or image into basket. This example has routings is as follows:

map.resource :basket, :has_many => [:items]


And we have to implement drag and drop user interface, following code is sample implementation with jQuery. It has URL where submits item data. If someone will change Rails routings, this code will be sucks.


$(function(){
// FIXME: It's not DRY
var ADD_ITEMS_TO_BASKET_PATH = '/basket/items';

$('img.draggableItem').draggable();
$('#basket').droppable({
drop: function(e) {
$.ajax({
url: ADD_ITEM_TO_BASKET_PATH,
type: 'post',
success: function(){}
});
}
});
});

So I guess we should use forms generated by Rails helper methods, and It set form's visibility is hidden. You set a required data into fields, and submit the form.

In Rails view:

<% remote_form_for([@basket, Item.new], :html => {:id => 'add_to_basket', :style => 'display:none'}) do |f| %>
<%= f.hidden_field :id %>
<%= submit_tag 'Add an item' %>
<% end %>

In Javascript:

$(function(){
$('img.draggableItem').draggable();
$('#basket').droppable({
drop: function(e, ui) {
var itemId = itemId(e.target);
var form = $('#add_to_basket');
form.find('> input[name="item[id]"]').val(itemId);
$('#add_to_basket > input[type="submit"]').click();
}
});
});


In this case, we don't need to write any routings directly in Javascript, so It's DRY.