プログラミング学習記録

主にRuby on Railsを使ったプログラミングを学んでいます。

JavaScriptでDOM操作

DOMとは

DOMとはDocument Object Modelの略。
HTMLの構造をツリー構造として扱い、プログラミング言語からHTMLの要素にアクセスし操作するための仕組み。
WebブラウザがWebサーバから送られたHTML文書を解析し、DOMというデータ構造に変換している。

DOMのツリー構造

https://i.gyazo.com/d71d54beeb17b2db2df9f877eb49c6f1.png

画像引用元  JavaScript HTML DOM

ツリーの各要素をノードという。
ノードには要素ノード(エレメントノード)、テキストノードがある。
要素ノードはHTMLの情報、テキストノードはテキスト部分の情報を持っている。

Rubyはサーバーサイドで動く言語なので、ブラウザ側で動くJavaScriptでDOMを操作する。

DOMを操作する

最近はReactやVue.jsなどのフレームワークがスタンダードになりつつあり、この方法で操作する機会は減りつつあるが、現在でも使用されている。

私はDOM操作をざっくりではあるけど学んだ結果、「なるほど、そんな風に動かしてるんだ〜」とそれまで曖昧だった、ブラウザ側でどのようにHTMLを操作するかの理解を深めることができた。

それでは例示しつつざっくり触れる。

<button>更新</button>

このままではDOM操作ができない。識別子がないと要素を識別できないからだ。

DOMを操作するにはidやclassが必要

<button id="button-update">更新</button>

ここからDOMを取得するには、下のJavaScriptを使う

document.getElementById("button-update")
// => "<button id='button-update'>更新</button>"

ブラウザ内の JavaScript エンジンには、基本的な文法解析機能のほかに、DOM 操作などブラウザ固有の処理を行うためのオブジェクトがあらかじめ用意されています。
その一つが、document オブジェクトです。これは、HTML 文書(つまり、画面に表示されている Web ページ)を表すオブジェクトで、DOM 操作のためのメソッドが含まれています。

引用元 JavaScript超基礎講座!DOM操作を学ぶ | Hypertext Candy

最初のdocumentがそのオブジェクト。 getElementById("id名")というメソッドで指定されたidを持つノードをDOMとして取得する。


ドキュメントの操作 - ウェブ開発を学ぶ | MDNによるとこのgetElementById("id名")というメソッドは古い手法らしく、querySelector()の方が推奨されているらしい。今回はあくまでも基本を抑えて理解するのが目的なのでgetElementById("id名")を使います。


要素の中のテキストを取得したい場合はinnerTextメソッドを使う

const buttonUpdate = document.getElementById("button-update")
buttonUpdate.innerText
// => "更新"

テキストを書き変えたい場合は

const buttonUpdate = document.getElementById("button-update")
buttonUpdate.innerText = "アップデート"

とすればいい。

イベントドリブン

何かをしたら何かが起こるようにすることを考える。
ここでは、インプットフォームに数字を入れてボタンをクリックすると、表示されている初期値0の数字が加算されていくものを作る。
HTML

<input type="number" name="amount" id="js-amount">
<button id="js-add-button">足す</button>
<p id="js-result">0</p>

考え方
まず、各要素のDOMを取得する必要がある。

var currentValue = 0  // ここは変数
const jsAddButton = document.getElementById('js-add-button')
const jsAmount = document.getElementById('js-amount')
const jsResult = document.getElementById('js-result')

次にクリックしたらどう処理していくかを書く。
DOMにイベントを仕掛けるにはaddEventListenerを使う。第一引数にイベントの種類を指定する。

jsAddButton.addEventListener('click', () => {
    const amount = jsAmount.value    // valueでinputに入力された値を取得
    currentValue += parseInt(amount)  // input内の値は実は文字列。parseIntで整数へ
    jsResult.innerText = currentValue  // currentValueを上書き
  })

これらを合わせて
JavaScript

document.addEventListener('DOMContentLoaded', () => {
  var currentValue = 0  // ここは変数
  const jsAddButton = document.getElementById('js-add-button')
  const jsAmount = document.getElementById('js-amount')
  const jsResult = document.getElementById('js-result')

 jsAddButton.addEventListener('click', () => {
    const amount = jsAmount.value    // valueでinputに入力された値を取得
    currentValue += parseInt(amount)  // input内の値は実は文字列。parseIntで整数へ
    jsResult.innerText = currentValue  // currentValueを上書き
  })
})

document.addEventListener('DOMContentLoaded', () => {})はブラウザがDOMの解析を完了させてからというような意味合い。
これがないとうまく動かないことがある。

参考

JavaScript超基礎講座!DOM操作を学ぶ | Hypertext Candy

ドキュメントの操作 - ウェブ開発を学ぶ | MDN

ドキュメントオブジェクトモデル (DOM) - Web API | MDN

JavaScript初心者でもすぐわかる!DOMとは何か?

パーシャルでコレクションをレンダリングする/ローカル変数を渡す

パーシャルとは

部分テンプレートまたはパーシャルは、出力を扱いやすく分割するための仕組みです。パーシャルを使用することで、ビュー内のコードをいくつものファイルに分割して書き出し、他のテンプレートでも使いまわすことができます。

引用 Railsガイドより

学習する中で、重要かつこんがらがりやすい使い方や省略した書き方について記事にまとめます。

コレクションをレンダリングする

投稿一覧画面などを作る場合、各投稿の情報を投稿表示用のパーシャルへ渡し、繰り返し表示させる方法を使うと良い。
その時、繰り返し表示させるからといって

<% @posts.each do |post| %>
    <%= render partial: 'post', locals: { post: post } %>
  <% end %>

のように書くと、@postsの数だけrenderメソッドが実行されパフォーマンスが悪化する原因となる。
こういう場合は、

<%= render partial: 'post', collection: @posts %>  

のようにcollectionオプションを使い、一度でレンダリングすると良い。 collectionオプションを使用するとcollectionオプションに指定した変数の要素の分だけ部分テンプレートが繰り返し表示される。

パーシャルはデータの繰り返し (コレクション) を出力する場合にもきわめて便利です。:collectionオプションを使用してパーシャルにコレクションを渡すと、コレクションのメンバごとにパーシャルがレンダリングされて挿入されます。

引用 Railsガイドより

省略記法

また、collectionオプションを利用した書き方には以下の省略記法があり、よく使う。

<%= render @posts %>

この省略法を使うには以下の条件が必要。

  • 呼び出すパーシャルがviewsフォルダ内のpostsフォルダにある
  • パーシャル名が_post.html.erbであること
  • パーシャル内で使う変数がpostであること

※各条件はpostの場合。使用したいパーシャル名・変数名によって読み変えるべし。

ローカル変数を渡す

パーシャル内ではインスタンス変数を使うのは望ましくない。
パーシャルをレンダリングする時にローカル変数を引数として渡すとよい。

なぜパーシャル内でインスタンス変数を使うべきでないかというと、パーシャルの再利用性が低くなるため。
例えばパーシャル内でインスタンス変数を使った場合、コントローラー側でインスタンス変数に変更を加えると、パーシャル側も変更しなければならなくなる(変更しないとバグの原因になり得る)。ビューと特定のコントローラーの依存が強まり、また特定のモデルのデータに関連づけられてしまうので、別の場所で再利用できなくなる。

依存性を低くし、再利用性を高めるためローカル変数を渡す。

<%= render pertial: 'article', locals: { article: @article } %> 

※localsオプションを書いている場合はpertialは省略できない

省略記法

<%= render 'article', article: @article %>  
<%= render @article %>  

注意

<%= render @posts %>  # コレクションのレンダリング(要素の繰り返し表示)

<%= render @post %>  # ローカル変数を渡したパーシャル

では意味と挙動が違うので注意。

参考

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

Action View の概要 - Railsガイド

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

【Rails基礎】ややこしい部分テンプレートの省略形について簡単にまとめてみた|TechTechMedia

Rails パーシャル(部分テンプレート)へローカル変数を渡したいとき - Qiita

Rails部分テンプレート(パーシャル)まとめ - Qiita

【41日目】パーシャルの中にインスタンス変数を使ってはいけない理由と正しい使い方 - Qiita

partialではインスタンス変数を参照しない方がいい - Qiita

Rails 5系でBootstrap 4系を導入する

Bootstrapとは

Bootstrapとは、WEBサイトやWEBページを効率よく開発するためのオープンソースCSSフレームワーク
HTML・CSSJavaScriptから構成されている。WEBページでよく使われるフォーム・ボタン・メニューなどの部品がテンプレートとして用意されている。
レスポンシブ対応がされているので、Bootstrapを導入するだけで様々な端末に対応したモバイルファーストなウェブサイトを構築することができる

注意点

Bootstrap 4系はJQueryに依存しており別途インストールする必要があるが、Bootstrap 5系からはjQueryとの依存関係がなくなりjQueryなしで使用できるようになっている。
Rails 6系からアセットパイプラインに変更が加えられ、デフォルトではSprocketsはJavaScriptを処理しなくなり、代わりにWebpackがデフォルトとして設定されている。

この記事では、Rails 5系でBootstrap 4系を導入する方法をまとめています。

導入方法の確認

Gemを使おう

Railsで外部のライブラリやフレームワークを導入する場合、ダウンロードしてそのまま使う方法とRails用に最適化されたGemを使う方法がある。
特段事情がない限りGemを使う方が良い。

RailsでGemを使う場合、利用したいGemライブラリをGemfileという定義ファイルに記載して、bundle installコマンドでライブラリのソースコードを取得する。この時bundlerというパッケージ管理ツールを使用している(bundle installはbundlerのコマンド)。

このbundlerが様々なライブラリのバージョンや依存関係を管理して、扱いやすくしてくれるというのが理由。
Gem同士の依存関係やバージョンによってはエラーがでて使えないことあるが、bundlerがこれを防いでくれる!(……こ、これが文明というヤツか!)

Bootstrap 4系はbootstrap-rubygem

※Bootstrap 3,Bootstrap 2を使用するならbootstrap-sassのREADMEの手順に従い導入してください。(なかなかややこしいけども!)

2021/10/08現在、Bootstrapの最新verは5.1.0。
bootstrap-rubygemのGitHubページのREADMEでは5系のインストール方法が記載されている。
それ以下のバージョンについて知りたい時はRubyGemsでGem名を検索すると良い。
最新verの詳細ページのバージョン履歴から、それぞれのバージョンの詳細(他のGemや環境との依存関係・ドキュメントへのリンクなど)を確認できる。

https://i.gyazo.com/c2691f355cac270f165551372586c205.png

また、bootstrap-rubygemのREADMEのgem v5.1.0(下の画像の青い部分)が、RubyGemsの最新verの詳細ページへのリンクになっいて、ここからも確認できる。

https://i.gyazo.com/fb9ca94a27da4ed20ad94267623f6c89.png

導入手順

1. bootstrap、jquery-railsをインストール

Gemfile

gem 'bootstrap', '~> 4.6.0'
gem 'jquery-rails'

と記述を加え、ターミナルで

$ bundle install

サーバー起動中ならここで再起動。

2. マニフェストファイルに記述する

application.scss

app/assets/stylesheets/application.cssをapp/assets/stylesheets/application.scssへリネームするか、前者を削除し後者を新規作成する。
app/assets/stylesheets/application.scss

@import "bootstrap";

と記載。BootstrapはCSSの機能を拡張する言語であるSassを使っているため、読み込めるようにする必要がある。
Sassを使う場合、マニフェストファイル(ざっくりいうと読み込むアセットを指定するもの。ここではapplication.scssのこと)にこのように記載する。
CSSでの*=require*=require_treeといった記述法は使用しないこと。(application.cssをリネームした場合はこの記述が残っていると思うので、全て消去する)

application.js

また、Bootstrap 4はJQuery(JavaScriptのライブラリの一種)にも依存しているので、こちらもマニフェストファイルに記載する必要がある。
app/assets/javascripts/application.jsを作り、以下を書く。

//= require jquery3
//= require popper
//= require bootstrap-sprockets

注意点としては、読み込み順。
通常マニフェストファイルは上から下へ順に読み込むので、bootstrap-sprocketsより上に jquery3を記載すること(エラーの原因になる)。

これで導入完了!

参考

GitHub - twbs/bootstrap-rubygem: Bootstrap 4 rubygem for Rails / Sprockets / Hanami / etc

bootstrap | RubyGems.org | your community gem host

File: README — Documentation for bootstrap (4.6.0)

Bootstrap · 世界で最も人気のあるフロントエンドフレームワーク

Railsアプリで Bootstrap 4 を利用する - Qiita

Bootstrapとは?最新版のBootstrap4の使い方についても徹底解説! | 侍エンジニアブログ

Bootstrap5がリリースされました! - LINE ENGINEERING

アセットパイプラインとマニフェストファイル

アセットパイプラインとは

ブラウザが認識できる言語は、HTML,CSS,JavaScriptの三種類。
しかし、Railsでアプリケーションをつくる場合、当然ながらERB(埋め込みRuby)を使用している。画像などのリソースもブラウザで表示させる場合がほとんどだ。
つまりそのままでは、ブラウザが認識・解釈し表示させることができない。
そこでRailsに限らず他の言語で作成されたWebアプリでも、ブラウザが認識できる言語へ変換する必要がある。
その仕組みこそがアセットパイプラインというフレームワーク

アセットパイプラインは画像、CSSJavaScriptといったアセットファイルを連結/圧縮することでRailsアプリを高速化します。また、より高級な言語で書かれたCSSJavaScriptコンパイルする機能も備えています。

引用元

【Rails】アセットパイプライン(Sprockets)の基本情報と実装方法 - AUTOVICE

Railsではsprockets-rails gem(SprocketsをRails用にカスタマイズしたもの)によって実装され、デフォルトで有効になっている。
Rails 6からはWebpackerが標準となっているが、Sprocketsも使われている。

アセットパイプラインの機能・処理

1 高級言語コンパイル
2 アセットの連結
3 アセットの最小化
4 ダイジェスト(フィンガープリント)の付与

という順で処理される

高級言語コンパイル

より高級な言語で記述されたコードはプリコンパイルされ、実際のアセットになる。(ブラウザが認識できるJavaScript,CSSファイルとして扱えるようにする。)
デフォルトでサポートされている言語は、CSSに代わるSass、JavaScriptに代わるCoffeeScriptCSS/JavaScriptに代わるERB。

アセットの連結

複数のJavaScript,CSSファイルをそれぞれ一つのファイルに連結することで、ブラウザがWebページをレンダリングするためのリクエスト数を減らすことができる。Webブラウザが同時に処理できるリクエスト数には限りがあるため、同時リクエスト数を減らすことができればその分読み込みが高速になる。
SprocketsはすべてのJavaScriptファイルを1つのマスター.jsファイルに連結し、すべてのCSSファイルを1つのマスター.cssファイルに連結する。

アセットの最小化

スペース、改行、コメントを削除してファイルを最小化(一種の圧縮)し、通信量を節約する。
Railsではsass-rails gemが自動的にGemfileに追加される。Sprocketsはアセット圧縮の際にこのgemを使用する。

ダイジェスト(フィンガープリント)の付与

コードの内容からハッシュ値を算出してファイル名の末尾に付与する。
このようにすると、コードが変更されればファイル名が変更されるので、ブラウザのキャッシュの影響で修正が反映されないという問題を防ぐことができる。


※アセットパイプラインはdevelopment環境とproduct環境で挙動が変わる。
development環境では、高級言語コンパイル、ダイジェストの付与は逐次自動で行われる。処理速度は良くないが、開発者が自らコンパイルする必要はない。
アセットの連結と最小化は行われない。連結が行われず、ファイル数分のlink,scriptタグが生成されるのでデバッグしやすい。
production環境では全ての処理が行われる。

アセットの読み込み

RailsではCSSを読み込むにはstylsheet_link_tag、JavaScriptを読み込むにはjavascript_include_tagというヘルパーメソッドを使う。
共通のビューである、app/views/layouts/application.html.erbの<head>~</head>内で読み込んでいる。
デフォルトでここから読み込んでいるのはapplication.css、application.js。

<%= stylesheet_link_tag "application", media: "all" %>
<%= javascript_include_tag "application" %>

このapplication.css、application.jsとは、アセットパイプラインによって連結された結果のファイルを指す。

マニフェストファイル

ファイルをどう連結して出力するかは、app/assets配下のapplication.cssやapplication.jsといったマニフェストファイルに記述する。
どのファイルを読み込むのかを取りまとめたファイルを、マニフェストファイルと呼ぶ。

マニフェストファイルには ディレクティブ (directive: 命令、指示) を含めます。ディレクティブを使用して必要なファイルを指定し、それに基いて最終的に単一のCSSJavaScriptファイルがビルドされます。Sprocketsはディレクティブで指定されたファイルを読み込み、必要に応じて処理を行い、連結して単一のファイルを生成し、圧縮します。

引用元

アセットパイプライン - Railsガイド

app/assets/javascripts/application.js

// ...
//= require rails-ujs
//= require turbolinks
//= require_tree .

デフォルトでこのような記述がある。
JavaScriptマニフェストファイルでは//=で始まる行は、Sprocketの独自仕様でアセットパイプラインに指示(ディレクティブ)を与えるためのもの。
require_tree .は「同階層に存在している全てのファイルを読み込む」という意味。

app/assets/stylesheets/application.css

/* ...
*= require_self
*= require_tree .
*/

require_selfは「自分自身を読み込む」という意味。

ここで注意が必要なのが読み込む順番
基本的に上から下へ順に読み込むのだが、require_treeディレクティブで読み込まれるファイルの読み込み順序は指定できない。そのため、特定の読み込み順に依存しないようにする必要がある。順序を間違えるとエラーの原因となることがある。通常はrequire_tree .は一番下に記述しておくのがいい。

CSSとSCSS(Sass)

まずは用語の確認から
CSS
HTMLで作られた文書構造にデザインを加えてWebページの見栄えを整える言語。「Cascading Style Sheets」 の頭文字をとったものであり、スタイルシートとも呼ばれる。


Sass
Sass(サス)はCSSを効率的に書くことができるメタ言語CSSに対して機能を拡張する。Syntactically Awesome StyleSheetsの略。
CSSではHTMLのクラスがネストになっている箇所でも、入れ子の構造で記述できないため、分けて書くしかないので直感的に関係の把握がしづらい。
Sassだとコードを入れ子で記述できるので書く量を減らしたり、クラス名がかぶってしまい、ほかにもスタイルが適用されてしまうということを防げる。また、Sassでは変数を定義して使うことができるので、文字色や文字サイズが変わっても、全部の箇所を変更する手間が省ける。 SCSS記法とSASS記法がある。
SASS記法はインデント制御で記述する(記述量を必要最小限にできる)。SCSS記法ではCSSと同じように{}を使い記述し、普通のCSSをそのまま記述しても動くのが利点。
SassはRailsでBootstrapを導入するときにも必要。


なぜここでSassについて言及するかというと、Sassはそのままではブラウザが解釈してくれない、つまりCSSコンパイルさせる必要があるからだ。
マニフェストファイルの書き方に注意が必要

Sassファイルを複数使用しているのであれば、Sprocketsディレクティブで読み込まずにSass @importルールを使用する必要があります。このような場合にSprocketsディレクティブを使用してしまうと、Sassファイルが自分自身のスコープに置かれるため、その中で定義されている変数やミックスインが他のSassから利用できなくなってしまいます。

引用元 

アセットパイプライン - Railsガイド

RailsでSCSSを導入する

Railsはデフォルトでsass-railsというgemが導入されているで、ファイル名を変更し、追加したCSSファイルが全て読み込まれるよう記述していく。
デフォルトではapp/assets/stylesheets/application.cssマニフェストファイルになっているので、これを削除して新しくapp/assets/stylesheets/application.scssを作る。
そこに読み込むファイルを@importを使って個別に読み込むファイルを記述する。 拡張子は省略。

@import "top";
@import "task";
@import "bootstrap-sprockets";
@import "bootstrap";

上記はあくまで例。
require_treeを使わないのは、ここでも読み込む順番の問題があるから。@importを使って必要なファイルのみ読み込む方がファイル管理を安全にできる。

参考

アセットパイプライン - Railsガイド

www.amazon.co.jp

アセットパイプラインとマニフェストファイルについて - プログラミングの備忘録

【Rails】アセットパイプライン(Sprockets)の基本情報と実装方法 - AUTOVICE

アセットパイプラインの概要を理解する - Wataruの技術備忘録

CSSとSCSSの違い - プログラミングの備忘録

SassとSASSとSCSSの違いについて | UX MILK

Sassとは? Sassの基本について | UX MILK

初学者(私!)にとっては「現場で使える Ruby on Rails 5速習実践ガイド」のChapter6のアセットパイプラインの説明がわかりやすかったです。
Railsガイド「ん?」→現場RailsのChapter6-8 アセットパイプライン参照→その後またRailsガイド でやっと腑に落ちました。

rails generateコマンド実行時に不要なファイルを生成しないよう設定する方法

rails gコマンドで生成されるもの

コントローラーやモデルを作成する場合、rails gコマンドを使うとコントローラーやモデルのファイルと共に、テストファイルなどの関連するファイルを自動で生成してくれる。
デフォルトで生成するものは以下の通り。

https://i.gyazo.com/146862d2fa1c2222e801c713253e6616.png

※ルーティングに関してはファイルを生成するのではなく、ルーティングを設定するという意味。

ただこのままだと開発を進める際に、自動で生成されるファイルに不要なものが含まれる場合がある。

不要なファイルを生成しないよう設定する方法

config/application.rbに設定を加えていく。
例えば、rails g controller コントローラー名(小文字複数形)を実行する際、コントローラーファイルとビューのフォルダのみ(アクション指定時はファイルも)生成し、ルーティングを含め後は必要に応じて自分で用意したいという場合。
この場合、assets,helper,testファイルが不要となり、ルーティングも自動で設定されないようにすればいい。(rails gコマンドを使う場合不要になりやすい設定は、上の表からもわかる通り大体この辺り。コントローラーやマイグレーションファイルが不要なら、別のコマンドを使えばいいのだから)
config/application.rb

...
module App名
  class Application < Rails::Application
    config.load_defaults 5.2

    # ここから
    config.generators do |g|
      g.skip_routes true  # routes.rbを変更しない
      g.assets false  # assets(CSS,JavaScript)ファイルを生成しない
      g.helper false  # helperファイルを生成しない
      g.test_framework false  # testファイルを生成しない
    end
    # ここまで

    config.generators.system_tests = nil
  end
end

ちなみに

制限を加えていない、デフォルトの状態でrails g controller コントローラー名 アクション名とアクションを指定して実行する場合、ビューではviews/コントローラー名/アクション名.html.erbが生成され、routes.rbにはget 'コントローラー名/アクション名'が追加される。(このようなルート定義でも動きはするが、RESTfulの観点からは望ましくない。そのため、routes.rbに変更を加えないよう設定を変え自身でルーティングを設定していく方が、柔軟に開発できるしRESTfulに沿った記述にしていける。)コントローラーファイルには空のアクションが追加される。
アクションを指定しない場合、ビューはコントローラー名のフォルダのみ作成され、routes.rbに変更は加えられない。コントローラーファイルも当然変更なし。
rails g scaffold モデル名を実行した場合、routes.rbにresourcesメソッドを使ってルーティングが一括で設定される。

参考サイト

Rails ジェネレータとテンプレート入門 - Railsガイド

rails generateで自動生成されるファイルの設定 - Qiita

Rails generate コマンドで生成するファイルを限定する方法 - Kazu Tech Blog

DIVE INTO CODE | rails g コマンドが行なっていること

実はこんなにある rails generate|TechRacho(テックラッチョ)〜エンジニアの「?」を「!」に〜|BPS株式会社

N + 1問題

N + 1問題とは

SQLが必要以上に実行されてしまい、パフォーマンスが落ちる問題。
どういう時に起こるかというと、外部キー制約でアソシエーションが定義された複数のテーブルの情報を、横断的に繰り返し処理を用いて取得しようとする場合。例えば、一対多の関係の場合(例ではUserモデルとPostモデル。1人のuserが複数のpostを持つ場合)、
app/models/user.rb

class User < ActiveRecord::Base
  has_many :posts  # 複数形
end

app/models/post.rb

class Post < ActiveRecord::Base
  belongs_to :user  # 単数形
end

Viewでuserのpostのcontent要素を表示したいとき
app/controller/users_controller.rb

class UsersController < ApplicationController
  def index
    @users = User.all
  end
end

app/views/posts/index.html.erb

<% @users.each do |user| %>
  <%= user.post.content %>
<% end %>

この時発行されるSQLは、まずControllerのindexアクションでUser.allの実行で一回。Userモデルの情報を全て取得。
その後、Viewのeach文を実行する際、まずuser_idが1のuserのpostの情報を得るためにPostのデータベースにアクセスするSQLが実行され、次の繰り返しでuser_idが2のuserのpostの情報を得るためのSQLが実行され……
と結局、全てのuserに関してpostを得る処理が繰り返しの都度実行される。
つまり、最初にuserの情報を得るために一回(1)
userそれぞれのpostの情報を得るためにuserの人数分の回数(N)
SQLが実行される。扱うデータの数量が大きくなるほど実行しなければならない回数が増えて処理が追いつかなくなってしまうという問題。

対応策

includesメソッドを使う

app/controller/users_controller.rb

class UsersController < ApplicationController
  def index
    @users = User.all.incledes(:posts)
  end
end

と変更する。
こうすることで、userの情報とpostの情報をまとめて取得できるので、SQLの実行回数を大幅に減らせる。

参考

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

【Rails】 N+1問題をincludesメソッドで解決しよう! | Pikawaka - ピカ1わかりやすいプログラミング用語サイト

Rails「N+1問題」超分かりやすい解説・解決方法【includesを使おう】 | にょけんのボックス

Draper

Draperとは

Railsで利用できる、Decoratorを導入するためのGem。
※Decoratorとはソフトウェアのデザインパターンの一つ。既存のオブジェクトに新しい機能や振る舞いを動的に追加するためのもの。

どんな場合に使用するか

Modelの情報をViewで表示しようとする場合、Viewを装飾するメソッドを追加したいがModelに書くとModelが肥大化してしまう。もちろんViewに書くと重複が多くなり、可読性が低下する。
helperに書いても動きはするが、scopeの問題があり、名前空間がグローバルなのでメソッド名が衝突する危険性がある。また、メソッドを想定していない使い方をしてバグの原因になりかねない。
よって、以下のような使い分けをすると良い。

  • helper   Modelから独立し関係していない表示ロジック
  • Decorator   一つのModelに関連した表示ロジック

導入

インストール

#Gemfile
gem 'draper'
$ bundle install

次にデコレーター層を追加するために以下を実行

$ rails generate draper:install

この後は

$ rails generate decorator ○○(モデル名)

で指定したモデルに対応したデコレーターファイルが作成される。

app/decorators/○○_decorator.rb

class ○○Decorator < Draper::Decorator
  delegate_all
  
  #ここにメソッドを書く
end

Modelがファットにならないようにするために使う。
Draperを使えばよりDRYに、よりオブジェクト志向的に書ける。

参考サイト

GitHub - drapergem/draper: Decorators/View-Models for Rails Applications

Decorator と Presenter を使い分けて、 Rails を ViewModel ですっきりさせよう - KitchHike Tech Blog

Decoratorの役割とDraperについて - Qiita

Rails Viewの表示のためにDecoratorを用意してHelperとModelを助ける - Qiita

Decorator パターン - Wikipedia

Draperの使い方 まとめ - 猫Rails