trailblazer / roar

Parse and render REST API documents using representers.

Home Page:http://www.trailblazer.to/gems/roar

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

parse_strategy: :find_or_instantiate requires all items in the collection to be present on update or deletes them otherwise

konung opened this issue · comments

It seems that parse_strategy: :find_or_instantiate behave unexpectedly. Well it least un-intuitively.

Here is an example of what I'm trying to achieve. Let's use Songs

Let's say I'm trying to send a patch REST action to add a new song to the playlist I previously created with 15 songs. I want to update 1 of them because I misspelled the name, and add 1 more .

PATCH to http://example.com/playlists/5

{
"title":"Alternative"
"songs":[
  {
    "title":"Smells like Teen Spirit",
  },
  {
    "id": 10,
    "title":"Spiders"
  }
]}

So what happens in my case is this:
All previous 15 songs that were in the collection get unlinked ( playlist_id gets unset) . And then a new song is added with an id of 16, and song number with id 10 get's updated to be linked to the playlist.

So all of a sudden I have 14 orphans records.

The only way to avoid this is to send all songs on each update with the existing ids.

This seems like unexpected behavior. I would expect a new record added and the one that I specified updated, but I wouldn't expect all the rest to be unlinked.

I'm using

representable (2.3.0)
  uber (~> 0.0.7)
roar (1.0.1)
  representable (>= 2.0.1, <= 3.0.0)
roar-rails (1.0.1)
  actionpack
  railties (>= 3.0.0)
  responders
  roar (>= 1.0.0, <= 1.1.0)
  test_xml (>= 0.1.6)
  uber (>= 0.0.5)

I just noticed comment here https://github.com/apotonick/representable/blob/master/lib/representable/parse_strategies.rb#L45

But that seems counter-intuitive. The only place this strategy is useful - is on create, not on update.

One more thought: I guess current behavior makes sense for smaller collections - but it will slow down the system for larger collections if you need to send back and forth thousands of items. Playlist - is a good example - it's common to have playlists with hundreds and even thousands of songs.

I introduced populators in Reform that are way more intuitive than parse strategies. I will remove the latter in the next bigger version. Please use a manual strategy for what you need to achieve.

BTW, there's no definition anywhere how to handle a case like yours, where you add and update items in PATCH. I am thinking about a media format for Roar, using HAL (I call it Object-HAL) where you can define an operation per item, so the API doesn't have to guess what you want.

Here's an example.

songs: [
  {id: 1, title: "Stain", _object: {operation: "update"},
  {title: "Brandy Wine", _object: {operation: "add"},
]

My goal is to make that more explicit. What do you think?

Hi Nick.
I'm going to take a look at populators right now - I'm not using Reform. Maybe I should?

Adding operation per object is not a bad idea, but:

  1. Not everyone is using HAL. My API is consumed by several different apps / devices - two of which have limited (older) json processing libs. ( They are not ruby based) .
  2. REST actions are pretty explicit already IMHO . Here is what I'm basing my approach on:

The HTTP RFC specifies that PUT must take a full new resource representation as the request entity. This means that if for example only certain attributes are provided, those should be removed (i.e. set to null).

An additional method called PATCH has been proposed recently. The semantics of this call are like PUT inthat it updates a resource, but unlike PUT, it applies a delta rather than replacing the entire resource. At the time of writing, PATCH was still a proposed standard waiting final approval.

From : http://restful-api-design.readthedocs.org/en/latest/methods.html#patch-vs-put

So the parse_strategy: :find_or_instantiate behaves as if it was a PUT, not a PATCH. But since you are planning to remove it in the next version - it's a moot point. So it sounds that I should switch to using instance: lamda{...} ?

Yes, use :instance and write your own populators because that's exactly how populators are implemented. Make sure to override :setter in case you don't want Representable to write to your model anymore (because you already do that in :instance).

It would be cool if you post some code here what you ended up doing, because this will help me to design the new populator API (both for Reform and Roar)! 🍻

To be honest, the more I read about REST and PUT and POST and PATCH the less I am convinced that we will ever agree on how it works, so I started working on a "common-sense" implementation of my own media format extension.

Anyways, sounds like you know what you're doing, so please keep me in the loop!

I changed that in Representable 2.4, it now behaves exactly as it does in Reform. You simply use a :populator and can do everything manually. I'll blog about it soon, be ready to test it! 😬

Thanks. I'm probably 2 weeks away of getting back to my JSON API. I'm actually in the midst of refactoring my service object / form objects layers as Trailblazer (operations & reform) right now. Cells are next. API - Representers ( I use it primarly to drive my JSON api for 1 specific app / prupose right now) is after that . Exciting times.

Awesome!!!!!!!!!!!! 🍻