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. :)
Redbox - A rails compatible lightbox
Posted by Craig Ambrose on August 16, 2006 at 03:34 AM
[Redbox has been updated, Click Here for more information.]
Lightbox, and all it’s similarly named siblings, is a colleciton of javascript and css designed to show off some page content, such as an image, or a div, in a floating box in the center of the page, while the rest of the page appears dimmed behind it.
This is as good a way as any to do the kind of ‘modal popup’ that if familiar in desktop applications. However, I couldn’t find such a library that was fully compatible with rails ajax functionality, and I have a client needing multi-page forms within such a box.
So, I’ve created one as a rails plugin. Rather predictibly named ‘Redbox’, this plugin is heavily based on Codey Linley’s Thickbox library. However, thickbox uses JQuery, which is yet another big javascript library, and I’ve chosen to use prototype instead for Redbox, as most rails sites already use it.
Demo
Don’t listen to me talk about it. Go try it out:
http://www.craigambrose.com/redbox_demo
Installation
Redbox is a rails plugin. You can install it using:
script/plugin install svn://rubyforge.org/var/svn/ambroseplugins/redbox
Please not that this copies three files into the appropriate public directories, being redbox.js, redbox.css and redbox_spinner.gif. If you already have files with these names, they will be overridden.
If you are updating an existing redbox installation, or if you accidentally delete those files, you can have them copied into the right spots again by typing the following from within you vendor/plugins/redbox directory
@rake update_scripts
Setup
All you need to do is include the javascript and css files into your layout. You’ll also need to include the default rails javascript files (if you aren’t already).
<%= stylesheet_link_tag 'redbox' %><%= javascript_include_tag :defaults %><%= javascript_include_tag 'redbox' %>
Usage
Redbox provides three helpers which are used instead of a regular “link_to” helper when linking to a redbox.
link_to_redbox(name, id, html_options = {})
This is used if you already have an HTML element in your page (presumably hidden, but it doesn’t have to be) and you wish to use it for your redbox. Specify it by it’s id, and you’re in business.
link_to_component_redbox(name, url_options = {}, html_options = {})
This serves essentially the same purpose, but it uses the url_options supplied to load another page from your app into a hidden div on page load. This saves you having to do it yourself, but beware that there are definate performance implications to using components.
link_to_remote_redbox(name, url_options = {}, html_options = {})
This waits until the link is clicked on to load the redbox using ajax, and displays loading graphics while it’s waiting.
link_to_close_redbox(name, html_options = {})
Allows you to put a link (presumably inside the redbox) to close it. Other way to close it is to refresh the entire page, but obviously closing it with javascript is spiffier.
That’s really all there is too it. It’s no doubt far from perfect, and it hasn’t been tested outside of firefox and safari yet, so I’d welcome any feedback.
