Need help with belongsToMany attach

I have this concept of Posts and Tags with belongsToMany relationship, I have been able to attach tags to a post. Now I want to edit the post and attach/update the post tags. This is what I have but it not working as expected (I mean the update post tags part):

const post = await Post.find(params.id)

// TODO: validate form inputs

post.title = request.input('title')
post.slug = urlSlug(request.input('title'))
post.content = request.input('content')
post.meta_description = request.input('meta_description')
post.status = request.input('status')

await post.save()

// Update post tags
await post.tags()
          .attach(request.input('tags'), (row) => {
            if (row.post_id === post.id && row.tag_id in request.input('tags')) {
                return
            }
          })

This adds new rows to the pivot table irrelevant if the table already contains the post_id and the tag_id. I want it to skip the insertion if there is a row with the same post_id and tag_id.

@virk @romain.lanz @duyluonglc help on this :point_up: ?

Couple of things here.

  1. The 2nd param to the attach method is used to add extra params to the pivot table row and is not used for filtering.
  2. Can u share the migrations, or maybe a sample repo to reproduce the issue?

Hope it will work with your case

 // ..............
await post.save()

// Save tags
const tags = []
for (let tagName of request.input('tags')) {
    const tag = await Tag.findOrCreate({'name', tagName})
    tags.push(tag)
}
// Update post tags
await post.tags().attach(tags)

update: may the pivot table has duplicate records with similar post_id and tag_id
should call detach() before attach

await post.tags().detach()
await post.tags().attach(tags)
2 Likes

Posts:

table.increments()
table.integer('user_id').unsigned().notNullable()
table.string('title').notNullable()
table.string('slug').unique()
table.text('content').notNullable()
table.boolean('status').defaultTo(0)
table.text('meta_description').nullable()
table.timestamps()

Tags:

table.increments()
table.string('name').notNullable()
table.string('slug').unique()
table.text('description').nullable()
table.timestamps()

PostTag:

table.increments()
table.integer('post_id').unsigned()
table.integer('tag_id').unsigned()
table.timestamps()

Thanks for sharing the schema. So here are couple of issues.

  1. request.input('tags') will be a string and accept method accepts an array of ids to attach.
  2. Also tags inside the request will be in String and not Number, so you need to sanitize your values properly.

The following should work fine

  const tags = request.input('tags').split(',').map((tag) => Number(tag))
  await post.save()
  await post.tags().attach(tags)
1 Like

Didn’t work as expected. If I unselect a particular tag for a post then update, the tags remain unchanged.

Detaching the tags then reattaching them worked.

1 Like

To make it shorter, you can use the sync method to detach and attach.

Behave the same way as:
await post.tags().detach()
await post.tags().attach(tags)

await post.tags().sync(tags)