5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-02-03

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

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

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

投稿画像テーブル(post_images)

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)

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)

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.モデルにアソシエーションの記述を行う

app/models/post_image.rb
class PostImage < ApplicationRecord
:
  has_many :tags, through: :post_image_tags
  has_many :post_image_tags, dependent: :destroy
:
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
:
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.モデルにタグの作成・更新のメソッドを定義

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で組み込む。
こちらは部分テンプレートのファイル。

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 )

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に代入

tag_list = params[:post_image][:tag_name].split(/[[:blank:]]/) 

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

###編集機能( edit, update )

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内に登録されているタグの取得を行っているこちらの一文についての手順は以下の通り

@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(" ")
=> "犬 チワワ"

以上で実装完了

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

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?