Rails 6 でTodoApp作る part4

機能武装していきます。

まず初めに

Seedsでサンプルデータ投入

fakerを利用してサンプルデータを大量に投入する。

Gemfileにgem 'faker'を記入してbundle

[ Gemfile ]

gem 'faker'
% bundle

seedsファイルを編集します。

[ db/seeds.rb ]

# ユーザー
User.create!(
  [
    {
      name: 'panda',
      email: 'panda@example',
      password: 'password',
      password_confirmation: 'password',
    },
    {
      name: 'rakuda',
      email: 'rakuda@example.com',
      password: 'password',
      password_confirmation: 'password'
    },
    {
      name: 'tamanegi',
      email: 'tamanegi@example.com',
      password: 'password',
      password_confirmation: 'password'
    },
  ]
)

  # タスク
  users = User.all
  30.times do
  title = Faker::JapaneseMedia::SwordArtOnline.game_name
  description = Faker::JapaneseMedia::SwordArtOnline.location
  users.each { |user| user.tasks.create!(title: title, description: description) }
end

ユーザーを3人作成して、各ユーザーに30タスクを作ります。

% rails db:reset db:seed
Dropped database 'db/development.sqlite3'
Dropped database 'db/test.sqlite3'
Created database 'db/development.sqlite3'
Created database 'db/test.sqlite3'

fakerについては ↓
GitHub - faker-ruby/faker: A library for generating fake data such as names, addresses, and phone numbers.

いろいろなサンプルデータがあって面白い


Ransackで検索機能

Tasksの一覧画面に検索機能をつけていくっ

GitHub - activerecord-hackery/ransack: Object-based searching.
に書いてある通り進めていきます。

Gemfileにransackを記入しbundle

[ Gemfile ]

gem 'ransack'
bundle

ここで、サーバーを起動中であれば再起動する。


続いて、コントローラーのindexアクションを編集します

[ tasks_controller.rb ]

  def index
    @q = current_user.tasks.ransack(params[:q])
    @tasks = @q.result(distinct: true).recent
  end

ビューに検索フォームを追加します

f:id:yukitoku_sw:20200119213925p:plain

[ tasks/index.html.slim ]

h1 タスク一覧

.nav
  = link_to '新規登録', new_task_path, class: 'nav-link'

.row.justify-content-end
  = search_form_for @q, class: 'mb-5 ' do |f|
    .form-inline
      = f.label :title_cont, 'タイトル', class: 'my-1 mr-2'
      = f.search_field :title_cont, class: 'my-1 mr-sm-2'
      = f.submit '検索', class: 'btn btn-primary my-1'
table.table
  thead.thead-default
    tr
      th タイトル
  (省略)

f:id:yukitoku_sw:20200119214116p:plain

検索機能がちゃんと動いていることもKiritoで確認!

[ コンソール ]

  Parameters: {"q"=>{"title_cont"=>"kirito"}, "commit"=>"検索"}
  User Load (0.3ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  ↳ app/controllers/application_controller.rb:8:in `current_user'
  Rendering tasks/index.html.slim within layouts/application
  Task Load (0.3ms)  SELECT DISTINCT "tasks".* FROM "tasks" WHERE "tasks"."user_id" = ? AND "tasks"."title" LIKE '%kirito%' ORDER BY "tasks"."created_at" DESC  [["user_id", 1]]

logを確認すると"title" LIKE '%kirito%と書いてあります
検索した文字列を含むものは全てokみたいなので試しにkiだけで検索してみます。

f:id:yukitoku_sw:20200119214751p:plain

もっと表示されるかと思いましたが・・・
タイトルにkiを含む全てのtaskが表示されました!


ActiveStorageで画像添付機能

ActiveStorageとは、ファイル管理gemのことです。 Rails5.2から標準搭載となったのでGemfileに書いてbundleする作業は不要です
environment環境で利用する場合は保存先の設定をする必要があります。今回はdevelopment環境で使う設定でやります。environment環境の設定は後日やります・・・

詳しくは、Active Storage の概要 - Railsガイド

gem自体はインストール不要ですが、下記コマンドでActiveStoragを使えるようにする必要があります。

 % rails active_storage:install
Copied migration 20200119130415_create_active_storage_tables.active_storage.rb from active_storage

すると、下記マイグレーションファイルが作成されます。

[ db/migration/??????????create_active_storage_tables.active_storage.rb ]_

# This migration comes from active_storage (originally 20170806125915)
class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
  def change
    create_table :active_storage_blobs do |t|
      t.string   :key,        null: false
      t.string   :filename,   null: false
      t.string   :content_type
      t.text     :metadata
      t.bigint   :byte_size,  null: false
      t.string   :checksum,   null: false
      t.datetime :created_at, null: false

      t.index [ :key ], unique: true
    end

    create_table :active_storage_attachments do |t|
      t.string     :name,     null: false
      t.references :record,   null: false, polymorphic: true, index: false
      t.references :blob,     null: false

      t.datetime :created_at, null: false

      t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
      t.foreign_key :active_storage_blobs, column: :blob_id
    end
  end
end

2種類のtableが作成されてそこに Active Storage の概要 - Railsガイド


準備が整いましたので実装していきます

[ models/task.rb ]

class Task < ApplicationRecord
  validates :title, presence: true, length: { maximum: 30 }
  validates :description, presence: true

  has_one_attached :image   # 追加
  belongs_to :user

  scope :recent, -> { order(created_at: :desc) }
end

has_one_attachedメソッドを使い、1つのTaskにつき1つの画像を添付できるようになります。その画像はimageとして呼ぶことができます。


画像を添付するためのformを追加します。

[ views/tasks/_form.html.slim ]

(省略)
= form_with model: task, local: true do |f|
  .form-group
    = f.label :title, 'タイトル'
    = f.text_field :title, class: 'form-control', id: 'task_title'
  .form-group
    = f.label :description, '詳細'
    = f.text_area :description, class: 'form-control', id: 'task_description'
  .form-group    # 追加
    = f.label :image, '画像'   # 追加
    = f.file_field :image, class: 'form-control', id: 'task_image'   # 追加
  = f.submit '登録', class: 'btn btn-success'

f:id:yukitoku_sw:20200119224109p:plain

こんな感じ


StrongParametersにimageを追加します

[ tasks_controller.rb ]

(省略)
 private

    def task_params
      params.require(:task).permit(:title, :description, :image)   <- :image追加
    end
(省略)

viewにimageできるようにする

[ views/tasks/show.html.slim ]

(省略)

      th 詳細
      td = @task.description
    tr
      th 画像
      td = image_tag @task.image if @task.image.attached?
    tr
      th 登録日
      td = @task.created_at
(省略)

全てのTaskに画像添付したいわけではないので、if @task.image.attached?をつけます。
これがないと画像がなかったときにエラーとなってしまいます。

機能が正常に動くか確認します。

f:id:yukitoku_sw:20200119225121p:plain

無事、画像の添付に成功しました!!


Kaminariでページネーション

現在一覧画面に30件のタスクが表示されています。
これから増えていけば増えていくほどページの読み込みが遅くなります。

こりゃいかん


[ Gemfile ]

gem 'kaminari'
% bundle

GitHub - kaminari/kaminari: ⚡ A Scope & Engine based, clean, powerful, customizable and sophisticated paginator for Ruby webapps Kaminariの詳細


さっそくコントローラーの設定から行きますっ

[ tasks_controller.rb ]

(省略)

  def index
    @q = current_user.tasks.ransack(params[:q])
    @tasks = @q.result(distinct: true).recent.page(params[:page])
  end

(省略)


続いてview

[ views/tasks/index.html.slim ]

(省略)
.row.justify-content-end
  = search_form_for @q, class: 'mb-5 ' do |f|
    .form-inline
      = f.label :title_cont, 'タイトル', class: 'my-1 mr-2'
      = f.search_field :title_cont, class: 'my-1 mr-sm-2'
      = f.submit '検索', class: 'btn btn-primary my-1'
.mb-3
  = paginate @tasks
  = page_entries_info @tasks
table.table
  thead.thead-default
    tr
      th タイトル
      th 登録日
 
(省略)

こんな感じ

f:id:yukitoku_sw:20200119231820p:plain

gemをインストールしたらサーバーを再起動するのは基本中の基本です。

[ コンソール ]

サーバー再起動

そして

f:id:yukitoku_sw:20200119232022p:plain

うまく表示されました!


しか〜し!

  • なぜ英語なのか

  • デフォルトの表示件数が25件/ページなのでもっと減らしたい


表記を日本語にする

config/localeskaminari.ja.ymlと言うファイルを作成します。

そして、ちょっと面倒ですが下記内容を記述します

[ config/locales/kaminari.ja.yml ]

ja:
  views:
    pagination:
      first: "&laquo; 最初"
      last: "最後 &raquo;"
      previous: "&lsaquo; 前"
      next: "次 &rsaquo;"
      truncate: "&hellip;"
  helpers:
    page_entries_info:
      one_page:
        display_entries:
          zero: "%{entry_name}がありません"
          one: "1件の%{entry_name}が表示されています"
          other: "%{count} 件の%{entry_name}が表示されています"
      more_pages:
          display_entries: "全%{total} 件中 %{first}&nbsp;-&nbsp;%{last} 件の%{entry_name}が表示されています"

次に、

config/initializerslocale.rbと言うファイルを作成し、下記を記述します

[ config/initializers/locale.rb ]

Rails.application.config.i18n.default_locale = :ja

そして、

[ コンソール ]

サーバー再起動

すると・・・

f:id:yukitoku_sw:20200119233440p:plain

tasks以外は日本語になりました!(tasksはまた別の場所で設定する必要があるため今回はパス)

しかしデザインが今市なので、Bootstrapを使います。(bootstrapは導入済みです)

[ コンソール ]

% rails g kaminari:views bootstrap4
Running via Spring preloader in process 2902
      downloading app/views/kaminari/_first_page.html.slim from kaminari_themes...
      create  app/views/kaminari/_first_page.html.slim
      downloading app/views/kaminari/_gap.html.slim from kaminari_themes...
      create  app/views/kaminari/_gap.html.slim
      downloading app/views/kaminari/_last_page.html.slim from kamina
(省略)

こんだけです

f:id:yukitoku_sw:20200119234041p:plain

いい感じになりました^^


残るは表示件数の調整

表示件数の変更方法は3通り?あるんですが、一番簡単なやつ

[ tasks_controller.rb ]

(省略)

  def index
    @q = current_user.tasks.ransack(params[:q])
    @tasks = @q.result(distinct: true).recent.page(params[:page]).per(6)
  end

(省略)

per(6)をつけるだけです *()内の数字を自由に入れてください
スクロールなしで表示できるのは6件でした!

f:id:yukitoku_sw:20200119235041p:plain


今回は以上です!


次回、後でやるをやるお