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

Hydration of belongsToMany relationship #20

Closed
egeriis opened this issue Aug 30, 2016 · 11 comments
Closed

Hydration of belongsToMany relationship #20

egeriis opened this issue Aug 30, 2016 · 11 comments
Milestone

Comments

@egeriis
Copy link

egeriis commented Aug 30, 2016

Hey!

I'm having an issue with my Hydrator, and I can't seem to figure our how to solve this.

    /**
     * @param RelationshipInterface $relationship
     * @param Post $model
     */
    protected function hydrateCompaniesRelationship(RelationshipInterface $relationship, User $user)
    {
        $user->companies()->attach($relationship->getIdentifiers()->getIds());
    }

This method is intended to register the companies that belongs to a user. But upon creating a new user it fails, as attach will attempt to register the relationship immediately and the user is still to have an ID issued by the database.

How do I go about this?

@lindyhopchris
Copy link
Member

Yeah, you need to skip hydration if creating then use the created hook in the controller.

In your hydrator:

/**
 * @param RelationshipInterface $relationship
 * @param Post $model
 */
protected function hydrateCompaniesRelationship(RelationshipInterface $relationship, User $user)
{
    if (!$user->exists) {
        return;
    }

    $user->companies()->attach($relationship->getIdentifiers()->getIds());
}

v0.4 controller:

protected function created(Model $model)
{
    $companies = $this->getResource()->getRelationships()->getRelated('companies');
    $this->hydrator->hydrateRelationship('companies', $relationship, $model);
}

In v0.5 I need to update the event hooks in the controller to receive the resource as the second method
argument as you cannot do $this->getResource().

@lindyhopchris
Copy link
Member

PS: happy for any suggestions if you think there's a better way of doing it, but this is working for me for any relationship where the model needs to exist first

@lindyhopchris lindyhopchris added this to the 0.5.0 milestone Aug 30, 2016
@egeriis
Copy link
Author

egeriis commented Aug 31, 2016

I'm not enough into this library to suggest alternatives. But it seems a bit dirty to have to do this in the controller, as the hydrator—from my intuitive perspective—should be responsible for all hydration.

If the Hydrator knows about calling hydrateCompaniesRelationship, couldn't it act on the model created hook if model does not yet exist?

@lindyhopchris
Copy link
Member

Yeah, good point.

I suppose there's a strong argument for this problem not being unique to Eloquent - i.e. there's a lot of database layers and abstractions that would need to do a hydration after creating the primary record.

So on that basis, I could add to the HydratorInterface a post-create hydration method for dealing with these scenarios. Then the EloquentController could automatically trigger this (probably immediately after calling $model->save() but before calling the created hook).

Not sure what to call the method, maybe hydrateCreated?

It'd require an EloquentHydrator but I was planning on putting one of those in anyway as there's already an EloquentSchema to extend.

@egeriis
Copy link
Author

egeriis commented Aug 31, 2016

Can you explain to an outsider, why the hydration of a newly created resource would be different from an existing?

@lindyhopchris
Copy link
Member

Ok, thinking about it again, then all the controller needs is a list of relationships that it needs to hydrate after creating the model. (As this problem only applies to creation, not to update.)

Not sure what to call it as it applied to any HasOneOrMany Eloquent relationships, but would need to use the resource relationship name.

So maybe your controller would have this property on it:

protected $hasOneOrMany = ['companies'];

And then the EloquentController save method would loop through those and call the hydrateRelationship method on those if it has just created the model.

@lindyhopchris
Copy link
Member

lindyhopchris commented Aug 31, 2016

Actually it wouldn't need that property as it's already got a list of relationship mappings in the $relationships property. So it'd just need to loop through the relationships on the received resource, map the key to the Eloquent model's key, then get the relationship from the model - and if it's an instance of HasOneOrMany, invoke the hydrator's relationship method.

That makes sense to me, but realise I might not have explained it well. Basically it's possible for the controller to work this out itself. The controller needs to work it out because it knows when the save has been executed, but the hydrator does not.

I'll add to v0.4

@lindyhopchris lindyhopchris removed this from the 0.5.0 milestone Aug 31, 2016
@egeriis
Copy link
Author

egeriis commented Aug 31, 2016

When updating a resource, how does it know to call hydrateSomethingRelationship? Does it simple traverse the relationships object?

@lindyhopchris
Copy link
Member

lindyhopchris commented Aug 31, 2016

@egeriis yeah, it traverses the relationship object received from the client. The hydrator is just meant to contain the logic of transferring data from client's representation of the resource to the domain record. The controller is the thing executing the CRUD operations (which is where I believe it should be because that's kind of the role of the controller.)

https://github.com/cloudcreativity/json-api/blob/master/src/Hydrator/AbstractHydrator.php#L96

@egeriis
Copy link
Author

egeriis commented Sep 8, 2016

Did you need anything from me, on this one?

@lindyhopchris
Copy link
Member

lindyhopchris commented Sep 9, 2016

Nothing more required - thanks for your input.

I've actually solved this and the demo app has a belongsToMany relationship on the posts resource to demo how it works, which is:

I've added an interface for a hydrator to indicate that it needs to hydrate related resources as well as the "primary" resource. This is actually really useful as I have a couple of resources in one of my apps that contain attributes from a related Eloquent model, so it can be used for that purpose as well as has-many relationships:
https://github.com/cloudcreativity/json-api/blob/feature/v0.6/src/Contracts/Hydrator/HydratesRelatedInterface.php#L49

The Eloquent controller on the 0.5.x-dev branch handles hydrators that implement this interface:
https://github.com/cloudcreativity/laravel-json-api/blob/feature/v0.5/src/Http/Controllers/EloquentController.php#L283

And additionally I've added an EloquentHydrator class which uses this to hydrate belongsToMany relationships - though you don't need to use this hydrator class, you can just implement the new interface on your existing hydrator:
https://github.com/cloudcreativity/laravel-json-api/blob/feature/v0.5/src/Hydrator/EloquentHydrator.php

Putting it all together, here is the new posts hydrator in the demo app:
https://github.com/cloudcreativity/demo-laravel-json-api/blob/laravel-5.3/app/JsonApi/Posts/Hydrator.php

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

No branches or pull requests

2 participants