Search code examples
laravel-5eloquentmany-to-many

How should I approach making this form in Laravel 5.2?


When I started this project I avoided using Laravel's form helper because it seemed like a convoluted way to make a form that didn't increase readability at all. I now wish that I had because model form binding is a lot harder than I anticipated.

This project is a blog website and posts have been set up to have a many-to-many relationship with tags (models posted at the bottom along with table schemas). When I go to edit a post, I want to have the tags associated with that post already selected in the field, with the option to remove them as well as add new tags. This is what I have to start:

<div class="form-group">
    <select class="form-control select2-multi" name="tags[]" multiple="multiple">
        @foreach($post->tags as $tag)

            <option value="{{ $tag->id }}" selected>{{ $tag->name }}</option>

        @endforeach

        @foreach($tags as $tag)

            <option value="{{ $tag->id }}">{{ $tag->name }}</option>

        @endforeach
    </select>
</div>

I realized I have a problem in that tags that are printed out as selected also get printed out in the second foreach.

At this point I am stumped. I have two ideas for what to do but I want to follow what would be the best practice so any advice is appreciated:

  1. Use procedural programming in the controller to remove any tags from the tag array that match the $post->tags tags before passing it to the view.

  2. Create a method in the tags controller that builds a query to select all tags except those associated with a post who's ID is passed as a parameter.

My idea for a SQL query that would do this (but I am unsure how to do this in eloquent):

SELECT  *
FROM    tags
WHERE   id NOT IN(  SELECT  tag_id
                    FROM    posts INNER JOIN post_tag ON posts.id=post_tag.post_id)

Am I making this problem more complicated than it is? Should I just use the form helper to bind the data to my form?

--- Post and Tag Models plus DB Schemas ---

Post model

class Post extends Model
{
    protected $table    = 'posts';

    /**
     * Define relationship between posts and categories.
     *
     * @return eloquent relationship
     */
    public function category()
    {
        return $this->belongsTo('App\Category', 'category_id');
    }

    /**
     * Define relationship between posts and tags.
     *
     * @return eloquent relationship
     */
    public function tags()
    {
        return $this->belongsToMany('App\Tag', 'post_tag', 'post_id', 'tag_id');
    }
}

Tag model

class Tag extends Model
{
    protected $table    = "tags";
    public $timestamps  = false;

    public function posts()
    {
        return $this->belongsToMany('App\Post', 'post_tag', 'tag_id', 'post_id');
    }
}

Schemas

posts(id, title, body, slug, category_id, created_at, updated_at)
tags(id, name)
post_tag(id, post_id, tag_id)

Solution

  • This will be my suggestion.

    In you controller

    public function edit(Post $post){
    
        $tags = Tag::all();
        $postTags = $post->tags->pluck('id')->toArray();
    
        return view('edit', compact('tags', 'postTags'));
    }
    

    In your blade

    ...
    @foreach($tags as $tag)
    
        {{--We list all the tags--}}
    
        {{--While listing them, we check if the current tag is part of the tags belonging to this post--}}
    
        {{--If it belongs then we select it--}}
    
        <option value="{{ $tag->id }}" {{ in_array($tag->id, $postTags) ? "selected" : null }}>{{ $tag->name }}</option>
    
    @endforeach
    ...