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.

crud_flow_rails_7 index index new new index:w->new:n show show index->show new:s->show:w create show->index destroy edit edit show->edit edit:s->show:e update

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:

crud_flow_copy_as_new_query index index new new index:w->new:n show show index->show new:s->show:w create show->index destroy show->new copy edit edit show->edit edit:s->show:e update

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:

copy

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:

crud_flow_copy_as_view index index new new index:w->new:n show show index->show new:s->show:w create show->index destroy edit edit show->edit copy copy show->copy edit:s->show:e update copy:w->show:w

Does the navigation make sense to users? Here’s an activity diagram which includes a prospective UI.

copy

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.

  1. 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 …
  2. 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.
  3. 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.
  4. 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.
  5. 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.