【rails】複数のタグを付ける

# 複数タグを登録する

タグを紐づける勉強のため、タグ機能を作成していきます。
理想は、pixivのようなタグ入力機能を目指したいです。
一覧のタグをクリックしたら、タグが自動でフォームに入力される機能ですね。
おそらく、jsで実装できると思いますが、まだ実装の目処は立ってません。


## 今回できること
- ruby 3.1 
- rails 6.1
- アイテムを投稿するとき、タグを付けて投稿できる
- 編集するとき、タグを入れ替えられる
- タグだけ投稿できる


## アソシエーション
至ってシンプルです。


![複数タグ.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3084646/708f53b5-4792-d2cc-3827-8b1ab36f80f9.jpeg)


アプリケーションの作成

    rails new アプリケーション名

:::note
余談ですが、
今回、アプリ名をtagとつけたのが仇となり、Tagモデルが作成できませんでした。
Ttagモデルにしました。
アプリケーション名と、モデル名は一意だったのですね。知りませんでした。


:::
:::note warn
コピペするので、もしかしたらttagというタイポみたいな箇所があるかもしれません。気づいたら修正してますが、もしかしたらttagのまま残ってる可能性があります。
:::
## モデルの作成

    rails g model Item
    rails g model ItemTag
    rails g model Tag

## 各データベースファイルに記述します
``` []_create_items.rb
      t.string :name, null: false
      t.text :item_text, null: false
```

``` []_create_item_tags.rb
  def change
    create_table :item_tags do |t|

      t.integer :item_id, null: false
      t.integer :ttag_id, null: false

      t.timestamps
    end

      # 同じタグを２回保存するのは出来ないようにする
    add_index :item_tags, [:item_id, :tag_id], unique: true

  end
```

``` []_create_tags.rb
  def change
    create_table :tags do |t|

      t.string :tag_name, null: false

      t.timestamps
    end
        #同じ文字列を登録できないようにする
    add_index :tags,:tag_name, unique: true
  end
```
## 各モデルファイルに、記述します
```item.rb
  has_many :item_tags, dependent: :destroy
  has_many :tags, through: :item_tags

  validates :name,:item_text, presence: true


  #編集するとき、タグの入れ替えをする。
    def save_tag(sent_tags)
      unless sent_tags.empty?
        self.item_tags.destroy_all

        sent_tags.each do |new|
          new_post_tag = Ttag.find_or_create_by(tag_name: new)

          #同じタグを登録するとエラーになるので、include?　で回避
           unless self.tags.include?(new_post_tag)
              self.ttags << new_post_tag
          end
        end
      end
    end

```
:::note
説明
unless sent_tags.empty?
self.item_tags.destroy_all
- もし送信されたタグが空でない場合は、そのアイテムの既存のタグをすべて削除する


sent_tags.each do |new|
&nbsp; new_post_tag = Ttag.find_or_create_by(tag_name: new)
self.ttags << new_post_tag
- 送信された各タグについて、Ttagモデルから既存のタグを検索し、存在しない場合は新しく作成します。 

self.ttags << new_post_tag
- 左オペランドにあるオブジェクトに右オペランドにあるオブジェクトを追加する
:::

```tag.rb
  has_many :item_tags, dependent: :destroy
  has_many :items, through: :item_tags
```
```item_tag.rb
  belongs_to :tag
  belongs_to :item
```

- throughをつけることにより中間テーブルを経由して、Itemモデルと、Tagモデルを紐づけさせてます。


## コントローラの作成
    rails g homes top
    rails g controller items index new edit
    rails g controller tags index

- アイテムの一覧ページ 作成ページ、編集するページ
- タグ一覧ページ
を作成します。


## ルートファイルに記述

```routes.rb
  root to: "homes#top"
  resources :items
  resources :tags
  resources :item_tags,  only: [:index, :create, :destroy]
 #なんとなくitem_tagsをルーティングしてみたけど、いらないよね
```

## アイテムを投稿する
### itemsコントローラに記述していく

``` items_controller.rb
  def index
    @items = Item.all
    @tag_list = Tag.all
  end

  def show
    @item = Item.find(params[:id])
    @item_tags = @item.tags
  end

  def new
    @item = Item.new
    @tag = Tag.new
    @tag_list = Tag.all
  end

  def create
   @item = Item.new
   #params
   @item.name = params[:item][:name]
   @item.item_text = params[:item][:item_text]

   #ビューから取ってきたものを拾ってきている
   #split(nil)は非推奨らしいので、半角スペース
   tag_list = params[:item][:tag_name].split(" ")

    if @item.save!
      @item.save_tag(tag_list)
      redirect_to items_path
    else
      redirect_to new_item_path
    end
  end

  def edit
    @item = Item.find(params[:id])
    #pluck :tag_nameというカラムの値を取得する
    @tags = @item.ttags.pluck(:tag_name).join(" ")
    @tag_list = Ttag.all

  end

  def update
    @item = Item.find(params[:id])
    #フォームから送信されたタグ名のリスト
    tag_list = params[:item][:tag_name].split(" ")

    if @item.update(item_params)
      #save_tagitemは、モデルで定義してある
       @item.save_tag(tag_list)
       redirect_to item_path(@item.id)
    else
       redirect_to :edit
    end

  end

  def destroy

    @item = Item.find(params[:id])
    @item.destroy
    redirect_to items_path

  end


  private

  def item_params
    params.require(:item).permit(:name, :item_text)
  end


```
### itemsのindexビューに記述
```

<h1>アイテム一覧ページ</h1>

    <% @items.each do |item| %>
      アイテム名:
        <%= link_to item_path(item.id) do %>
          <%= item.name %>
        <% end %>

       <br>

     アイテム説明:
        <%= item.item_text %>

       <br>
    
      タグ: 
        <% item.ttags.each do |tag_list| %>
          <%= tag_list.tag_name %>
          
        <br>
        
        <% end %>
      
    <% end %>
```

### itemsのnewビューに記述
```
<h1>投稿画面</h1>
<%= link_to "一覧画面",root_path %>
<%= form_with model: @item do |f| %>
<p>タイトル</p>
  <%= f.text_field :name %>

<p>本文</p>
  <%= f.text_area :item_text %>

<p>タグ</p>
 <%= f.text_field :tag_name, placeholder: "タグを複数つけるには半角スペースで区切ってください" %>


  <%= f.submit "投稿" %>
<% end %>

<br>
下の一覧から、タグを押すとフォームに自動入力されるようにする。
pixivのタグ付けみたいにしたい。

<p>タグリスト</p>
<% @tag_list.each do |tag| %>
  <%= tag.tag_name %>
<% end %>
```

### itemsのeditビューに記述
```
<h1>編集ページ</h1>
<%= link_to "詳細ページ", item_path(@item.id) %><br>

<%= form_with model: @item do |f| %>
  <%= f.text_field :name %>
  <%= f.text_area :item_text %>

  <%= f.text_field :tag_name,value:@tags %>


<%= f.submit "更新" %>
<% end %>

以下のタグをクリックして、上のフォームに自動追加できるようにする
<% @tag_list.each do |tag_list| %>
  <%= tag_list.tag_name %>
<% end %>
```


## タグも投稿できるようにしていく。
### tagsコントローラに記述
``` tags_controller.rb
  def index
    @tags = Tag.all
    @tag = Tag.new
  end

  def create

   #一度に複数を登録する
   tags = tag_params[:tag_name].split(" ")

    tags.each do |tag|
       #create(カラム名: ブロック変数)
       #find_or_create_by 同じ値が存在する場合、既存のレコードを返し
       #存在しない場合には新しいレコードを作成する
      Ttag.find_or_create_by(tag_name: tag)
    end
   redirect_to tags_path
  end



  def destroy
    @tag = Ttag.find(params[:id])
    @tag.destroy
    redirect_to tags_path
  end


  private

  def tag_params
    params.require(:tag).permit(:tag_name)
  end
```

::: note
find_or_create_by(カラム名: ブロック変数)
       同じ値が存在する場合、既存のレコードを返し
       存在しない場合には新しいレコードを作成する

:::


## 終わり

タグ付けはできるようになった。
ここから、タグをクリックすると、そのタグがついているアイテムを検索できるようにしていく。

最終的には、pixivのようなタグ付けができるようにしていきたい。（目処はついていない）


## 参考にした記事

https://qiita.com/ki_87/items/a344ea566c88b10b950c

https://qiita.com/kyooooonaka/items/c2da052168a540372afd