laravel icon indicating copy to clipboard operation
laravel copied to clipboard

Since Laravel support pivot columns in m-to-m relationships, is it possible to create an extension to support it here ?

Open minamassoud opened this issue 4 years ago • 16 comments

this is for only many-to-many relationships, since this kind of relations constitutes an additional table in the database, and sometimes this table can have some additional pivot columns.

According to jsonapi we can't include an "attributes" key in a relationship. However, we can add a "meta" key. so can we add pivot columns as the following example ?

{
  "data": {
    "type": "posts",
    "attributes": {
      "content": "...",
      "slug": "hello-world",
      "title": "Hello World"
    },
    "relationships": {
      "tags": {
        "data": [
          {
            "type": "tags",
            "id": "1",
            "meta": {
                "pivot": {
                     "published": true
                 }
            }
          }
        ]
      }
    }
  }
}

for now I am accomplishing this by the above request body example, but with also using the hooks in the controller to override the functionality to get the pivot and adding it to the flow.

in the documentation it is recommended to create a resource for that pivot table, but it just seems to be an overkill for this task, also it will introduce more routes

minamassoud avatar Aug 20 '21 09:08 minamassoud

I'm interested

xhava avatar Aug 23 '21 16:08 xhava

Personally I prefer the resource for the pivot. The reason being that it gives the client control over whether to eager-load the related resources or not. In your example, when serializing the posts resource, the client can't control whether it wants the meta member of the tags relationship serialized or not.

However I can see how your solution could be useful. I'm not going to have time to dev it though - there's plenty for me to already do, so I can't really justify spending time on this myself.

Your best approach would be to write a field that is like the BelongsToMany field provided by this package, but handles serializing and deserializing pivot attributes from meta. I'd be happy to help with that, but you'd need to write it due to my limited time.

lindyhopchris avatar Aug 23 '21 17:08 lindyhopchris

@lindyhopchris, so sorry for late reply, this month is the holidays month for me. :) I will surely work on that, should I put it as a PR request ?

minamassoud avatar Sep 02 '21 09:09 minamassoud

If you're going to do it as a PR request, you should make it a customisable feature of the BelongsToMany field. You should not change the current default behaviour of that field... i.e. using meta to populate the pivot should be off by default, so the developer would need to opt-in.

Your alternative would be to write it as a separate package. Your choice which approach to go for.

lindyhopchris avatar Sep 02 '21 09:09 lindyhopchris

@lindyhopchris I created that Gist for the belongsToManyWithPivot it will get the pivots if exists.

minamassoud avatar Sep 15 '21 11:09 minamassoud

Having thought about this, I'm planning to write a specific chapter in the docs about this issue. In summary, the way to do pivot values while complying with the spec is to use a pivot model and represent that in the API as its own resource. That way not only do you get the ability to create/update pivot values, you also get all the benefits of include paths, sparse fields etc etc.

That for me is the right way to do this while complying with the spec and getting all the benefits of the spec at the same time. For me it doesn't make sense to put pivot values in the meta of a relationship, because it then becomes completely unclear as to when these values should or shouldn't be serialized when encoding a resource identifier.

If someone wants this done another way, they'll need to write their own add on package to do it. I'd be willing to make limited changes to help them plug such a package in, but I want the default recommendation of this package to be - do pivot values with pivot models/resources.

Hope that all makes sense! I realise this might be slightly contentious, but sticking to the spec is always a good rule IMHO, especially where the spec already allows this to be done.

lindyhopchris avatar Jan 02 '22 18:01 lindyhopchris

this is for only many-to-many relationships, since this kind of relations constitutes an additional table in the database, and sometimes this table can have some additional pivot columns.

According to jsonapi we can't include an "attributes" key in a relationship. However, we can add a "meta" key. so can we add pivot columns as the following example ?

{
  "data": {
    "type": "posts",
    "attributes": {
      "content": "...",
      "slug": "hello-world",
      "title": "Hello World"
    },
    "relationships": {
      "tags": {
        "data": [
          {
            "type": "tags",
            "id": "1",
            "meta": {
                "pivot": {
                     "published": true
                 }
            }
          }
        ]
      }
    }
  }
}

for now I am accomplishing this by the above request body example, but with also using the hooks in the controller to override the functionality to get the pivot and adding it to the flow.

in the documentation it is recommended to create a resource for that pivot table, but it just seems to be an overkill for this task, also it will introduce more routes

@Mina-R-Meshriky can you let me know what you modified to get that working? I would like to do something similar without creating a resource for the pivot table.

ale1981 avatar Jul 26 '22 13:07 ale1981

you can check the following https://github.com/laravel-json-api/eloquent/pull/19

But bear in mind, that this is a custom implementation, it is not supported by this package. nor is it officially supported by the jsonapi specification.

you can create a new class for BelongsToMany and copy the code inside it, then use it instead of this package's BelongsToMany class.

minamassoud avatar Jul 26 '22 15:07 minamassoud

@Mina-R-Meshriky Thanks. That seems to be for populating pivot data rather than displaying it?

ale1981 avatar Jul 26 '22 16:07 ale1981

This is for storing the data. You can control what to display using a resource file

minamassoud avatar Jul 26 '22 16:07 minamassoud

@Mina-R-Meshriky I have a resource, however the docs don't mention anything related to displaying pivot values on the relationship along with the type and id, I would like to display a pivot value in the same way you are above, is this possible?

ale1981 avatar Jul 26 '22 17:07 ale1981

In the resource file => relationships function =>

$this->relation('relation')->withMeta([
'Name' => $this->pivot->variable
])

minamassoud avatar Jul 26 '22 18:07 minamassoud

@Mina-R-Meshriky The following gives me an error, Unable to encode compound document. If I remove the withMeta method it works?

    /**
     * Get the resource's relationships.
     *
     * @param Request|null $request
     * @return iterable
     */
    public function relationships($request): iterable
    {
        return [
            $this->relation('attributes')->withMeta([
                'value' => $this->pivot->raw_data
            ])
        ];      
    }

ale1981 avatar Jul 26 '22 19:07 ale1981

But what is the cause of the error. Enter to ur storage/logs and look at the error. The real error will be the one exactly after the unable to encode error.

minamassoud avatar Jul 26 '22 19:07 minamassoud

@Mina-R-Meshriky Thanks. However it seems that its not showing the meta value per relation, just as one value at the end of the relation array?

    /**
     * Get the resource's relationships.
     *
     * @param Request|null $request
     * @return iterable
     */
    public function relationships($request): iterable
    {
        return [
            $this->relation('attributes')->withMeta([
                'value' => 'test'
            ])
        ];      
    }
"data": [
        {
            "type": "items",
            "id": "23",
            "attributes": {
                "sku": "12345678",
                "name": "Item name",
                "enabled": true,
                "createdAt": "2017-10-31T10:24:29.000000Z",
                "updatedAt": "2022-07-14T15:12:15.650000Z"
            },
            "relationships": {
                "attributes": {
                    "links": {
                        "related": "/api/v2/items/23/attributes",
                        "self": "/api/v2/items/23/relationships/attributes"
                    },
                    "data": [
                        {
                            "type": "attributes",
                            "id": "72"
                        },
                        {
                            "type": "attributes",
                            "id": "25"
                        },
                        {
                            "type": "attributes",
                            "id": "8"
                        },
                        {
                            "type": "attributes",
                            "id": "5"
                        },
                        {
                            "type": "attributes",
                            "id": "103"
                        }
                    ],
                    "meta": {
                        "value": "test"
                    }
                }
            },
            "links": {
                "self": "/api/v2/items/23"
            }
        },

I would like it to show as

                    "data": [
                        {
                            "type": "attributes",
                            "id": "72",
                            "value": "test"
                        },
                        ....

ale1981 avatar Jul 26 '22 21:07 ale1981

Yes, the Meta will be on the whole relationship. and I usually use it only on BelongsTo relations.

I think you can read this part in the docs, it might help with your use case. https://laraveljsonapi.io/docs/2.0/resources/relationships.html#data

minamassoud avatar Jul 29 '22 15:07 minamassoud