動的なタイトルの実装
例えば今回トップページのタイトルを「 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のブロック内の値を送ることができる
これによりタイトルを動的に出力できるようになる!!
参考文献
コメントのフォーム作成
前回のように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わかりやすいプログラミング用語サイト
コメント機能の実装
掲示板にユーザーがコメントできるようにする
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リソースにネストされたルーティングが生成できる!!
参考文献
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
これにより画像アップロードができるようになる!!
参考文献
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をロードしているかの違い
参考文献
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ガイド
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わかりやすいプログラミング用語サイト