Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

parse_strategy feature should allow richer matching with existing linked or embedded resources #85

Closed
joshco opened this issue Dec 9, 2013 · 18 comments

Comments

@joshco
Copy link

joshco commented Dec 9, 2013

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:

  1. If a collection property is not present in the request to the server, then leave the collection alone. Dont get rid of it.
  2. control how parse_strategy does its matching? How does it know which record it is supposed to be updating?
@joshco
Copy link
Author

joshco commented Dec 9, 2013

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?

@joshco
Copy link
Author

joshco commented Dec 9, 2013

One valuable strategy could be where

  1. The server individually matches up representations in incoming request body for embedded resources with existing resources on the server
  2. 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)

@apotonick
Copy link
Member

Ignoring how the matching would work, I see those fundamental concepts for strategies:

  1. Ruthlessly synchronize the existing collection with the incoming. Breaks if they're not the same size. This is what :sync does.
  2. Create a new Address for each item in the incoming addresses collection and replace the latter with the new collection. This is kinda of a "stupid POST" and is what the current ::collection implementation does.
  3. Make the ::collection :addresses configurable so Roar knows if it should create a new Address 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.

@joshco
Copy link
Author

joshco commented Dec 9, 2013

That is encouraging!

But what is the :instance option? I don't recognize it in the documentation.

@apotonick
Copy link
Member

The :instance option is defined here.

@apotonick
Copy link
Member

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"}
]
  1. Add those songs to album.
  2. Replace songs with new list ("Roxanne" and "So Lonely")

Adding New

songs: [
  {id: 1, title: "Roxanne"},
  {title: "Fallout"}
]
  1. Create "Fallout", Find "Roxanne"
  2. Add/Replace songs

Skipping/Deleting

songs: []
  1. Leave songs alone, don't invoke album.songs=,
  2. Delete all songs (we need this semantic as well).

@apotonick
Copy link
Member

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.

@andresf
Copy link
Contributor

andresf commented Dec 11, 2013

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?

@faust45
Copy link

faust45 commented Oct 16, 2014

Hi guys, just interesting is it any progress on this?

thanks for your work!

@apotonick
Copy link
Member

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 ?

@faust45
Copy link

faust45 commented Oct 16, 2014

@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"?

@apotonick
Copy link
Member

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.

@apotonick
Copy link
Member

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.

@apotonick
Copy link
Member

Parse strategies got superseded by populators which are way more intuitive. http://trailblazer.to/gems/representable/3.0/populator.html

@joshco
Copy link
Author

joshco commented Nov 27, 2015

that link doesnt work

@apotonick
Copy link
Member

I know, still writing it! 😉

@joshco
Copy link
Author

joshco commented Nov 28, 2015

hurry up! :)

@abinoam
Copy link

abinoam commented Jul 8, 2016

@apotonick https://github.com/apotonick/roar#syncing-objects still cites :parse_strategy

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants