動的なタイトルの実装

例えば今回トップページのタイトルを「 RUNTEQ BORAD APP 」
ユーザーログインタイトルを「 ログイン | RUNTEQ BORAD APP 」
とする。このようにタイトルの一部分だけ変更して表示する機能は
application_helper.rbに
以下のモジュールメソッドを用いて作成する

#application_helper.rb
module ApplicationHelper
  def page_title(page_title = '')
    base_title = 'RUNTEQ BORAD APP'
    if page_title.empty?
      base_title
    else
      page_title + " | " + base_title
  end

上記のメソッドはbase_title変数にRUNTEQ BORAD APPを代入して、
if page_title.empty?によって引数(page_title)に渡された値が空かどうかを判定、
空であるならbase_titleを返し、空でないなら「page_title」と「 | 」と「base_title」を連結させたものを返すメソッド。
このメソッドをapplication_helper.rb記載するのはどこでも使えるようにするため
次にapplication.html.erbのheadタグ内に

<title><%= page_title(yield(:title)) %></title>

を記載、このyield(:title)にcontent_forメソッドをユーザーのログイン画面のビューの方に

<% content_for :title do %>
   ログイン
<% end %>

と記載することでcontent_forのブロック内の値を送ることができる
これによりタイトルを動的に出力できるようになる!!

参考文献

レイアウトとレンダリング - Railsガイド

【Rails】content_forを理解するyieldを学ぶ - Qiita

コメントのフォーム作成

前回のようにcommentリソースがboardsリソースにネストされている

ルーティングの場合、コメント作成はfomr_wtihにboardモデルとcommentモデルと複数モデルを渡すことでフォームデータの送信先を指定できる

今回は、掲示板詳細画面にコメントのフォームをパーシャルで埋め込むので、boardsコントローラのshowアクションに

def show
    @board = Board.find(params[:id])
    @comment = Comment.new
    @comments = @board.comments.includes(:user).order(created_at: :desc)
 end

と、URLのid情報を持ったBoardモデルのインスタンス、空のcommentモデルのインスタンス、ユーザー情報に関連づいたcommentモデルの集合を降順で取得  

コメントフォームのパーシャルはshow.html.erbに  

<% render ‘comments/form’ , { board: @board, comment: @comment } %>

を記述して呼び出し、この時パーシャル内でboard変数には@boardインスタンスcomment変数には@commentインスタンスを指定  

今回はどの掲示板(boardオブジェクト)に関連づいたコメント(commentオブジェクト)をデータベースに保存するため、form_withメソッドの引数にmodel: オプションにboardオブジェクトとcommenntオブジェクトを渡す必要があるので

<%= form_with model: [board, comment], local: true do |form| %>

と記述

modelにboardオブジェクトと、それに関連付いたcommentオブジェクトを渡すことで、rails側が自動的にcommentオブジェクトが空かどうかを判定して、空なら、boards/id/commentsのURLを指定、createアクションを呼び出し、中身がある場合、boards/id/comments/idのURLを指定、updateアクションを呼び出している。今回の場合showアクションでboardsインスタンスはurlのid情報が、commentインスタンスは空なので、送信先はboards/:id/comments、つまりcommentアクションのcreateアクションにフォームデータが送られる

 

参考文献

【Rails】form_withの使い方を徹底解説! | Pikawaka - ピカ1わかりやすいプログラミング用語サイト

  【Rails】form_with/form_forについて【入門】 - Qiita

ネストしているモデルに対するform_withの書き方 - Qiita

コメント機能の実装

掲示板にユーザーがコメントできるようにする

Commentモデルの作成及びアソシエーションの確認

コメントは掲示板に属して、ユーザーに属するので、

bundle exec rails g model Comment body:text user:references board:references

を実行、Userモデルは複数のCommentモデルを持ち、boardモデルも複数のCommentモデルを持ち、Commentモデルのbodyカラムは最長65535文字まで、また、ユーザーや掲示板が削除された時、関連付けたコメントを削除できるように

#user.rb
has_many :comments, dependent: :destroy
#board.rb
has_many :comments, dependent: :destroy
#comment.rb
validates :body, presence: true, length: { maximum: 655_35 }

をそれぞれ追記 また、ルーティングについて今回1つの掲示板に対して、複数のコメントがあり、その1つ1つに特定のURLを振り分けるとする。このようにリソースの配下に他のリソースを配置するにはルーティングをネストすることで、この親子関係をルーティングで表現できる。今回の場合は以下のように宣言する

#routes.rb
resources :boards do
  resources :comments
end

bundle exec rails routesで確認すると

board_comments GET    /boards/:board_id/comments(.:format)                                                     comments#index
                          POST   /boards/:board_id/comments(.:format)                                                     comments#create
        new_board_comment GET    /boards/:board_id/comments/new(.:format)                                                 comments#new
       edit_board_comment GET    /boards/:board_id/comments/:id/edit(.:format)                                            comments#edit
            board_comment GET    /boards/:board_id/comments/:id(.:format)                                                 comments#show
                          PATCH  /boards/:board_id/comments/:id(.:format)                                                 comments#update
                          PUT    /boards/:board_id/comments/:id(.:format)                                                 comments#update
                          DELETE /boards/:board_id/comments/:id(.:format)                                                 comments#destroy

のようにboardsリソースにcommentリソースがネストされたルーティング生成できる また、commentコントローラのshow,edit,update,destroyアクションについてはboardsリソースは必要ない、こんな時に使われるのがshallowオプション、本来なら

resources :boards do
  resources :comments, only: %i[index new create]
end
resources :comments, only: %[show edit update destroy]

と記述することでこの4つのアクションだけはcommemnts/:idのようにboardsリソースをネストしないようにできるが、shallowオプションでこれを

resources :boards do
    resources :comments, shallow: true
 end

と簡潔に記述できる bundle exec rails routesで確認すると

 board_comments GET    /boards/:board_id/comments(.:format)                                                     comments#index
                          POST   /boards/:board_id/comments(.:format)                                                     comments#create
        new_board_comment GET    /boards/:board_id/comments/new(.:format)                                                 comments#new
             edit_comment GET    /comments/:id/edit(.:format)                                                             comments#edit
                  comment GET    /comments/:id(.:format)                                                                  comments#show
                          PATCH  /comments/:id(.:format)                                                                  comments#update
                          PUT    /comments/:id(.:format)                                                                  comments#update
                          DELETE /comments/:id(.:format)                                                                  comments#destroy

とindex,create,newアクションだけboardsリソースにネストされたルーティングが生成できる!!

参考文献

Active Record の関連付け - Railsガイド

Rails のルーティング - Railsガイド

carrierwaveを使った画像アップロード

CarrierWaveとは

railsアプリに簡単に画像をアップロードできるライブラリ

使い方

Gemfileに以下を記述して、bundle install

gem 'carrierwave', '~> 2.0'

その後、以下を実行することで、アップローダーファイルが生成される
今回はファイル名をBoard_imageとすると

bundle exec rails g uploader Board_image

を実行することで、app/uploaders/board_image_uploader.rbが生成される

class BoardImageUploader < CarrierWave::Uploader::Base
  storage :file
  
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  def default_url
    'board_placeholder.png'
  end
  
  def extension_whitelist
    %w[jpg jpeg gif png]
  end
end

ここにアップロードするファイルのカスタム設定を記述できる
store_dirではアップロードされたファイルの保存先を指定している
default_urlでは、何もファイルが指定されなかった時のデフォルトファイルを指定している
extension_whitelistではアップロードに選択できるファイルのフォーマットを指定できる
次に、今回は掲示板(Board)に画像用のカラム(board_image)を追加するので、

bundle exec rails g migration add_board_image_to_boards board_image:string
bundle exec rails db:migrate

を実行して、bundle exec rails db:migrate
次に画像用のカラムとuploaderファイルを関連づけるためにboard.rbに

mount_uploader :board_image, BoardImageUploader

を記述
掲示板フォームに画像アップロードフォームを追加

<%= form_with(model: board,local: true,class: 'form-group') do |form| %>
  <%= render 'shared/error_messages', object: form.object %>
  <div class="form-group">
    <%= form.label :title %>
    <%= form.text_field :title,class: 'form-control' %>
  </div>
  <div class="form-group">
    <%= form.label :body %>
    <%= form.text_area :body,class: 'form-control',rows: 10 %>
  </div>
  <div class="form-gruop">
    <%= form.label :board_image %>
    <%= form.file_field :board_image,class: 'form-control'%>
    <%= form.hidden_field :board_image_cache %>
  </div>
  <% form.submit "投稿する" %>
<% end %>

<%= form.hidden_field :board_image_cache %>は
例えば記載しないと掲示板の投稿に失敗した時、アップロードに選択したファイルが消えてしまうので、アップロードに失敗した際もファイルが消えないようにするため
boardsコントローラでストロングパラメータにboard_image、board_image_cacheを追記

def board_params
  params.require(:board).permit(:title, :body, :board_image, :board_image_cache)
end

これにより画像アップロードができるようになる!!

参考文献

GitHub - carrierwaveuploader/carrierwave: Classier solution for file uploads for Rails, Sinatra and other Ruby web frameworks

[Rails6.0.2]CarrierWaveを基本に忠実に使ってみる | ゆるりエンジニア

Rails CarrierWaveを使った画像投稿機能の実装メモ|ひびきき|note

rails db:migrate:resetとrails db:resetの違い

rails db:migrate:reset

databaseを削除してもう一度作成して、db:migrateを実行している

rails db:reset

databeseを削除して、現在のschema.erbをロードして、dbを作成している

つまりdatabaseをdb/migrate/以下のファイルを全て実行しているか、db/schema.rbをロードしているかの違い

参考文献

Active Record マイグレーション - Railsガイド

rake db:reset と rake db:migrate:reset の違い | EasyRamble

railsのTimezone設定

タイムゾーンとは

共通の時間を使うエリアのこと、日本だと使われる時間はJST、世界の基準時刻のUTC(協定世界時)と比較して、+9時間の時差があるため、UTC+09:00やUTC+9などで表記される

設定方法

railsタイムゾーンはconfig/application.rbで設定できる  

DBの保存時間の変更をする

config.active_record.default_timezone = :local

デフォルトは:utcとなっている

表示のみをUSTからJSTにする

config.time_zone = 'Tokyo'

デフォルトはUTCとなっている
確認はコンソール上でTime.zoneやTime.currentなどで確認できる  

irb(main):001:0> Time.now

=> 2020-11-17 22:55:28 +0900

irb(main):002:0> Time.current

=> Tue, 17 Nov 2020 22:55:37 JST +09:00

irb(main):003:0> Time.zone

=> #<ActiveSupport::TimeZone:0x00007ff8e94bde00 @name="Tokyo", @utc_offset=nil, @tzinfo=#<TZInfo::DataTimezone: Asia/Tokyo>

時刻のI18n

rails-i18n gemがja.yml内に用意してくれているので参照
コードはこんな感じ

# config/locales/ja.yml
ja:
  time:
    am: 午前
    formats:
      default: "%Y年%m月%d日(%a) %H時%M分%S秒 %z"
      long: "%Y/%m/%d %H:%M"
      short: "%m/%d %H:%M"

このロケールファイルを使って時刻を例えば以下のように国際化できる

<%= l board.created_at, format: :long %>

参考文献

Rails 国際化 (i18n) API - Railsガイド

RailsのTimezone設定をUTCからJSTに変えたい時 - Qiita

rails-i18n/ja.yml at master · svenfuchs/rails-i18n · GitHub

N+1問題の対策

N+1問題とは?

データベースからデータを取り出す際に、大量のSQLが発行されて、動作が重くなる現象

具体的な内容

ユーザー(users)が掲示板(boards)を投稿できるサービスがあって、掲示板一覧を表示したい場合、コントローラではallメソッドを使い、boardsテーブルからデータを取得する。 この時、1回SQLが発行される。
次にビューで掲示板の一覧を表示するとき、ユーザーと掲示板がアソシエーションを組んでいて、掲示板を投稿した人のユーザー名も表示するようにする。 掲示板の情報はallメソッドで取得してきたが、投稿したユーザー名はusersテーブルから取得する必要がある。よって、eachメソッドとかで1つの掲示板のユーザー名を表示させるたびにusersテーブルからレコードから取得するSQLが自動で実行される。これがN個あればN回SQLが実行されるので、全てのboardsテーブルのレコードを取得する+N回SQLが実行されるのでN+1問題と呼ばれる
つまりこのN回が多くなればなるほどSQLを発行する回数が増えるので重くなる

対策

includesメソッドを使う

@boards = Board.all.includes(:user)

のようにモデル名.includes(:関連名)の記述することで、今回ならboardsテーブルからデータを取得するときに、関連するusersテーブルのデータも取得してくれる。これによって都度SQLを発行されることがなくなる!!

参考文献

Active Record クエリインターフェイス - Railsガイド

【Rails】N+1問題って何?原因と対処法を徹底解説! | Pikawaka - ピカ1わかりやすいプログラミング用語サイト