Are 'New' and 'Edit' RESTful? (Rails)
Jon Sully
8 Minutes
But they come out-of-the-box!? Rails is RESTful!
This isn’t meant to be a long post, but I do want to take the time to explore 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):
Two routes stand out to me in this image. These two:
Lets walk through them so I can explain why.
GET
to/photos
. Canonically this would be the spoken equivalent to “get all of the photos”. Makes sense to me!POST
to/photos
. This one’s spoken equivalent would be more like, "here’s a new one, add it to the group". I can get on board with that.GET
to/photos/1
. Continuing the spoken-trend, this would be like "give me the details for this singular unit of the group (photo)“.PUT
to/photos/1
. Speaking this one out would probably be, "on this already-existing unit (photo), update the information on it”.DELETE
to/photos/1
. Maybe the simplest of all of them, "delete this already-existing unit (photo)“.
The important part about all of these actions and routes is that they are a pure abstraction of action and path without care toward visual interface (UI). 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, only how the under-the-hood request(s) need to (a) use the right HTTP verb, and (b) use the correct URL path.
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? What ‘state’ or information are we transferring to the client by answering the request? Actually none. It’s an additional (not-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
without actually transferring any information (yet).
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 scaffold a model:
rails g scaffold House address rooms:integer
Then 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 how to successfully create a new house by POST
ing to the path /houses
. edit
should give the user (on whatever platform they’re on) the necessary means or instructions for how to successfully edit the current values on a given house (id = :id
) by PATCH
ing to the path /houses/:id
.
There’s a subtle phrasing there that I want to elaborate on. The goal of /new
and /edit
is to provide the user with the instructions for how to do a subsequent RESTful action. Sure, that’s typically a Rails form that’s marked up and formatted in such a way that the user can enter data into HTML input fields and the browser will natively follow the Submit action system to send form-encoded data with the correct nesting / format, but it doesn’t have to be. Technically, the HTML page rendered at /new
could just be a paragraph explaining how to use cURL to POST
to /houses
. That would be a valid /new
view, from the Rails ethos perspective. Your users certainly wouldn’t appreciate it… and half of them probably wouldn’t understand it, but the point is not that /new
and /edit
are made to expose forms to execute subsequent requests — they’re made to give the user instructions for how to make a subsequent request. The form is just simplest mechanism of doing that. (And that’s why most forms have syntax like “fill out the fields below then hit submit” just to make it extra clear)
This is all contrasted with typical JSON REST API consumers that do have mechanisms built out for POST
ing to /houses
or PATCH
ing to /houses/:id
. JSON REST API consumers (one app acting as a ‘consumer’ talking to another app) 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 POST
s correctly every time. Have you ever integrated an app with Stripe? Then you’ve done this. Instead of providing a /new
or /edit
form every time your application wanted to create a new Payment
, Stripe told you (the developer) what format / shape to sent data to the JSON API endpoint in and you encoded that shape into your app directly. Now your app can send the correct shape every time and doesn’t need guidance on how to do so — it was hard-coded by you. This was indeed one of the goals of the REST convention — provide a predictable and reliable formatting for interacting with resources in other systems.
It applies beyond other consumer systems too. Sometimes even us developers like to interact with various services via command-line requests. Understanding RESTful endpoint norms makes that much easier! Netlify, for example, maintains a JSON API that contains essentially all of the functionality of their standard HTML/browser-based management interface. Because I know RESTful norms, I know that I can GET
to /sites
and expect to get back a list of all of my sites hosted on Netlify. Similarly, I can POST
to /sites
and expect to make a new one (provided I send the correct information with the request), or PUT
to /sites/jonsully
and expect to edit/update this site’s attributes. Just by following the resourceful conventions, I can traverse Netlify’s JSON API with ease via command line requests.
Okay Jon… so
new
andedit
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 lot of code or just a little code! It’s all part of learning to understand how a single controller can correctly serve a resource with different views, variants, and parameters following the resourceful style. I’ll write more on that in the future.
The answer also comes in my next post/series 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. 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: The series has been written! Rails Wizards Pt. 1