I’ve wanted to blog for a while now about some user experience problems I’ve seen with a RESTful approach in Rails. I love the RoR framework, and I think REST is very useful for giving a consistent structure to your web interface. But there are a couple things that harm a RESTful site’s usability.
The first comes about when a form gets submitted to create a new object, and it has errors. Before submitting, you were on
/employees/new, and now you’ve POSTed to
/employees. In the Ruby world, you gather up the error messages and re-render the form, but the URL doesn’t change. That means your location bar still shows
/employees, even though you’re seeing the same thing you saw on
/employees/new. It also means that if you type Ctrl-L, Enter to reload the page (am I the only one who does this?), you get either the index page (if it exists), a nasty routing error (if the developer was careless), or something else unexpected (if bad routing errors are handled).
You get similar weirdness when updating an existing object, because you PUT to
/employees/1, and re-requesting the page from the Location Bar takes you to the show page, not the edit one.
It’s not just Ctrl-L that’s broken. The Rails way also breaks bookmarking and (perhaps most important) sharing buttons (e.g. for Facebook and Twitter) and URL-based tracking (e.g. Google analytics).
It seems better either to create new objects by POSTing back to
/employees/new and update old ones by PUTing to
/employees/1/edit, or to issue a redirect on errors so the user goes back to the original page. I would prefer this second approach, because it keeps things consistent: the form for new objects is always at
/employes/new, never at
/employees, etc. It also preserves bookmarkability, sharability, and analytics.
Sadly, RoR’s way of storing errors on your model object makes the redirect approach impractical. You’d have to save model state and errors on some flash-like object. Just putting the messages into
flash[:error] isn’t quite good enough if you want to preserve the user’s input and highlight the problematic form fields. Then the form would need to know to check flash (or rather your improved version that has some drawer for not-to-be-rendered-as-a-notice type things), or your own code would have to pull the model out of flash, if it exists there. And of course flash is implemented via
session, so this might break with the new cookie-based session storage.
The second problem with REST involves the back button. Suppose you’re writing a survey, where each question can have one answer per user. The question appears on
/questions/1. That page contains a form either POSTing to
/questions/1/answers or PUTing to
/questions/1/answers/1. Now suppose I’m taking the test, so I see a question, POST a new answer, then have second thoughts. I click Back, change my answer, re-POST . . . and see an error message. This is because Rails wrote the form to POST to
/questions/1/answers, not to PUT to
/questions/1/answers/1. It would seem more intuitive to always POST to
/questions/1/answer (“answer” is a verb here).
Arguably my first complaint is about Rails rather than REST. The second complaint, on the other hand, seems to have no good RESTful solution. How can any RESTful framework know where to submit the form, if it has to work with the Back button? If Rails has any fault here, I’d say it is in making it so easy for
edit.html.erb to share the same form code, even though the form gets rendered differently in each case.