parse_strategy feature should allow richer matching with existing linked or embedded resources
joshco opened this issue · 18 comments
I've got a model for People and Addresses. People have many addresses.
In my PersonRepresenter, I've got:
collection :addresses, :class => Address, :extend => AddressRepresenter,
:embedded => true , :parse_strategy => :sync
I initially stumbled upon the sync thing because I noticed that if I did a POST of a person, but didn't include the _embedded address array, it would delete my existing address collection for a given person.
Note, I'm doing some matching on POSTs. If you do a post of a person and the server finds that a matching person already exists then it passes the existing person object to the consume! call in the person controller.
Is there a way I can:
- If a collection property is not present in the request to the server, then leave the collection alone. Dont get rid of it.
- control how parse_strategy does its matching? How does it know which record it is supposed to be updating?
Copy/Paste of Nick's response on roar-talk:
the new option parse_strategy: :sync is pretty dumb: Instead of automatically creating new Address instances for the :addresses collection, it uses the instances and syncs their properties.
However, this only works when the parsed document contains the exact same number of addresses than the object #from_json is being called onto.
What we need to do is provide some more "cleverer" strategies which find out which object is an existing object and which one should be created. These strategies could provide some standard semantics for handling PUTs and POSTs in classic Rails apps.
Should we try to identify the strategies in a github issue?
One valuable strategy could be where
- The server individually matches up representations in incoming request body for embedded resources with existing resources on the server
- The matching algorithm is likely to be specific to a given API in terms of what fields constitute a match or not. Therefore, the matching method is provided to roar as some sort of callback ( proc, labmda, inheritance etc)
Ignoring how the matching would work, I see those fundamental concepts for strategies:
- Ruthlessly synchronize the existing collection with the incoming. Breaks if they're not the same size. This is what
:sync
does. - Create a new
Address
for each item in the incomingaddresses
collection and replace the latter with the new collection. This is kinda of a "stupid POST" and is what the current::collection
implementation does. - Make the
::collection :addresses
configurable so Roar knows if it should create a newAddress
or retrieve an existing from the data layer. Configuration could be a lambda or a strategy. This would fit both PUT and POST semantics.
collection :addresses, parse_strategy: [
sync_with: lambda {|doc| a = Address.find(doc[:id]) ? a : Address.new }]
That could be abstracted with a dedicated strategy.
Actually, now that I look at it, this works already if you use the :instance
option.
That is encouraging!
But what is the :instance option? I don't recognize it in the documentation.
I'll use representable's Album has_many Song
model to sketch what we've to specify.
These 2 examples illustrate a POST or PUT to albums/best-of-police/
.
(UPDATE: What I wanna point out is: it's quite simple to implement the "model-syncing" in representable where representable will retrieve real object instances from the DB. However, we have to speak about different semantics when processing collections.)
Adding/Replacing
songs: [
{id: 1, title: "Roxanne"},
{id: 2, title: "So Lonely"}
]
- Add those songs to album.
- Replace songs with new list ("Roxanne" and "So Lonely")
Adding New
songs: [
{id: 1, title: "Roxanne"},
{title: "Fallout"}
]
- Create "Fallout", Find "Roxanne"
- Add/Replace songs
Skipping/Deleting
songs: []
- Leave
songs
alone, don't invokealbum.songs=
, - Delete all songs (we need this semantic as well).
After editing the last comment 3 times I finally understood what we want! It's two different things.
1. Process Incoming Collection
songs: [
{id: 1, title: "Roxanne"},
{title: "Fallout"}
]
Representable will parse this collection and sort out which item to retrieve from the DB (probably by checking the id:
field) and which to create.
2. Sync Collection
After the incoming collection is mapped to real objects, the user actually has to decide if he wants to add those objects, update the collection, update existing objects, only, etc.
That looks pretty good!
In the case of replacement, it would be nice to decide what to do with the replaced document. In some cases you might want to keep the resource available for others (e.g. removing a user from your twitter's "following" list) and in some cases you might want to delete the resource entirely (e.g. removing a tweet ... NSA notwithstanding). Perhaps a callback would work there?
Hi guys, just interesting is it any progress on this?
thanks for your work!
Yeah, totally! This goes into disposable
which will take care of abstracting model-specific operations to the form/representer layer. What are you trying to achieve, @faust45 ?
@apotonick thanks for feedback
i have json collections on client side
and i need perform operations delete/create/update on client side
then sync with server (Rails app)
i looking for some solution which allow me sync in simple way
what you mean "goes into disposable"?
Create and update already works with representable, only delete needs to be implemented.
Are we talking about collections or singular properties here?
Haha, disposable
is a gem I am working on to extract all the "real" database behaviour from representable/roar. Never mind, you won't need to know about it if you're using Roar.
Ok, some more explanations. Representable/Roar/Reform's job is transforming data to and from Ruby objects and not to create, find or delete ActiveRecord models. This belongs to another layer, which will be provided in disposable
.
Parse strategies got superseded by populators which are way more intuitive. http://trailblazer.to/gems/representable/3.0/populator.html
that link doesnt work
I know, still writing it! 😉
hurry up! :)
@apotonick https://github.com/apotonick/roar#syncing-objects still cites :parse_strategy