Web Application Design: designing a copy feature on a CRUD interface
Feature: Users may copy a Wat's attributes to a new Wat.
Today we’re going to start looking into adding a “copy” feature to the CRUD interface created using Ruby on Rails scaffolding–see a couple of posts back. We have to start with the high level survey of just two impementations, and, to keep it brief, I’m only going to introduce the complications to be explored and examined later.
Firstly, I didn’t present the “abstract flow” for the Rails 7 scaffolding in the last post so lets look at that right now.
The requirement is to be able to create new resource based on the attributes of an existing resource.
Feature: Users may copy a Wat's attributes to a new Wat.
As a user, I want to be able to copy attributes from
an existing Wat to a new Wat, and be able to edit
the new Wat before it is persisted.
Of the possible ways to implement this, two strike me as immediately preferable and here I want to try to understand my instincts about them: why do I think they may be generally preferable and what would make one preferable to the other.
Copy as new query
Given A user visiting the view for Wat show
When the user follows the link to Wat copy
Then the user visits the view for Wat new
And the fields of Wat new are pre-filled with the copied Wat's values
The first idea is to allow the new
view to accept parameters. There
will be a link from the show
view to new
such that an HTTP request
like this will get made:
GET /wats/new?name=waat
The controller action for WatsController#new
will render the new
form with the fields pre-populated with the parameters provided. The
user can change the fields if they want, then submit a the form. The
abstract flow of this implementation looks like this:
This approach has a certain elegance: I see a pleasing symmetry in the abstract design, it’s minimal in implementation steps because those steps make good use of the existing actions, and it just seems like it’s “the simplest thing that could possibly work.”
There’s a couple of questions to ask before implementing a feature like this and, hopefully they’re covered in the feature’s specifications, but if not, one has to ask one of them.
So, is that something users would expect? Look at the workflow:
Does that seem right? Although, it seems “logical” to me, I have to admit, it might not seem that logical to others. Maybe it would be helpful to have some indication that we were copying an object and not creating one ex nihilo? We could detect the parameters being sent and change the headline on the page, however that would introduce either logic in the view or maybe the controller action would have to determine the headline and pass it to view–both undesirable complications.
For me, things fundamentally break down if there is a need for representing the original object on the copy form or if the copy feature needs to create a relationship between the original and copy. Once one of the parameters for an action is an identifier for an action’s resource, we ought to consider changing from a “collection” route to a “member” route. There’s no view to reuse that would make any sense here, we’ll have to make a new one.
Copy as view
Given A user visiting the view for Wat show
When the user follows the link to Wat copy
Then the user visits the view for Wat copy
And the fields of Wat copy are pre-filled with the copied Wat's values
And the user can see information about the Wat they want to copy.
The other idea is to create a dedicated view for copying. The request for it would be:
GET /wats/{id}/copy
The Wat copy view would combine aspects of the forms of the new
and
edit
views, presenting a form whose action is POST
and whose
fields are all prepopulated with values from the Wat
identified in
the URL. Other representations of the original Wat
could also be
made available, in the header, or a link back to it’s show
view.
The abstract flow for this implementation looks like this:
Does the navigation make sense to users? Here’s an activity diagram which includes a prospective UI.
I think this is an improvement, even if it’s somewhat more work to set up. Ruby on Rails conventions provide affordances in the form of partials and view variables bound in the the controller that make implementing a view like this less repetitive than it might be without them.
Summary and Questions
To generalize and conclude, we looked at two implementations of a copy feature:
- one that reused an existing interface enabling it to use parameters derived from the original object to copy, and
- one that created a new interface with a single parameter designating the object to copy
and we considered how a requirement that the original object be represented in the view, informed us that we should prefer creating new interface to reusing an existing one for this feature.
There are a couple of paths forward.
- I could look at the question of “why did I immediately dismiss
other possible implementations prefering ones that only affected
interfaces?” Gut instincts should sometimes be critically
examined. If I had to rationalize it, I would say it was because
view actions are “safe” and therefore we can add them more-or-less
fearlessly. However it does possibly complicates the “unsafe”
actions (
create
,update
,delete
). So … - If the feature required some special side effect for copying that wasn’t required for creating, how would that affect the implementation? What kind of requirements suggest that we should create a new “unsafe” action? We should probably use a different example when we get to these questions.
- Requirements can be vague or poorly expressed. This one might have been written “user may click a button …” which might suggest something else. I don’t think it’s reasonable to expect product owners to be as exacting as strictly necessary when defining new features, but there are, I think some critical questions to ask, kind of like we did here and that would be worth exploring.
- Both implementations require a way to extract the necessary attributes from the source object. I haven’t thought about it much but this aspect–mappings of objects to parameters–seems like something worth investigating.
- I started a test repo, but I don’t feel comfortable sharing it just yet, but when I do, I think looking at the actual implementations and the Rails features which smooth the way for each implementation are worth an appreciation post.
Anyway, that’s at least some of what I’m thinking about for the next posts.