【Ruby on Rails】 gemを使用しないタグ機能の実装（初心者向け）

##機能要件
・投稿フォームで任意の文字列を送信すると、タグとして投稿に結び付き保存される
・一度の入力で複数のタグの作成が可能（スペース区切りで入力）

##1.モデル・テーブルの作成
以下3つのモデルを作成
・画像投稿用のモデル（PostImage）
・タグ用のモデル（Tag）
・上記2つの中間テーブル（PostImageTag）

```terminal:terminal
$ rails g model PostImage
$ rails g model Tag 
$ rails g model PostImageTag
```

**投稿画像テーブル（post_images）**

```rb:XXXXXXXXXXXXXX_create_post_images.rb
class CreatePosts < ActiveRecord::Migration[5.0]
  def change
    create_table :post_images do |t|
      t.string :image_id 
      t.text :caption
      t.references :user, foreign_key: true

      t.timestamps
    end
    add_index :post_images,[:user_id,:created_at]
  end
end
```
**タグテーブル（tags）**

```rb:XXXXXXXXXXXXXX_create_tags.rb
class CreateTags < ActiveRecord::Migration[5.0]
  def change
    create_table :tags do |t|
      t.string :name

      t.timestamps
    end
    add_index :tags, :name, unique:true
  end
end
```
**中間テーブル（post_image_tags）**

```rb:XXXXXXXXXXXXXX_create_post_image_tag.rb
class CreatePostImageTags < ActiveRecord::Migration[5.0]
  def change
    create_table :post_image_tags do |t|
      t.integer :post_image_id
      t.integer :tag_id

      t.timestamps
    end
    add_index :post_image_tags, :post_image_id 
    add_index :post_image_tags, :tag_id
    add_index :post_image_tags, [:post_image_id,:tag_id],unique: true
  end
end
```

ファイルの修正が完了したら`rails db:migrate`でDBにテーブルを作成

##2.モデルにアソシエーションの記述を行う

```rb:app/models/post_image.rb
class PostImage < ApplicationRecord
:
  has_many :tags, through: :post_image_tags
  has_many :post_image_tags, dependent: :destroy
:
```

```rb:app/models/tag.rb
class Tag < ApplicationRecord
:
  validates :name, presence:true, length:{maximum:20}
  has_many :post_images, through: :post_image_tags
  has_many :post_image_tags, dependent: :destroy
:
```

```rb:app/models/post_image_tag.rb
class ArticleCategory < ApplicationRecord
  belongs_to :post_image
  belongs_to :tag
  validates :post_image_id, presence:true
  validates :tag_id, presence:true
end
```

##3.モデルにタグの作成・更新のメソッドを定義

```rb:app/models/post_image.rb
:
  def save_tags(save_post_image_tags)
    # 登録されているタグを取得
    current_tags = self.tags.pluck(:name) unless self.tags.nil?
    # 古いタグを取得（登録されているタグ - フォームから新規に送られてきたタグ）
    old_tags = current_tags - save_post_image_tags
    # 新しいタグの取得（フォームから新規に送られてきたタグ - 登録されているタグ）
    new_tags = save_post_image_tags - current_tags

    old_tags.each do |old_name|
    # 古いタグを削除
      self.tags.delete Tag.find_by(name: old_name)
    end

    new_tags.each do |new_name|
    # 新しいタグがテーブルに存在しなければ新規登録
      post_image_tag = Tag.find_or_create_by(name: new_name)
      self.tags << post_image_tag
    end
  end
:
```

##4. フォーム画面の作成

フォーム部分を部分テンプレートとして用意し、新規投稿および編集画面にrenderで組み込む。
こちらは部分テンプレートのファイル。

```erb:app/views/post_images/_index.html.erb
<div>
<%= form_with model: post_image, local:true do |f| %>
<%= render 'layouts/error_messages', model: f.object %>
  <div class="image-field">
    <%= attachment_image_tag post_image, :post_image, fallback: "no_image.jpg", size:"400x400" %>
    <%= f.attachment_field :post_image %>
  </div>

  <div class="form-group">
    <%= f.label :"説明文" %>
    <%= f.text_field :caption, autofocus: true %>
  </div>

  <div class="form-group">
    <%= f.label :"タグ" %>
    <%= f.text_field :tag, value: tag_list %>
  </div>

  <div class="submit-btn">
    <%= f.submit "Upload" %>
  </div>
</div>
```

##5.コントローラにアクションを定義

###新規投稿機能（ new, create ）

```rb:app/controllers/post_image_controller.rb
:
   def new
     @post_image = PostImage.new
     @tag = Tag.new
   end

   def create
     @post_image = current_user.post_images.new(post_image_params)
     # フォームから送られてきたタグの値を、空白区切りで分割し、配列として代入
     tag_list = params[:post_image][:tag_name].split(/[[:blank:]]/) 
　　　if @post_image.save
       # save_tagメソッドを呼び出し、フォームの値を引数に渡す
       @post_image.save_tags(tag_list)
       flash[:notice] = "投稿を作成しました"
       redirect_to post_image_path(@post_image)
     else
       render "new"
     end
   end
:
   private 
   
   def post_image_params
     params.require(:post_image).permit(:image, :caption)
   end
   # タグのフォーム入力値に対応するストロングパラメータ
   def tag_params
     params.require(:post_image).permit(:tag_name)
   end
:
```
コメントで書いた部分の記述をもう少し詳しく説明
こちらの行ではparamsのタグのフォーム入力値に値する部分を、`splitメソッド`を用いて空白区切りで分割し、配列にして`変数tag_list`に代入

```rb:
tag_list = params[:post_image][:tag_name].split(/[[:blank:]]/) 
```
`splitメソッド`の引数を`(/[[:blank:]]/)`にすることで、フォーム内の文字列を全角半角関係なくスペースで区切って配列を作成することができる。
また`.split(",")`のようにすれば、カンマ区切りで配列を作成できる。
この場合はユーザーへフォーム入力の際にカンマ区切りで記入するように指示する必要がある。
例えば（※複数のタグを作成する際は、タグをカンマ区切りで入力してください）とか。

###編集機能( edit, update )
```rb:app/controllers/post_image_controller.rb
   def edit
     @post_image = PostImage.find(params[:id])
     # @post_imageに結びついたタグを取得し、空白区切りで表示
     @tag_list = @post_image.tags.pluck(:name).join(" ")
   end

   def update
     @post_image = PostImage.find(params[:id])
     tag_list = params[:post_image][:tag_name].split(/[[:blank:]]/)
     if @post_image.update(post_image_params)
       @post_image.save_tags(tag_list)
       flash[:notice] = "投稿を更新しました"
       redirect_to post_image_path(@post_image)
     else
       render "edit"
     end
   end
```
`updateアクション`での処理は新規作成の際と流れは同じ
`editアクション`内でDB内に登録されているタグの取得を行っているこちらの一文についての手順は以下の通り

```rb
@tag_list = @post_image.tags.pluck(:name).join(" ")
```
・`pluckメソッド`を使用して`@post_image`に紐づいたタグの`nameカラム`の値を取得
・`joinメソッド`で配列の値を結合して文字列を作成

上記の処理内容をコンソールで見てみるとこんな感じ

```
$ rails console
> post_image = PostImage.find(1)
> post_image.tags
=>[
　　#<Tag id: 1, name: "犬", created_at:"~~~", updated_at:"~~~">, 
　　#<Tag id: 2, name: "チワワ", created_at: "~~~", updated_at: "~~~">
　　]
> post_image.tags.pluck(:name)
=> ["犬", "チワワ"] 
> post_image.tags.pluck(:name).join(" ")
=> "犬 チワワ"
```

以上で実装完了

**記載内容に間違いがあった場合はご指摘頂けると嬉しいです。
ご連絡お待ちしております。**