CRUD vs The Inline Create Form

Posted by Craig Ambrose on August 26, 2006 at 11:51 PM

The Problem

So you’ve listened to DHH talk about keeping your design CRUDy, and you want
all your controllers to do little more than handle the Create, Read, Update and Delete actions for a given model. I’d like to
talk a little about a very common case in which we hit a few problems with this, being the inline create form.

To give a concrete example, lets say that I have a simple blog application, with two models: Article (a single blog post),
and Comment (someone’s response to a particular post). Comment belongs_to Article, and Article has_many Comments.

In the show action for Article, we provide a link for the user to “Add a Comment”. The simplest implementation of this would
be for the link to send you to the “new” action on the Comments controller. If you’re using simply_restful, the link might look like
this:

<%= link_to "Add a Comment", new_comment_url %>

With this implementation, our code looks nice, but the interface isn’t ideal. We’d like this new comment form to appear, inline, in
the article show page. That requires some ajax, so let’s change it to:


<div id="new_comment_container">
<%= link_to_remote "Add a Comment", :url => new_comment_url, :update => 'new_comment_container' %>
</div>

Providing we remember to tell the comment show action to render with no layout, this will pop that page nicely into
our Article show page when the link is used. The downside, however, is that the user presses the link and has to wait.
Since there is no user feedback in browsers when ajax actions are submitted, we obviously need to do something like
display a spinner image, and perhaps de-activate the link in some way so that it can’t be clicked twice. If we do
this, the user has feedback, but they still have to wait.

Maybe you think this is OK, but unfortunately a large number of sites don’t make the user wait in this situation, and your
client may expect better. Users are used to waiting when the submit data. They don’t necessarily want to wait for the form
to come up, at least not in an ajax application where they don’t really need to.

So, we both know the solution. The new comment form has to be written into the article show template. This is such a common
technique that I’m sure I can get away with referring to it as a pattern, and putting it in capital letters. Behold
the Inline Create Form. Ok, so it’s not all that exciting, but we’re all doing it, and in my opinion the implementation introduces
some nasty smells that I’d like to try and minimise. Lets imagine a really naive implementation where the new comment
form is actually typed into the article’s show template. I know you wouldn’t do that, gentle reader, but please bare
with me. Also, for those who don’t do this often, here’s a reminder of what our code now looks like.


<%= link_to_function "Add a Comment", "Element.hide('add_comment_link'); Element.show('new_comment_form')", :id => 'add_comment_link' %>
<div id="new_comment_form" style="display: none">
...here is the form, imagine lots of inputs and labels and things...
</div>

Put the Form in a Partial

This one is obvious. We put the bunch of html that represents the new comment form, typically not including the hidden div
(new_comment_form) into a partial. You probably do this already. Even if the form isn’t used anywhere else, it’s such a
conceptually different piece of HTML from the rest of the article show template, that it deserves to be in a file of it’s very own.


<%= link_to_function "Add a Comment", "Element.hide('add_comment_link'); Element.show('new_comment_form')", :id => 'add_comment_link' %>
<div id="new_comment_form" style="display: none">
<%= render :partial => 'comment_form' %>
</div>

Put the Partial in the Comments View Folder

If this form is really the normal form for comments, and not something specific from the point of view of articles, then
it seems better to put it with the comments actions. If we move the file into the comments view folder, then we only
need to call it _form.rhtml, and we can still reference it from the article show action.


<%= link_to_function "Add a Comment", "Element.hide('add_comment_link'); Element.show('new_comment_form')", :id => 'add_comment_link' %>
<div id="new_comment_form" style="display: none">
<%= render :partial => 'comments/form' %>
</div>

Submit the Form to the Comments Controller

If case it wasn’t clear, the form gets submitted to the comments controller, even though it’s displayed on the article page. The
file _form.rhtml might look something like this.


<%= form_remote_tag :url => comment_url, :update => 'comment_form_body', :html => {:id => 'comment_form_body'} %>
...here is the form, imagine lots of inputs and labels and things...
<%= end_form_tag %>

The form submit gets handled by the create action in the comments controller. This action re-renders the form if a validation error
occurs, presumably by rendering the partial above.

What About Controller Code to Initialise the Form?

Realistically, we need some code to initialise the form when it is first called. In this simple example, it would probably
look like this (current_user is presumably a method on your application controller).


@comment = Comment.new
@comment.author = current_user

Now this is fairly minimal. We can stick it inside Articles.show, and the world isn’t going to end. If we end up using the form as a stand-alone
page, we can stick it in the comments new action as well. However, I certainly have real word example where the code is more complex
than this, and even if it is only a couple of lines of code, frankly this is wrong. Putting code to initialise the comment form, inside
the article show action, rather than inside the comment new action, and for a reason only based on speeding up the load time of a
user interaction is most definitely a bad smell.

The rails API docs suggest removing view/controller duplication using partials and filters. It’s true, we could move this controller
code into a before_filter, and apply that filter to the article show action. However, that does not improve the design problem even
a tiny bit. The only difference is that it obscures it. In agile development, we call this a deodorant. It doesn’t fix a bad smell
in the code, it just hides it.

Rails Components Solve This Problem

The force is more powerful on the dark side. Have a look at how show.rhtml would be written if we used components.


<%= link_to_function "Add a Comment", "Element.hide('add_comment_link'); Element.show('new_comment_form')", :id => 'add_comment_link' %>
<div id="new_comment_form" style="display: none">
<%= render_component :controller => 'comments', :action => 'new' %>
</div>

A component is an inline render to a totally different rails action, and the controller code for that action gets called too. Hey, that’s
exactly what we’re actually doing here! Anything else is just an optimisation. With the above code, the form initialisation code
stays where it belongs (in the comments new action). In fact, this code would allow the comments controller to behave in ‘full page mode’
as well, providing it knew to render it’s layout if it isn’t called as a component, but to not render it if it is (and yes, this is
easy to do). That gives us a non-ajax fallback, which might be important for some people. For myself, I just like the elegance of the
properly decoupled code.

Why Components are ‘Bad’

DHH doesn’t like components because he wants to build applications up from very cleverly designed building blocks, rather than sticking
vertical slices together (my words, but I think that’s what he means). He has a point, but that isn’t really what we’re trying to do
here. Or, if it is, the decision was made as soon as we decided that we wanted an inline form. If we really wanted to de-couple our
controllers, we shouldn’t have inlined the form at all. If we decide that it’s a nicer experience for the user if we do inline it
(and it is, which is why most apps do it), then we have already introduces the problem, and we need a solution. Of the examples
I’ve presented so far, the component approach yields the nicest code.

However, components have speed issues. Stefan Kaes
has done some profiling and found the component approach very slow. The main issue is lots of filters being called again. I am
yet to try profiling this is simpler cases. My applications, even large ones, don’t tend to have lots of before filters
(user authentication is the main one) so there may yet be a place for components. Or perhaps components, or something like them, can
be cut down a bit and told to re-use some of the loading code (such as all the filters that are held in common by the component’s
controller, and the controller that it was called from).

Summary

This problem is not solved, however I believe that it’s much more important that two lines of controller code. As I use simply_restfull
more, my controllers are becoming less coupled, and as a result, areas like this which are still entwining them in nasty ways
become more an more apparent. The inline create form is the simplest and most common example of this, but the problem is much more
general.

My long term prediction. If we can isolate the part of our control logic which is pure CRUD, then we can move that into the framework.
Then, writing control logic becomes about how those CRUD controllers hang together.

I have no idea what that would look like. :)

Tags: (none)
Hierarchy: previous, next

Comments

There are 9 comments on this post. Post yours →

Ian Heggie

What wrapping the ajax call in a proxy object that may choose to eagerly load, and then using the proxy object as needed? This should keep the initial response time fast because we are not processing or loading any “just in case” baggage, but then allowing the proxy objects to “get ready” behind the scenes so we can give a fast response when requested.

The choice of what gets eagerly loaded and in what order could then be moved to the controller as a set of hints (if you arn’t doing anything else, then consider loading me) ... this could be dynamic controlled depending on server load.

Then we decouple the choice of what gets eagerly loaded (even possibly order on a complex page) from the functionality of what happens when user X presses button Y.

Otherwise it is a bit like a running race without the “Get ready, get set” warnings before the “Go!” ...

Craig

I like where you’re going with that Ian. Although that would make it take longer overall for that form to be loaded, because it would now need two round-trips to the server, the page itself would load after the first trip (minus the form) and so the user wouldn’t notice. If they clicked on the button to show the form, before it had finished loading, then they would have to wait for it to load, but they would probably have to be a pretty quick draw to manage that.

Of course, we’re also getting fairly complex here. It’d need to be hidden behind some powerful mojo to make it no harder to use than normal. Something like the component interface would be good.

using simply_restful

or vanilla routes
‘comments’, :action => ‘new’ %>

Aaron Blohowiak

How much controller code are you using to initialize the model for the form? Are you sure that code wouldnt be better off in the model itself?

Craig

To be honest Aaron, not much at all. I think I’m mainly just bothered that there’s any there at all. Often it’s just Foo.new, but I wonder why I’m creating a new Foo object in the index action for Bar. It strikes me as a symptom of bad design, but I’m not exactly losing much sleep over it. :)

The main reason I think it’s bad design comes down to the fact that I’m only doing it for optimisation purposes, and optimisation (at least premature optimisation) is often the enemy of clean design. If I has some way that was just as efficient that allowed me to call the right piece of code, being the new action on the Foo controller, then I’d be much happier.

IIRC, you don’t actually need a Comment object to have a comment form. For defaults, you can either specify in the partial (question: how to decide if form defaults belong in presentation/model?), or for things like the author (in your example) they should be set in comments.create (don’t want people falsifying it).

Another idea off the top is to have a common mixin containing initialization code that you can put in both Author.show and Comment.new controllers. Still have the problem of referencing comment initialisation in the author controller, but at least it’s DRY.

kml

lkml

kml

lkml

I’d also like to see a nice solution to this. I have found that by using unobtrusive javascript (i’m using lowpro) with RESTful techniques, things tend to fall into place as the thing has to work with javascript turned off and as a result is more crudy by default.

I have found myself adding cross-domain view stuff with lowpro’s onReady event, I don’t know if this is just shifting the problem somewhere else? but it certainly makes for DRYer code.

felix

i unall to install the red box plugin .please help to me

Post a comment

Required fields in bold.