Skip to content

Instantly share code, notes, and snippets.

@juliocesar
Created May 14, 2012 00:43
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save juliocesar/2690951 to your computer and use it in GitHub Desktop.
Save juliocesar/2690951 to your computer and use it in GitHub Desktop.
Rails the the tale of API design

Rails the the tale of API design

This all began in a discussion between myself Matt Allen. We're working on a project together. A project in which he started development on before I joined, using Rails with plugins such as simple_form, and resources such as accepts_nested_attributes (henceforth referred to as ANA). As he should, really.

I then joined in as a JavaScript developer, building an independent front-end, and got Matt to turn the existing Rails side of the app into an API server.

I soon enough hit a spot where I was submitting a resource which had zero or many nested resources in it. That then led me to deal with Rails' ANA API.

First, let's get a few things out of the way. I know that:

  • you can make Backbone work with it. So if you say "it's easy", etc, you're missing the point.
  • that it may be solved by an incredibly clever snippet of JS. Maybe.
  • that ANA works the way it does because when submitting a form in a Rails app (no JS), you need to be able to do stuff such as deleting 1 nested resource, while adding another 2, and so on. All in one go.
  • when Java/Spring devs create web services with shitty APIs, everyone cries foul. This is not shittty per se, but it's at least less than ideal.
  • some think a pure JS front-end is a bad idea. I don't. Silly, corner-case based, generalised arguments against it won't convince me otherwise.

From the perspective of creating an API, I think the way Rails ANA works has a few issues.

Problem 1: Object in root

I can't recall why, and at this point I don't care about why Rails finds it should include the object name in an object, and it's attributes in a nested object. I recall a while back when it started doing so by default (I was still doing a lot of Rails development), the first thing I did was disable it.

Why? The resource is referred to in the URL itself, so it's redundant. It creates the annoying situation of having to refer to the data being returned by the server as, say, response['book'][...]. It doesn't contribute to anything really.

Fortunately that's easy to solve, apparently.

Problem 2: Collections that aren't arrays

Now THIS is the main reason why the discussion started. Rails ANA puts nested models in an object, and it looks roughly like his for creating a new model:

POST  /books  { "book" : { "title" : "Foo", "notes_attributes": { "new_123123123": { "_destroy_": false, "content": "Blah" } } } }

I think it should look like:

POST  /books  { "title" : "Foo", "notes": [ { "content" : "Blah" } ] }

Refer to the 3rd item of the "I know that" section above before you consider explaining me why ANA works the way it does. This, however, is a web app where the front-end is JavaScript. If I need to add more notes to an existing book, I'll do this:

POST  /books/1/notes  [ { "content" : "Foo bar" } ]

OR delete:

DELETE  /books/1/notes/1

In a nutshell, what's not cool is for an API consumer to have to represent something that is naturally fit for a collection (an array) as an object, because Rails needs that for when you don't have JS enabled (or a dynamic interface).

What's also not cool is for an API consumer to have to know it should name it's hash/object keys in such or such way to achieve different effects for creating resources in the server. URLs and methods beat that.

Is that it?

Yes. Now again, I'm not saying this is a project-breaking kind of problem. It does however imply in any consumer of the API having to run laps for stuff they, ideally, wouldn't have to. And you'd get a more succinct, beautiful API in the process.

@Osseta
Copy link

Osseta commented May 14, 2012

One advantage to the notation with many add/delete/update requests embedded in the one request vrs multiple requests is that the first format allows the whole lot to be done within a database transaction allowing for an all or nothing style update. Depending on the business requirements this may be important.

Doing this using the Array format means you may need to consider having reserved "attributes" that the domain model can't use because the framework needs to use them. Or you need separate arrays for each actions (update/create/delete). Whatever the solution simple Arrays by themselves don't really contain enough meta data.

All I'm saying is that even for API only app there is still value, if required, to the object format.

@juliocesar
Copy link
Author

Whatever the solution simple Arrays by themselves don't really contain enough meta data.

In which case, you could do what Backbone does (nothing to do with making it compatible again, but as an example), and have an object for your collection, but keep the actual attributes in an array contained by said object.

E.g.:

{ "title": "Book 1", "notes" : { "metadata" : "test", "models": [ ... ] } }

The problem with the Rails' ANA object-for-collection approach is having "commands" built in hash keys, and using objects to contain collections directly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment