'New' and 'Edit' Aren't RESTful (Rails)

This isn't meant to be a long post, but I do want to take the time to clarify the 'new' and 'edit' default Rails controller actions and :resources routing defaults as it relates to "RESTful-ness" and/or resourceful routing. Let's start with what the Rails guide notes:

The foundation of RESTful routing is generally considered to be Roy Fielding’s doctoral thesis, Architectural Styles and the Design of Network-based Software Architectures. Fortunately, you need not read this entire document to understand how REST works in Rails. REST, an acronym for Representational State Transfer, boils down to two main principles for our purposes:

  • Using resource identifiers (which, for the purposes of discussion, you can think of as URLs) to represent resources
  • Transferring representations of the state of that resource between system components.

For example, to a Rails application a request such as this:

DELETE /photos/17

would be understood to refer to a photo resource with the ID of 17, and to indicate a desired action – deleting that resource. REST is a natural style for the architecture of web applications, and Rails makes it even more natural by using conventions to shield you from some of the RESTful complexities.

It goes on to note how the :resources command within the routes file generates these routes (following RESTful design):

chart

Two routes stand out in this image. These two:

chart h

But why? Well lets walk through the others with a RESTful eye. First, a GET to /photos — "get all of the photos". Seems pretty straightforward. Then a POST to /photos — "here's a new one, add it to the group", that makes sense. Then a GET on /photos/1 — "give me the details for this singular unit of the group", fair enough! Getting to PUT on /photos/1 — "on this already-existing unit, update the information on it". And finally a DELETE on /photos/1 — "delete this already-existing unit". The important part about all of these actions and routes is that they are a pure abstraction of action and path without care toward interface. Any system should be able to leverage these routes to understand and interact with the full data model for your application. They're simple — "give me (a) thing(s)" or "do something to a thing" and don't impose or presume any knowledge of how the stuff looks.

That's where edit and new differ. Let's look at those ones. First, a GET to /photos/new. What is that actually doing from a RESTful standpoint? Actually nothing. It's an additional (not-really-RESTful) route designed to provide an interface for a subsequent RESTful action for systems/users that need guidance in how to interact with your application. Same idea with a GET to /photos/1/edit — an additional path to provide an interface for a subsequent PUT to /photos/1.

If you take a look around outside the Rails ecosystem and read some of the generic "what's a RESTful API anyway?" type stuff, you'll see no mention of new or edit there, but perhaps the best proof I can provide is a fresh Rails app itself. Spin up a fresh Rails 6.1 app and run rails g scaffold House address exterior_color interior_color current_family_last_name rooms:integer square_feet:integer (it's a random example but it'll make more sense in the next post), pop open houses_controller.rb and you'll find these actions:

  # GET /houses or /houses.json
  def index
    @houses = House.all
  end

  # GET /houses/1 or /houses/1.json
  def show
  end

  # GET /houses/new
  def new
    @house = House.new
  end

  # GET /houses/1/edit
  def edit
  end

  # POST /houses or /houses.json
  def create
    @house = House.new(house_params)

    respond_to do |format|
      if @house.save
        format.html { redirect_to @house, notice: "House was successfully created." }
        format.json { render :show, status: :created, location: @house }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @house.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /houses/1 or /houses/1.json
  def update
    respond_to do |format|
      if @house.update(house_params)
        format.html { redirect_to @house, notice: "House was successfully updated." }
        format.json { render :show, status: :ok, location: @house }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @house.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /houses/1 or /houses/1.json
  def destroy
    @house.destroy
    respond_to do |format|
      format.html { redirect_to houses_url, notice: "House was successfully destroyed." }
      format.json { head :no_content }
    end
  end

Interesting. Notice how the comments above index, show, create, update, and destroy all call out a .json view format? Indeed index.json.jbuilder, show.json.jbuilder, and _house.json.jbuilder were all created for us. But the comments above new and edit don't note any .json format and the only views generated for new and edit were new.html.erb and edit.html.erb. Why?

Because users operating on an HTML interface typically don't have mechanisms for executing RESTful actions directly. So Rails (being an excellent framework at making us productive) gives us two routes beyond standard REST to give users without RESTful interfaces a means for executing RESTful actions. Typically this is in HTML, but it doesn't actually need to be. new should give the user (on whatever platform they're on) the necessary means or instructions for successfully creating a new house by POSTing to /houses. edit should give the user (on whatever platform they're on) the necessary means or instructions for successfully editing the current values on a given house by PATCHing to /houses/:id.

This is all contrasted with typical JSON REST API consumers that do have mechanisms built out (by other developers) for POSTing to /houses or PATCHing to /houses/:id. JSON REST API consumers (other systems, typically) don't need a new form to guide them in the correct way to format a subsequent POST, the developers of the consumer instead encoded the correct format into the consumer system's code so that the consumer system always POSTs correctly every time. This was indeed one of the goals of the REST framework — provide a predictable and reliable formatting for interacting with resources in other systems. It's not all systems though. Sometimes we (human) developers rely on RESTful endpoints even when 'talking' to a system in JSON over API endpoints with cURL or httpie.

Okay Jon... so new and edit aren't exactly part of the REST API spec... why does any of this matter?

Well, learning of course! Both in the sense of learning about how REST works, how Rails implements REST (and more), and how Rails communicates over various formats is good to know! It can be the difference between implementing a second controller / more code from not understanding how a single controller can correctly serve a resource with different views for each request format.

But also, the answer comes in my next post which gives an option for multi-page forms ("wizards") in Rails. The method I introduce there overrides the new method on a controller which got me thinking about whether or not that's "okay" from a Railsy-ness standpoint until I realized that my override is doing the same thing (providing a user with an interface to interact in RESTful ways) just a little differently!

EDIT: Next post written! Multi-page Forms ("Wizards") In Rails + Hotwire / Turbo Frames

Photo by @the_roaming_platypus on Unsplash


Want to stay in the loop? 1 - 2 emails/month.

No spam. No data selling. Never.


Latest Blogposts

Trailing Slashes and Gatsby

The Ins and Outs of How Gatsby Does Slashes

April 16 2021

Tool Highlights: Typora

An app that allows Markdown to be my primary writing syntax

March 23 2021

Comparing JAMstack and Rails

Two different tools for two different jobs

March 20 2021