gemを使わない tag機能の実装

詳しく後日編集します

ポイント

  • 中間テーブル

  • 複数のテーブルを同時にcreateするモデルを使用

  • jsでajax通信

  • updateする

model

アソシエーションの記述
model/post.rb

class Post < ApplicationRecord
  belongs_to :user
  has_many :post_tag_relations, dependent: :destroy
  has_many :tags, through: :post_tag_relations
  has_one_attached :image

  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to :status
end

model/post.rb

class Tag < ApplicationRecord
  has_many :post_tag_relations, dependent: :destroy
  has_many :posts, through: :post_tag_relations
  //一意性のバリデーションはここに記述
  validates :name, uniqueness: true
end

model/post_tag_relation.rb

class PostTagRelation < ApplicationRecord
  belongs_to :post
  belongs_to :tag
end

model/posts_tag.rb
ここに同時保存の内容を記述(マイグレーションファイルはいらないので、rails gで作成しないで手作業で生成する)

class PostsTag
  include ActiveModel::Model
  attr_accessor :title, :article_text, :status_id, :category_id, :image, :name, :user_id, :post_id

  with_options presence: true do
    validates :title        , length: {maximum: 20}
    validates :article_text , length: {maximum: 300}
    validates :status_id    , numericality: {other_than: 1, message: "は--以外から選んでください"}
    validates :category_id    , numericality: {other_than: 1, message: "は--以外から選んでください"}
    validates :name
    validates :image
  end

  def save
    post = Post.create(title: title, article_text: article_text, status_id: status_id, category_id: category_id, image: image, user_id: user_id)
    tag = Tag.where(name: name).first_or_initialize
    tag.save

    PostTagRelation.create(post_id: post.id, tag_id: tag.id)
  end

  def update
    binding.pry
    @post = Post.where(id: post_id)
    post = @post.update(title: title, article_text: article_text, status_id: status_id, category_id: category_id, image: image, user_id: user_id)
    tag = Tag.where(name: name).first_or_initialize
    tag.save

    map = PostTagRelation.where(post_id: post_id)
    map.update(post_id: post_id, tag_id: tag.id)
  end
end

コントローラー

class PostsController < ApplicationController
  before_action :set_post, only: [:show, :edit, :update]

  def index
    @posts = Post.all.order(created_at: :desc)
  end

  def new
    @post = PostsTag.new
  end

  def create
    @post = PostsTag.new(post_params)
    if @post.valid?
      @post.save
      return redirect_to root_path
    else
      render :new
    end
  end

  def edit
    @form = PostsTag.new(title: @post.title, article_text: @post.article_text, status_id: @post.status_id, category_id: @post.category_id, user_id: current_user.id, post_id: @post.id,image: @post.image)
  end

  def update

    @form = PostsTag.new(update_params)
    if @form.valid?
      @form.update
      redirect_to root_path
    else
      render :edit
    end
  end

  def tag_search
    return nil if params[:keyword] == ""
    tag = Tag.where(['name LIKE ?',"%#{params[:keyword]}%"] )
    render json:{ keyword: tag }
  end

  private

  def post_params
    params.require(:posts_tag).permit(:title, :article_text, :status_id, :category_id, :image, :name).merge(user_id: current_user.id)
  end

  def update_params
    params.require(:posts_tag).permit(:title, :article_text, :status_id, :category_id, :image, :name).merge(user_id: current_user.id, post_id: params[:id])
  end

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

PostsTagで同時保存

class PostsTag
  include ActiveModel::Model
  attr_accessor :title, :article_text, :status_id, :category_id, :image, :name, :user_id, :post_id

  with_options presence: true do
    validates :title        , length: {maximum: 20}
    validates :article_text , length: {maximum: 300}
    validates :status_id    , numericality: {other_than: 1, message: "は--以外から選んでください"}
    validates :category_id    , numericality: {other_than: 1, message: "は--以外から選んでください"}
    validates :name
    validates :image
  end

  def save
    post = Post.create(title: title, article_text: article_text, status_id: status_id, category_id: category_id, image: image, user_id: user_id)
    tag = Tag.where(name: name).first_or_initialize
    tag.save

    PostTagRelation.create(post_id: post.id, tag_id: tag.id)
  end

  def update
    binding.pry
    @post = Post.where(id: post_id)
    post = @post.update(title: title, article_text: article_text, status_id: status_id, category_id: category_id, image: image, user_id: user_id)
    tag = Tag.where(name: name).first_or_initialize
    tag.save

    map = PostTagRelation.where(post_id: post_id)
    map.update(post_id: post_id, tag_id: tag.id)
  end
end

view(posts/new.html)

※ edit.htmlには@formを渡してます

<%= form_with model: @post, url: posts_path, local: true do |f|%>
      <%= render 'shared/error_messages', model: f.object %>

      <div class="field">
        <%= f.label :status_id, "公開/非公開 必須" %><br />
        <%= f.collection_select(:status_id, Status.all, :id, :name, {class:"genre-select"}) %>
      </div>

      <div class="field">
        <%= f.label :category_id, "カテゴリー 必須" %><br />
        <%= f.collection_select(:category_id, Category.all, :id, :name, {class:"genre-select"}) %>
      </div>

      <div class="tag-field", id='tag-field'>
        <%= f.label :name, "タグ" %>
        <%= f.text_field :name, class:"input-tag" %>
      </div>
      <div id="search-result">
      </div>

      <div class="field">
        <%= f.label :title, "記事タイトル" %><br />
        <%= f.text_field :title, id:"article_title" %>
      </div>

      <div class="field">
        <%= f.label :image, "投稿画像" %><br />
        <%= f.file_field :image, id:"post_image" %>
      </div>

      <div class="field">
        <%= f.label :article_text, "記事テキスト" %><br />
        <%= f.text_area :article_text, class: :form__text, id:"prototype_catch_copy" %>
      </div>

      <div class="actions">
        <%= f.submit "保存する", class: :form__btn  %>
      </div>
    <% end %>

参考: railsでタグ付け機能を実装する方法(投稿から編集まで