Ruby on RailsでGem無しでタグ機能をつける

タグ機能を実装するToxi法

多対多の関連付けをする

下の表のような3つのモデルを作ります。他のカラムを追加しても良いですが、最低限必要なカラムだけ表示しています。

次にモデル間の関連付けをします。

関連付けにはhas_many、belongs_toを使います。

・post.rb

class Post < ApplicationRecord
  has_many :tagmaps, dependent: :destroy
  has_many :tags, through: :tagmaps
end

・tag.rb

class Tag < ApplicationRecord
  has_many :tagmaps, dependent: :destroy
  has_many :posts, through: :tagmaps
end

・tagmap.rb

class Tagmap < ApplicationRecord
  belongs_to :post
  belongs_to :tag
end

タグ情報を新規で登録できるようにする

次に、タグ情報を登録できるようにしていきます。

posts.controller.rbのcreateに追加していきます。

def create
    @post = current_user.posts.build(post_params)
    tag_list = params[:post][:tag_name].delete(' ').delete(' ').split(',')
    if @post.save
      @post.save_posts(tag_list)
      redirect_to posts_path, success: '投稿しました'
    else
      flash.now[:danger] = '投稿に失敗しました'
      render :new
    end
end

今回追加したのは、3行目と5行目のコードです。

送られてきたタグ情報をsplit(“,”)でカンマ区切りにして、deleteでタグの前後にある半角スペースと全角スペースを消しています。

そして、save_postsメソッドでタグを保存していきます。save_postsメソッドはモデル側で設定します。

post.rb

def save_posts(tags)
    current_tags = self.tags.pluck(:tag_name) unless self.tags.nil?
    old_tags = current_tags - tags
    new_tags = tags - current_tags

    old_tags.each do |old_name|
      self.tags.delete Tag.find_by(tag_name: old_name)
    end

    new_tags.each do |new_name|
      post_tag = Tag.find_or_create_by(tag_name: new_name)
      self.tags << post_tag
    end
end

こんな感じで、既存のタグが被らないように、データベースに保存していきますが、ちょっとうまく説明できないので割愛させてもらいます。すみません。

次に、posts.controller.rbのupdateとeditも変更して、編集もできるようにしておきます。また、フォームも変更しておきます。

posts.controller.rb

  def edit
    @post = current_user.posts.find(params[:id])
    @tag_list = @post.tags.pluck(:tag_name).join(',')
  end

  def update
    @post = current_user.posts.find(params[:id])
    tag_list = params[:post][:tag_name].delete(' ').delete(' ').split(',')
    if @post.update(post_params)
      @post.save_posts(tag_list)
      redirect_to posts_path, success: '投稿を更新しました'
    else
      flash.now[:danger] = '投稿の更新に失敗しました'
      render :edit
    end
  end

form.html.erb

<div class="tag">
    <%= form.label :tag %>
    <%= form.text_field :tag_name, value: @tag_list %>
</div>

タグの表示

posts.controller.rb

def index
    if params[:tag_id]
      @tag_list = Tag.all #追加
      @tag = Tag.find(params[:tag_id])
      @posts = @tag.blogs.published.order(time: "DESC").page(params[:page]).per(10)
      @posts_side = Post.published.order(time: "DESC")
    else
      @tag_list = Tag.all #追加
      @posts = Post.published.order(time: "DESC").page(params[:page]).per(10)
      @posts_side = Post.published.order(time: "DESC")
    end
    respond_to do |format|
      format.html
      format.rss { render :layout => false }
    end
end

view

  <% @tag_list.each do |list| %>
     <p><%=link_to list.tag_name,posts_path(tag_id:list.id)%><%= list.posts.published.count %></p>
  <% end %>