84
69

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 5 years have passed since last update.

GeekSalon東京(大学生限定プログラミングコミュニティ)Advent Calendar 2019

Day 14

railsでタグ検索機能を実装し多対多を理解する

Last updated at Posted at 2019-11-11

#1.概要
railsでアプリを作っていて、投稿が多くなってきた時にタグ機能で整理したい!タグ検索機能を実装して、見やすいサイトを作りたい!こんなこと思う人多いのではないでしょうか。
今回は簡単なrails投稿アプリを開発しながら、タグ検索機能を実装して行きます。
※なお、今回はGemを使っていません&&用意されたタグをユーザーが選択してタグ付けとする予定なのでユーザーがタグを作成する機能はここでは想定していません。

環境
ruby 2.5.6
Rails 6.0.0

##こんな感じのタグ検索機能が完成する予定
Qiita-tag-gif3.gif

#2.タグ付け機能の実装
##2.1.事前準備
まずタグ機能を実装するために投稿するだけのサンプルアプリを作っていきます。

ターミナル
cd 
cd desktop 
rails new TagSample

それでは投稿周りの機能をscaffoldを利用して一瞬で作成します。

ターミナル
cd TagSample
rails generate scaffold post body:text title:string
rails db:migrate

##2.2.モデル作成
まず、tagモデルを作成します。

ターミナル
rails generate model tag name:string

続いてpostモデルとtagモデルの中間テーブルとなるpost_tagモデルを作成して行きます。中間テーブルの作成にはreferencesというパラメーターを用いてpostモデル及びtagモデルの中間テーブルであることを示します。

ターミナル
rails generate model post_tag post:references tag:references
rails db:migrate

これにてモデルの作成は完了しました。db/schema.rbが以下のようになっているはずです。

schema.rb
ActiveRecord::Schema.define(version: 2019_10_30_135534) do

  create_table "post_tags", force: :cascade do |t|
    t.integer "post_id", null: false
    t.integer "tag_id", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["post_id"], name: "index_post_tags_on_post_id"
    t.index ["tag_id"], name: "index_post_tags_on_tag_id"
  end

  create_table "posts", force: :cascade do |t|
    t.text "body"
    t.text "title"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  create_table "tags", force: :cascade do |t|
    t.string "name"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  add_foreign_key "post_tags", "posts"
  add_foreign_key "post_tags", "tags"
end

##2.3.アソシエーションの設定
まず、中間テーブルであるpost_tagモデルにはreferencesにより以下のような記述がすでにあるはずです。
これはpost_tagをpostモデル、tagモデルそれぞれ一つづつと関連させています。

post_tag.rb
class PostTag < ApplicationRecord
  belongs_to :post
  belongs_to :tag
end

続いてtagモデルとpostモデルにそれぞれを関連づける設定を追記します。

tag.rb
class Tag < ApplicationRecord
    has_many :post_tags, dependent: :destroy
    has_many :posts, through: :post_tags
end

ここでは、


has_many :post_tags, dependent: :destroy

によりtagモデルとpost_tagモデルの関連づけ。及びタグが削除された時にpostモデルとの関連付けも自動で削除するように設定しています。

has_many :posts, through: :post_tags

これによって、中間テーブルであるpost_tagモデルを介してのpostモデルとの関連付けを記述しています。
また、以下に書きますが、postモデルに対してもその逆の記述をし、中間テーブルを介してtagモデルと関連付けています

post.rb
class Post < ApplicationRecord
    has_many :post_tags, dependent: :destroy
    has_many :tags, through: :post_tags
end

これでアソシエーションの記述は終了です。
##2.4.seed.rbにタグレコードを追加
seedを以下のように記述することで、tagテーブルのnameカラムに値を追加してタグを生成します。ここでは適当に有名なコーヒーチェーンの名前を5個くらい書いておきました。

db/seeds.rb
##省略
#ここから追記
Tag.create([
    { name: 'スターバックス' },
    { name: 'ドトール' },
    { name: 'エクセルシオール'},
    { name: 'タリーズ'},
    { name: 'サンマルク'},
    { name: 'コメダ'}
    ])
#ここまで

以下のコマンドでdbに変更を反映させます

rails db:seed

dbにdb/seedで入れた値は入ったでしょうか?rails consoleでTag.allと叩くことでdbに適切に値が入っているか確認できます。
##2.5.viewのformでtagを選択できるようにする
続いてtagを投稿ページで選択できるように、_form.html.erbにチェックボックスを追記して行きます。

posts/_form.html.erb
#省略
#追記ここから
  <div class="check_box">
    <span>タグ</span>
    <%= form.collection_check_boxes(:tag_ids, Tag.all, :id, :name) do |tag| %>
     <div>
       <%= tag.label do %>
         <%= tag.check_box %>
         <%= tag.text %>
       <% end %>
     </div>
   <% end %>
 </div>
#追記ここまで

<div class="actions">
    <%= form.submit %>
  </div>
<% end %>

collection_check_boxesについては詳細の説明は省きます。ググってください(いずれ追記します。。。)
##2.5.コントローラーでストロングパラメーターを追記
posts/controllerにformで追加したプロパティ名であるtag_idsを以下のように追記します。
tag_idsは複数のタグが渡されるので、配列の形式でpost_paramsに渡すよう記述をします。

posts/controller.rb
#省略
    def post_params
      params.require(:post).permit(:body, :title, tag_ids: [])
    end
end

##2.6.投稿にtagが表示されるようviewを追記
渡された複数のタグをeach文で表示させるためそれぞれshowとindexに以下を追記。

show.html.erb
<% @post.tags.each do |tag| %>
    <%= tag.name %>
<% end %>
index.html.erb
<% post.tags.each do |tag| %>
    <%= tag.name %>
<% end %>      

#3.タグ検索機能の実装
最後にここからタグ検索機能を実装して行きます
選択したtagの投稿のみ取得するようにindex.html.erbを以下のように編集して行きます。

index.html.erb
<p id="notice"><%= notice %></p>

<h1>Posts</h1>
#ここから追記
<%= form_tag posts_path, method: :get, class: 'boards__searchForm' do %>
      <%= select_tag :tag_id,
                     options_from_collection_for_select(Tag.all, :id, :name, params[:tag_id]),
                     {
                       prompt: 'タグで絞り込み検索',
                       onchange: 'submit(this.form);'
                     }
      %>
    <% end %>
#ここまで
#省略

続いてposts_controllerのindexアクションを以下のように編集します。

posts_controller.rb
class PostsController < ApplicationController
  before_action :set_post, only: [:show, :edit, :update, :destroy]

  # GET /posts
  # GET /posts.json
  def index
#ここから追記
    @posts = params[:tag_id].present? ? Tag.find(params[:tag_id]).posts : Post.all
#ここまで
  end

以下の記述により、tag_idがセットされていたらTagから関連づけられたpostsを呼び、tag_idの指定がなければ、全ての投稿を表示するよう記述されています。

Tag.find(params[:tag_id]).posts : Post.all

これで完成です。ここまでやると以下のような簡単なタグ生成アプリができるのではないでしょうか?
Qiita-tag-gif3.gif

#4.終わり
少々、説明が雑ですが(ごめんなさいリライトします)これでタグ検索機能の実装の完了とします。
#5.終わりに
GeekSalonという大学生限定のプログラミングコミュニティではメンターが精力的に記事をアウトプットしています。
こちらはwebメンターのまこっちゃん
https://qiita.com/makoto15

84
69
1

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
84
69

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?