ruby-on-railssimple-formacts-as-taggable-onselect2-rails

How to use select2-rails with acts_as_taggable_on (simple_form)


I need to allow users to select tags for their posts, but from closed list (let's say only admin can add new tags, or they are predefined in other way). Each post can have many tags, so it needs to be multiselector. Unfortunately, after adding select2-rails tags weren't able to be saved in DB. How can I fix it? This doesn't work for me.

PostsController:

class PostsController < ApplicationController
  before_action :find_post, only: [:show, :edit, :update, :destroy]
  before_action :authenticate_user!, only: [:new, :create, :edit, :update, :destroy]

  def index
    @posts = Post.page(params[:page]).per(10)
    @tags = ActsAsTaggableOn::Tag.all
  end

  def new
    @post = current_user.posts.new
    @tags = ActsAsTaggableOn::Tag.all
  end

  def create
    @post = current_user.posts.new(post_params)
    if @post.save
      redirect_to @post, notice: _('Post created')
    else
      render :new, notice: _('Something went wrong')
    end
  end

  def show
    @tags = ActsAsTaggableOn::Tag.all
  end

  def edit
    authorize @post
    @tags = ActsAsTaggableOn::Tag.all
  end

  def update
    if @post.update(post_params)
      redirect_to @post, notice: _('Post updated')
    else
      render :edit, notice: _('Something went wrong')
    end
  end

  def destroy
    authorize @post
    if @post.destroy
      redirect_to root_path, notice: _('Post deleted')
    else
      redirect_to @post, notice: _('Something went wrong')
    end
  end

  private

  def post_params
    params.require(:post).permit(:title, :header, :content, :tag_list)
  end

  def find_post
    @post = Post.friendly.find(params[:id])
  end
end

Model:

class Post < ActiveRecord::Base
  extend FriendlyId
  friendly_id :title, use: :slugged
  acts_as_ordered_taggable_on :tags

  belongs_to :user

  validates :title, :header, :content, presence: true
  validates :title, uniqueness: true
end

View:

= simple_form_for @post do |f|
  = f.input :title
  = f.input :header
  = f.input :content, input_html: { rows: 10 }
  = f.input :tag_list, collection: @tags, input_html: { multiple: true, class: 'tags' }
  = f.submit 'Save', class: 'btn btn-warning'
= link_to _('Back'), :back

application.js :

$(document).ready(function(){
  $('.tags').select2({
    placeholder: 'Click to select',
    theme: 'bootstrap'
  });
});

Solution

  • Well, it was much easier than I thought. All I had to do was permit tag_list as an array tag_list: [] (default syntax :tag_list won't save tags in DB, as it was described here and here), but only this change caused strange behaviour - saving tags ids instead of names. Second thing, which was not mentioned in above links, is to explicity define label and value method for selector (it is important, because on default it would pass ids of tags, not tags names, so it will create new tags with names identical to ids of previous tags).

    In controller:

    def post_params
      params.require(:post).permit(:title, :header, :content, tag_list: [])
    end
    

    And in View:

    = simple_form_for @post do |f|
      = f.input :title
      = f.input :header
      = f.input :content, input_html: { rows: 10 }
      = f.input :tag_list, collection: @tags, value_method: :name, label_method: :name, input_html: { multiple: true, class: 'tags' }
      = f.submit 'Save', class: 'btn btn-warning'
    = link_to _('Back'), :back