Ruby on Railsの備忘録

エンジニアになるために覚えたことを記録していきます。

ポモドーロ・テクニックのすすめ

こんなことでお悩みありませんか?

  • 家でプログラミング学習をしているけれど、モチベーションが続かない。

  • すぐに集中力が切れてTwitterYoutubeを見てしまう。

  • とにかくやる気を起こしたい。

そんな貴方にポモドーロ・テクニックをとてもお勧めします!!!

私も飽きてきたりやる気が出なくなる時がありましたが、ポモドーロを導入してから進捗がかなり捗るようになりました!

そもそもポモドーロ・テクニックって何?

25分間一つのことに集中して取り組む

5分休憩を挟む

もう一度25分間一つのことに取り組む

これをポモドーロ・テクニックと言います。 4回繰り返したら15分ほどの長い休憩を取り、また再開します。 これだけのことなのですが私はかなり集中力が向上して持続力も続く様になりました。

メリット

  • 25分間集中+5分間休憩というサイクルをとることで脳が疲れにくくなり集中力が持続しやすくなる。

  • 時間配分がかなり計画的になるのでダラダラ勉強するということが少なくなる。

  • ポモドーロ回数を記録しておくと1日の勉強時間がパッとわかり達成感につながる。

  • 集中力の調子が悪くても25分間は意外と短いので続けやすい。

デメリット

  • テストなど試験時間が決まっているものには向かない。

  • たまに25分以上やりたい時もある時はヤキモキするかもしれません。(私はそういう時は休憩挟まずにもう一度25分間続けています。)

実はこれからポートフォリオでポモドーロアプリを作成しようと考えています! ポモドーロアプリにTodoリストとポモドーロの合計数を1日ごとに記録してグラフ化したり日数ごとの勉強時間をグラフで表示できる様にしたり一つのタスクにどれくらいのポモドーロをかけたかを記録できる様にする予定です。 仕事でも使える様に万人向けのデザインにしていこうと考えています。

おすすめポモドーロアプリ

245cloud.com

24分間曲を流しながら集中したあと、5分間ポモドーロを終えた人だけに交換日記が見れます。書き込みもできます。 曲を流さない無音モードもあります。

www.g-g-g-g.games

簡単なやることリストを書き込み、それぞれのタスクに合わせてポモドーロが実行できます。タスクがクリアできたらチェックを入れることで達成感も。 1日の累計ポモドーロ回数もグラフ化して見ることもできます。

PostgreSQLでdb:createをしようとしたらエラーにぶつかった

現場RailsPostgreSQLでdetabaseを作成しようとしたら詰まったのでメモ

環境

OS Mac Catalina10.15.3

Rails 6.0.3.4

ruby 2.6.5

エラー内容

PostgreSQLはすでにインストールしていました。 bundleなどいろいろアップデートした後にbin/rails db:createをしようとしたら以下のエラーに遭遇。

could not connect to server: Connection refused
    Is the server running locally and accepting
    connections on Unix domain socket "/tmp/.s.PGSQL.5432"?
Couldn't create 'scaffold_app_development' database. Please check your configuration.
rails aborted!
PG::ConnectionBad: could not connect to server: Connection refused
    Is the server running locally and accepting
    connections on Unix domain socket "/tmp/.s.PGSQL.5432"?

これだけだとエラーの原因がわからなかったのでいろいろ調べていたところ、

postgres -D /usr/local/var/postgres

このコマンドを実行するとエラー内容が返ってきました。

2020-11-25 15:04:32.093 JST [58334] FATAL:  database files are incompatible with server
2020-11-25 15:04:32.093 JST [58334] DETAIL:  The data directory was initialized by PostgreSQL version 12, which is not compatible with this version 13.1.

PostgreSQLのversionが合っていないからエラーが出ているらしい。

brew postgresql-upgrade-database

こちらのコマンドを実行してdatabaseをupgradeしたら成功

==> Upgraded postgresql data from 12 to 13!
==> Your postgresql 12 data remains at /usr/local/var/postgres.old

無事にデータベースの作成に成功しました。

 scaffold_app git:(master) ✗ bin/rails db:create
Database 'scaffold_app_development' already exists
Database 'scaffold_app_test' already exists

最初この様なエラーでupgradeが失敗したが2回同じコマンドでupgradeを実行したら成功しました。こちらのエラーは原因がよくわかりません。

Error: Upgrading postgresql data from 12 to 13 failed!
==> Removing empty postgresql initdb database...
==> Moving postgresql data back from /usr/local/var/postgres.old to /usr/local/var/postgres...
Error: Failure while executing; `/usr/local/opt/postgresql/bin/pg_upgrade -r -b /usr/local/Cellar/postgresql@12/12.5/bin -B /usr/local/opt/postgresql/bin -d /usr/local/var/postgres.old -D /usr/local/var/postgres -j 8` exited with 1.

Web技術の基本 第2章まとめ

Webを実現するコンピューターネットワーク Webを支えているのがコンピューターネットワークと呼ばれるコンピューターがお互いに接続して情報のやり取りをする仕組みです。

クライアントとサーバー ネットワーク上で情報やサービスを提供する役割を持つコンピューターを「サーバー」、サーバーから提供された情報やサービスを利用する役割を持つコンピューターを「クライアント」と言います。Webサイトを提供しているのがサーバーでWebサイトを表示するスマートフォンやパソコンに搭載されているWebブラウザがクライアントに当たる。サーバーの役割においてWebサイトを提供するサーバーをWebサーバーと呼ぶ。

インターネット スマホやパソコンを利用してWebサイトを閲覧する場合、インターネットサービスプロパイダーが提供するサービスを利用し、インターネットへ接続する必要がある。インターネットとは、自宅や会社、学校など小さな範囲のネットワークが1つ1つお互いに接続し世界中のネットワークがつながった環境のこと。Webサーバーもインターネットに接続されることでWebサイトを世界中へ提供することができる。

インターネットとサービスプロバイダー インターネットサービスプロバイダーはプロバイダーやISPと略されることが多く各国に複数のプロパイダーが存在する。スマホやパソコンはプロバイダーと接続し、プロバイダーはプロバイダー同士で接続し合うことで世界中が1つのネットワークとして形成されインターネットとして成り立っている。

インターネット接続を提供するプロバイダーは階層構造でつながっている。海外のプロバイダーと直接接続したりIX(インターネット・エクスチェンジ)を利用してプロバイダー同士が接続している大規模なプロバイダーを第一プロバイダーと言います。中小規模な第2プロバイダー、3次プロバイダーといった」プロバイダーは上位のプロバイダーを経由することでインターネットサービスをユーザーに提供している。

インターネットの標準プロトコル プロトコルとはネットワークに接続された機器同士が通信する時のあらかじめ決められた共通のルールや手順のことを言います。お互いに同じプロトコルを利用することによりデータのやり取りが可能となります。(HTTPなど)

TCP/IP TCP/IP(Transmission Control Protocol/ Internet Protocol)とはインターネットにおける様々なサービスを実現するためのプロトコルの集まりのことを言います。インターネットのアクセスが可能なスマホやコンピューターは全てTCP/IPに対応しています。一昔前まではコンピューターに搭載されているOSや機種ごとに独自のプロトコルが利用されていたため同じ機種同士でないとお互いに接続できなかった。インターネットの普及に伴ってお互いに接続するためのプロトコルとしてTCP/IPがインターネットにおける標準として広く利用される様になった。

HTTPもTCP/IPの一種 TCP/IPは役割ごとに階層化されており、HTTPもTCP/IPにおけるアプリケーション同士のやり取りを行う層のプロトコルのなかに含まれている。HTTPにはWebサーバーがどこにあるのか、Webコンテンツをどの様に転送するのかといった取り決めがない。インターネットにおいてHTTPdakedeha足りない部分をTCP/IPの他のプロトコルが補うことでお互いに接続することを可能にしている。

TCP/IP TCP/IPは役割ごとに4つの階層(レイヤー)に分かれています。

  • アプリケーション層(レイヤー4)
  • トランスポート層(レイヤー3)
  • インターネット層(レイヤー2)
  • ネットワークインターフェース層(レイヤー1) レイヤーごとの役割に応じたプロトコルが各レイヤーで利用されており4つの階層のプロトコルが連携することによりインターネットでの通信が可能となる。

アプリケーション層の機能 アプリケーション層ではWebブラウザやメールソフトなどのアプリケーションごとのやり取りを規定しています。これらのアプリケーションの多くはクライアント/サーバーシステムで構成されており、クライアントとサーバー間のサービス要求と応答で成り立っています。またアプリケーション層では扱うデータをネットワークで転送するのに適したデータ形式に変換し、逆にネットワーク経由で受け取ったデータを私たちが理解できる様に変換する役割も持っています。データの転送処理などはアプリケーション層より下位の層が担当しています。

TCPUDP アプリケーション層のやりとりに応じて実際にデータの転送処理をしているのがトランスポート層に位置するTCPUDP(User Datagram Protocol)といったプロトコルです。データの転送は分割して行われ、TCPでは分割されたデータの順序や欠落をチェックしているのに対してUDPは分割されたデータの順序や欠落を保証していません。TCPはWebサイトやメールなどデータ損失が起きると困る様なアプリケーションで利用されており、UDPは信頼性が低いものの通信の手続きが簡略化されているので効率よく通信できるので動画ストリーミングなどで利用されています。

IPアドレスとポート番号 IPアドレス インターネットに接続されたコンピューターを特定しデータの行き先を管理するために利用されているのがIPアドレスと呼ばれる識別番号。インターネットにおいてIPアドレスは世界中でたった一つでありいわばIPアドレスは世界中で利用できる住所の様なもの。コンピューターに割り当てられた住所を指定することでインターネット上の特定のコンピューターへ接続できます。 ポート番号 Webサービスやメールサービスといった様にコンピューターは様々なサービスを提供しています。IPアドレスは接続したいコンピューターを指定できますが、コンピューターが提供するサービスまでは指定できない。そのため、コンピューターが提供するサービスを提供するためにポート番号と呼ばれるものを利用します。ポート番号とはコンピューターの内部にあるサービスを識別するための番号でマンションやアパートで言う部屋番号に当たる。IPアドレスとポート番号を指定することで特定のコンピューターの特定のサービスを受け取ることができる様になります。 Webサーバーは80番 ポート番号は「0~65535」までの数字で範囲によって用途が決められている。サービス(アプリケーション)によっては使用するポート番号を独自に決めることができるが、一般的にWebサーバー(HTTP)であれば80番といった様にポート番号が決められており、ポート番号によってサービスを識別できる。

URLとドメイン URL URLはWebサイトの場所を示す際に用いられることが多く、「アドレス」や「Webアドレス」とも呼ばれ、私たちにとっても馴染みのあるもの。 URLはWebサイトだけではなくインターネットやLANなどのネットワーク上にあるデータやファイルの場所とそれらの取得方法を指定するために利用される。URLにはサービスを特定するためのポート番号の記載がありませんが、これはHTTPを用いてサーバーに接続する場合はポート番号80を使用すると決まっているため省略している。 ドメイン インターネット上に存在するサーバーを特定し、接続するための識別番号としてIPアドレスが存在しますが「example.com」と言うドメインと呼ばれる文字列を使用する時もある。数字で表記されるIPアドレスは私たちにとって覚えにくく扱いにくいためドメインIPアドレスの別名として利用されている。ドメインIPアドレスと同様に一位であり、世界中において同じドメインは一つとして存在しない。

ドメインとホスト名は同じ意味で使用される場合もありますが厳密にはドメイン名は「ネットワークを特定するための文字列」、ホスト名は「ネットワーク上のコンピューターにつける識別用の文字列」です。そして、ホスト名とドメイン名をつなげたものをFQDN(Fully Qualified Domain Name : 完全修飾ドメイン名)と言い、これでネットワーク上のコンピューターを特定できます。

DNS ドメインは文字列で記述されるため私たちにとってわかりやすいものですが、インターネット上においてコンピューター同士の接続にはあくまでもIPアドレスが利用されるためコンピューターへ接続するためにはIPアドレスが必要となります。そのため、ドメインを利用してコンピューターへ接続する際はドメインIPアドレスへと変換する必要がある。

ドメインIPアドレスへ変換 ドメインIPアドレスへ変換する仕組みをDNS(Domain Name System)と言います。そしてDNSのサービスを提供するサーバーをDNSサーバーと言います。DNSの仕組みは電話帳と似ている。電話帳では名前と電話番号が紐づいている様に、DNSではドメインIPアドレスが紐づいて管理されているためDNSを利用することでドメインからIPアドレスを知ることができる。ドメインIPアドレスが紐づけることを名前解決と言う。

IPアドレスの問合せ URLにドメインが利用されている時は必ずDNSサーバーへIPアドレスの問い合わせを行なっています。そしてDNSサーバーより取得したIPアドレスを元にWebサーバーへと接続しています。

DNSにおいてドメインからIPアドレスを取得するための情報をAレコードと言います。逆にIPアドレスからドメイン名を取得するための情報をPTRレコードと言います。

HTTP HTTPはあくまでデータのやり取り(要求と応答)のみを取り決めており、Webサイトを閲覧する際はHTTPだけではなく、IP、TCPといった様々なプロトコルを組み合わせて利用しています。 Webサイト閲覧においてWebブラウザからWebサーバーに対してデータを要求した際はアプリケーション層ではHTTP、トランスポート層ではTCP、インターネット層ではIP、ネットワークインターフェース層ではイーサネットプロトコルが利用されます。下層に渡される際にヘッダーというデータの概要や送信先が記載されている荷札の様なものが追加されます(カプセル化)。上層に渡される際に使用済みのヘッダーが取り除かれていきます・(非カプセル化

Web技術の基本 第1章まとめ

Webとは? Webの正式名称はWorld Wide Web Web上の文書はハイパーテキストと呼ばれる言語で構成される。ハイパーテキストはWebページの中にハイパーリンクと呼ばれる別のWebページへの参照を埋め込むことができる。

インターネットとWebの違い Web CERN(セルン、欧州原始研究機構)のティム・バーナーズ=リーにより開発される。 元々は各国の実験者がすぐに情報にアクセスできる様にするためWebの原型となるENQUIREというシステムが開発された。それが後に改良されてWWWが誕生した。その後、ティムはWebブラウザとWebサーバーを開発、インターネットに公開を始めた。 当初のWebは文字のみしか扱えなかったが画像が使える様に改良されWebブラウザが普及し始める。そして、インターネットと共に世界に広まっていく様になった。 インターネット ARPA(アーパ、アメリカ国防総省の高等研究計画局)によって開発されたARPANETというコンピューターネットワークが原型となる。ARPANETプロトコルの見直しを経て徐々に拡大し、やがて世界中に広まりインターネットと呼ばれる様になる。 当初は接続回線が高価であったため企業や研究機関のみで利用されていたが技術の発展とともに接続回線が安価になり一般ユーザーにも広まり始めた。その頃にWebがインターネットで使えるシステムとして発表された。

インターネットとWebはセットで使われるようによって世界的に広まった。

様々なWebの用途 一つのドメインにある複数のWebページの集まりWebサイトと呼ぶ。Webページ同士はハイパーリンクによって相互に繋がっていてユーザーはそれを辿ることによってWebページ間に跨がる文章を読むことができる。

コンピューターの機能とユーザーのやりとりの橋渡しをする機能をユーザーインターフェースと呼ばれる。ハイパーテキストで作られたメールの操作画面に対してユーザーが行った操作をWebサーバーがユーザーの代わりにメールサーバーに伝えてメールの操作を行う。

ユーザーインターフェースに対し、ソフトウェア同士のやりとりの橋渡しをする機能をAPI(アプリケーションプログラミングインターフェース)と呼ぶ。スマートフォンのアプリのデータ送信・受信の処理によく使われる。

HTMLとWebブラウザ HTML ハイパーテキストを記述するための言語がHTML(Hyper Text Markup Language) HTMLでは文章の表示方法やハイパーリンクをタグというマークによって表現する。この様な言語を一般的にマークアップ言語と呼ばれる。 Webブラウザ ハイパーテキストは文章にタグで意味づけをしたもので人間がそのまま読むには適していない。ハイパーテキストを解釈して人間が読みやすい様に作り替えて表示してくれるプログラムをWebブラウザと呼ぶ。 GoogleChromeFireFoxIEなどがWebブラウザにあたる。 Webブラウザの種類によって表示の方法に多少の違いはあれど、HTMLのルールは世界共通なので基本的にどの種類のWebブラウザでも同じ様にコンテンツを見ることができる。

WebサーバーとHTTP 配信プログラムWebサーバー WebサーバーはWebブラウザからコンテンツの要求があると必要なコンテンツをネットワークを通してWebブラウザに送信する役割を持つ。コンテンツはWebサーバーによって配信されることでWebページと呼ばれる様になる。また、自分が要求されたコンテンツを持っていない時は持っていないというメッセージを返したり別のWebサーバーに要求する様案内することも役割の一つ。一般的にApache(アパッチ、Apache HTTP Server)やIIS(Internet Information Service)というプログラムがよく利用される。

やりとりの手順HTTP Webページが表示される際はWebブラウザからWebサーバーにコンテンツを要求し、Webサーバーが要求されたコンテンツをWebブラウザに送信するやりとりが行われる。ハイパーテキストを送信するためのやりとりの手順と、やりとりするメッセージの書式は世界共通の仕様として決められている。 この世界共通のハイパーテキストをやりとりする手順をHTTP(Hyper Text Transfer Protocol)といい、ハイパーテキストの要求、送信手順意外にも要求されたコンテンツを持っていなかった場合の応答方法やWebサイトが移転したことをWebブラウザに伝える方法など、ハイパーテキストをやりとりする上で必要となる様々な手順が定義されている。世界基準で手順が決められていることでどの種類のWebブラウザでもあらゆる種類のWebサーバーとの間で同じ手順でハイパーテキストとやりとりできる。

WebブラウザとWebサーバーが同じものだと思っていた。Webブラウザハイパーテキストを人間向けに作り替えてくれるプログラムでWebサーバーはコンテンツとなるハイパーテキストやWebページをやりとりするプログラム。Webサーバーは世界共通でハイパーテキストのやりとりの仕様を定義しており、そのプロトコルをHTTPという。

Webページが表示される流れ Webページを取得する時に最初にURL(Uniform Resource Locator)で取得したいWebページをWebブラウザに指定する。このURLにはどういうやりとりの手順でどのWebサーバーになんのコンテンツを取りに行くかという情報が含まれていて、Webブラウザはこれらの情報をもとに要求を送る。

転送されたコンテンツはWebブラウザでHTMLの内容を解釈してタグによって決められた意味を参考にしながら人間の見やすい形に整形して表示する。

一度のコンテンツの転送で送られるファイルは一つです。HTMLの解釈の結果で画像など他のファイルが必要になった場合は再度Webサーバーにそのファイルの転送を要求し転送してもらったファイルをHTMLの表示画面にはめ込む。画像のファイル転送にもHTTPやHTTPSが使われる。これを繰り返し、全ての必要なファイルを揃えることができればWebページの表示が完了する。

静的ページと動的ページ 静的ページ 何度アクセスしても同じものが表示されるページを静的ページと呼ぶ。例(企業や団体の紹介サイトなど) 情報などを提供するサイトはいつも同じ情報(予め準備されたコンテンツ)を表示する必要があるため、一般的に静的ページで構成される。 Webが普及すると利用範囲が研究資料以外にも拡大してより豊かな表現が求められる様になり、閲覧するユーザーの状態や要求に応じて表示する内容を変化させる動的ページの技術が生まれた。

動的ページ アクセスした時の状況に応じて異なる内容が表示されるページを動的ページと呼ぶ。検索ページなどが動的ページにあたる。検索サイトではユーザーからWebブラウザを通して送られてくるデータ(検索文字列)を受け取りそれをもとにWebサーバーが検索処理を実行する。その後、検索結果を表示するためのHTMLファイルをWebサーバーが作成し、Webブラウザに送信する。このやり方でユーザーが入力した文字列に対して毎回最新の検索結果の情報を返信できる。他の例としてはユーザーが書き込むたびに内容が増えていく掲示板サイトやログインするするユーザーごとに異なるページを表示する会員サイトなどがあります。

動的ページの仕組み CGI webサーバーがwebブラウザからの要求に応じてプログラムを起動させるための仕組みをCGI(Common Gateway Interface)と呼びます。動的ページを利用する際はwebブラウザCGIが用意された場所を示すURLにアクセスします。要求を受信したWebサーバーでは、CGIによってプログラムが起動します。プログラムはWebブラウザから送信されてきたデータやWebサーバー自身が持っているデータなどからHTMLファイルを作り出し、Webサーバーを通してWebブラウザに送信されます。こうすることでWebブラウザから同じ要求が送られてきてもWebサーバーからは毎回異なったコンテンツを送信することができます。

サーバーサイド・スクリプト CGIから呼び出されるプログラムはサーバーサイド・スクリプトと呼ばれます。基本的にどの様なプログラミング言語で記述しても大丈夫ですが、一般的には文字列の扱いに長けたスクリプト言語と呼ばれる言語で記述されます。(Ruby,Javascriptなどはスプリクト言語)

クライアントサイド・スクリプト サーバーサイド・スクリプトに対し、HTMLに埋め込まれ、Webブラウザによって読み込まれる際に実行されるクライアントサイド・スクリプトというものもある。Webブラウザの設定によっては埋め込まれたプログラムが動かないこともあるため、使用する際には注意が必要。(主にJacaScriptが用いられる。)

Webの標準化 HTMLの様なWebで用いられる技術は、Webの発展に伴って機能拡張や新技術の開発が行われてきました。例えばHTMLの表示スタイルを指定する言語であるCSS(Cascading Style Sheets)、HTMLの親戚にあたるマークアップ言語、XML(Extensible Markup Language)、XHTML(Extensible Hyper Text Markup Language)などWebで使える新しい言語も開発されている。 この様な機能を開発者が独自に行ってしまうとしっかりとした規格が決まらないのでWebブラウザの開発者はどの機能までを処理できる様にするのが正しいのかわからず、「どのWebブラウザでも同じ様にWebページが表示される」状態ができなくなる。そのため、しっかりとした規格を作る必要がある、この規格を決める作業を標準化と言う。

標準化をすすめる団体 Webの標準化をすすめる団体がWebの生みの親ティム・パーナーズ・リーが創設したW3CWorld Wide Web Consortium)です。W3CはHTML、CSSXMLXHTMLなどの標準化を行っている。 W3Cが標準化したものは勧告という形で発表され、従うことは強制ではない。しかし、どのWebブラウザでも同じ様にWebページが表示される状態を実現するため今はほとんどのWebサイトがW3C標準に準拠している。

Webの設計思想 標準化されたもの以外にもWeb技術の世界では設計における思想がいくつか存在する。 RESTful REST(REpresentiational State Transfer)とは4つの原則からなるシンプルな設計を指します。このRESTの原則に従って設計されたシステムをRESTfulなシステムと呼びます。RESTfulなシステムは「前回のやりとりの結果」の様な情報を保持する必要がないためシンプルな構造になりやすくやりとりの方法や情報の示し方が統一されていることや1つの情報に別の情報をリンクさせられることからRESTfulなシステム同士であれば円滑に情報連携を行える。今では多くのWebアプリケーションがRESTfulとなる様に設計されている。

セマンティックWeb ティム・バーナーズ=リーが提唱している構想で、Webページの情報に意味(セマンティック)を付け加えたもの。こうすることでコンピューターが自律的に情報の意味を理解して処理できる様になることが期待できる。 HTMLでは意味を付与することができないため、セマンティックWebの世界ではWebページをXMLという言語によって構成する。XML文書の中にRDFという言語で意味を記述し、言葉の相互関係などはOWLという言語で記述します。この様な情報に関する意味を示す情報をメタデータと呼びます。これらの言語についてもW3Cで標準化がすすめられています。 セマンティックWebでは情報を検索する時の精度を上げることができたりWebの中から特定の種類の情報を集めて活用することができる様になる。ただしWeb既存のWebページへのメタデータ付与への作業を考えるとWeb全体への普及はまだまだ先であると言えます。

WebアプリケーションとWebシステムの違い Webページの集まりが「Webサイト」、Webを介して人が利用するサービスを提供するのが「Webアプリケーション」でプログラムが利用するサービスを提供するのは「Webサービス」と呼ばれる。WebサイトやWebアプリケーションやWebサービスを提供するための仕組みが「Webシステム」となります。

中間テーブルを使ったお気に入り機能の実装!

目次

実装したいこと

  • 掲示板にブックマーク機能を追加したい。

  • ユーザーがブックマークした掲示板を一覧できるページを実装したい。

これだけの機能なのに、めちゃくちゃ難しい。いろんなことを調べるいい機会になりました。 かなり多くのものを調べたので順序立てて説明をしていきます。

実装の大まかな流れ

  • 中間テーブルとなるBookmarkモデルの実装
  • UserモデルとBoardモデルとのアソシエーションを実装
  • Userモデルにお気に入り登録のギミックを定義
  • BookmarkControllerの実装
  • Routingの設定
  • Viewの実装

Bookmarkモデルの仕組み(多対多)

で、いざお気に入り機能をつけよう!となってもどうやって実装するの・・・?となります。なので、まずどうUserモデルとBoardモデルに紐づけていくのか考えて見ましょう。

UserとBookmarkとBoardの関係

ユーザーと掲示板とブックマークの関係を見ていきましょう。

ユーザーはたくさんの掲示板をブックマークすることができます。反対に、掲示板はたくさんのユーザーにフォローされることができます。

つまり、UserもBoardもBookmarkをたくさん持っているということになります。このような関係を多対多の関係と言われています。

中間モデル

多対多のモデルを実装するにはお互いのforeign_keyを知っている必要があります。そのために、お互いのidを格納するテーブル、中間テーブルを実装する必要があります。

Bookmarkモデルの作成

中間テーブルとなるBookmarkモデルを作成していきます。

ターミナル

rails g model Bookmark user:references board:references

migrateファイル

class CreateBookmarks < ActiveRecord::Migration[6.0]
  def change
    create_table :bookmarks do |t|
      t.references :user, null: false, foreign_key: true
      t.references :board, null: false, foreign_key: true

      t.timestamps
    end
    add_index :bookmarks, [:user_id, :board_id], unique: :true
  end
end

ユーザーが同じ掲示板をお気に入り登録しないようにunique: :trueをつける必要があります。

add_index :bookmarks, [:user_id, :board_id], unique: :trueでuser_idとboard_idの組み合わせがuniqueであることを設定します。 これでrails db:migrateを行います。

bookmark.rb

class Bookmark < ApplicationRecord
  belongs_to :user
  belongs_to :board

  validates :user_id, uniqueness: { scope: :board_id}
end

migrationにもunique: :trueを付けたので、モデルにもバリデーションを記載します。

uniquenessとscopeについて

validates :user_id, uniqueness: { scope: :board_id} end

上記は各掲示板idと同じユーザーidがお気に入り関係にならないように一意性制約を付けています。 rails cで確認して見ます。

irb(main):001:0> user = User.first
irb(main):002:0> board = Board.first

# userが掲示板をお気に入り登録する。
irb(main):003:0> user.bookmark(board)
   (0.1ms)  begin transaction
  Bookmark Exists? (0.9ms)  
  Bookmark Create (3.2ms)  
   (0.7ms)  commit transaction
  
=> #<ActiveRecord::Associations::CollectionProxy [#<Board id: 1, title: "hogehoge", body: "3333333", user_id: 1, created_at: "2020-11-19 10:27:20", updated_at: "2020-11-19 10:27:20", board_image: "8965-1-20151228165149_b5680ea1512891.jpg">]>


# もう一度同じユーザーで掲示板のお気にいり登録を試みる。
irb(main):004:0> user.bookmark(board)

# すでにお気に入り登録されているので、バリデーションに引っ掛かりrollbackされる。
(0.1ms)  begin transaction
  Bookmark Exists? (0.2ms)  
   (0.1ms)  rollback transaction
Traceback (most recent call last):
        2: from (irb):4
        1: from app/models/user.rb:27:in `bookmark'
ActiveRecord::RecordInvalid (バリデーションに失敗しました: Userはすでに存在します)
# 違うユーザーを指定
irb(main):005:0> user_2 = User.second

# 違うユーザーで掲示板をお気に入り登録を試みると成功する。
irb(main):006:0> user_2.bookmark(board)
(0.1ms)  begin transaction
  Bookmark Exists? (0.2ms)  
  Bookmark Create (0.8ms)  
   (1.4ms)  commit transaction
  
=> #<ActiveRecord::Associations::CollectionProxy [#<Board id: 1, title: "hogehoge", body: "3333333", user_id: 1, created_at: "2020-11-19 10:27:20", updated_at: "2020-11-19 10:27:20", board_image: "8965-1-20151228165149_b5680ea1512891.jpg">]>

bookmarkメソッドはUserモデルに定義しています。また後で紹介しますがconsoleで出てくるので載せておきます。

user.rb

  # お気に入りにしている掲示板を取得する
  has_many :bookmarks_boards, through: :bookmarks, source: :board


  # お気に入り追加
  # <<で引数で渡した掲示板の情報がbookmark_boardsに入っている
  def bookmark(board)
    bookmarks_boards << board
  end
参考記事

uniqueness: scope を使ったユニーク制約方法の解説 - Qiita

Active Record バリデーション - Railsガイド

【初心者向け】丁寧すぎるRails『アソシエーション』チュートリアル【幾ら何でも】【完璧にわかる】🎸 - Qiita

UserモデルとBoardモデルのアソシエーションの設定

Bookmarkモデルの実装が終わったので、他のモデルにもアソシエーションなどの設定を行っていきます。

Boardモデル

board.rb

class Board < ApplicationRecord
  belongs_to :user
  has_many :comments, dependent: :destroy
  has_many :bookmarks, dependent: :destroy # 追記
  mount_uploader :board_image, BoardImageUploader

  validates :title, length: { maximum: 255 }, presence: true
  validates :body, length: { maximum: 65535 }, presence: true
end

掲示板はたくさんのBookmarkを持つことができるのでhas_manyを使います。

Userモデル

user.rb

  has_many :bookmarks, dependent: :destroy
  # お気に入りにしている掲示板を取得する
  has_many :bookmarks_boards, through: :bookmarks, source: :board

UserもたくさんのBookmarkを持つことができるのでhas_manyを使っていきます。

ちょっと待って、has_many :bookmarks_boardsって何?throughとかsourceも使っているけどよくわからない・・・。 というわけでこれからhas_many throughについて調べたことをまとめていきます。

has_many throuth

まずhas_many throughはUserモデルでどういう働きをしているのかというと、ユーザーがお気に入りしている掲示板を取得することができるようになります。

Twitterでいいね一覧が表示できる機能がありますよね。それと同じようにお気に入り登録した掲示板を一覧で表示できるページを作るために必要となってきます。

has_many throughを使わずにユーザーのお気に入りした掲示板を取得したいとなると、このようなコードになります。

# user.bookmarksでuserがお気に入り登録した掲示板のidが入っているレコードの集合を取得することができる。
irb(main):014:0> user.bookmarks

=> #<ActiveRecord::Associations::CollectionProxy [#<Bookmark id: 7, user_id: 1, board_id: 1, created_at: "2020-11-20 00:50:02", updated_at: "2020-11-20 00:50:02">, #<Bookmark id: 10, user_id: 1, board_id: 3, created_at: "2020-11-20 01:57:05", updated_at: "2020-11-20 01:57:05">, #<Bookmark id: nil, user_id: 1, board_id: 1, created_at: nil, updated_at: nil>]>

# userが最初にお気に入り登録した掲示板のレコードを取得
irb(main):005:0> user.bookmarks.first
  Bookmark Load (0.5ms)  
=> #<Bookmark id: 7, user_id: 1, board_id: 1, created_at: "2020-11-20 00:50:02", updated_at: "2020-11-20 00:50:02">

# 上記で取得したレコードにboardメソッドを実行するとお気に入り登録した掲示板の内容が取得できる!
irb(main):006:0> user.bookmarks.first.board
  Bookmark Load (0.1ms)  
  Board Load (0.2ms)  
=> #<Board id: 1, title: "hogehoge", body: "3333333", user_id: 1, created_at: "2020-11-19 10:27:20", updated_at: "2020-11-19 10:27:20", board_image: "8965-1-20151228165149_b5680ea1512891.jpg">

# つまり、user.bookmarksのひとつひとつのレコードにboardメソッドを実行すればユーザーがお気に入りにした掲示板の内容の集合を取得することができる!

# なのでmapメソッドを使ってユーザーがお気に入り登録した掲示板のレコードにboardメソッドを実行し、それを配列に組み込んでいく。
irb(main):007:0> user.bookmarks.map{|bookmark| bookmark.board}
  Bookmark Load (0.4ms)  
  Board Load (0.2ms) 
  Board Load (0.1ms)  
=> [#<Board id: 1, title: "hogehoge", body: "3333333", user_id: 1, created_at: "2020-11-19 10:27:20", updated_at: "2020-11-19 10:27:20", board_image: "8965-1-20151228165149_b5680ea1512891.jpg">, #<Board id: 3, title: "aaaaaaaaaa", body: "aaaaaaaaaaa", user_id: 2, created_at: "2020-11-20 01:56:08", updated_at: "2020-11-20 01:56:08", board_image: nil>]

# 上記の式を(&:)を使って書き換えます。
irb(main):008:0> user.bookmarks.map(&:board)
=> [#<Board id: 1, title: "hogehoge", body: "3333333", user_id: 1, created_at: "2020-11-19 10:27:20", updated_at: "2020-11-19 10:27:20", board_image: "8965-1-20151228165149_b5680ea1512891.jpg">, #<Board id: 3, title: "aaaaaaaaaa", body: "aaaaaaaaaaa", user_id: 2, created_at: "2020-11-20 01:56:08", updated_at: "2020-11-20 01:56:08", board_image: nil>]

つまり、user.bookmarks.map(&:board)を使えばユーザーのお気に入り登録している掲示板の情報の集合を取得できるというわけです!

このコードをcontrollerなどに書いて実装するのも一つの方法だと思いますが、あまり直接的ではないのと、このコードをビューに落とし込むのも大変です。

そこでhas_many throughの登場です。 これを使えばuser.bookmarks.map(&:board)をモデル内に簡単に実装できちゃいます。

has_many :bookmarks_boards, through: :bookmarks, source: :board

:bookmarks_boardsと定義することでメソッド化して使うことができます。

user.bookmarks.map(&:board)このコードを見ながら解説していくと

Userのインスタンスにbookmarksメソッド(through:で定義)を実行し、それで得られたBookmarksのインスタンスデータのひとつひとつの要素に対してboardメソッド(source:で定義)を実行する

ということです。

なので、多対多のモデルを作った時に必ずと言っていいほど活躍するというわけです!

参考記事

実はRailsチュートリアルの第14章の動画を見るとすごくわかりやすいです。

Ruby on Rails チュートリアル:プロダクト開発の0→1を学ぼう

Railsガイドの文献

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

Bookmarks_Controllerの実装

よし、モデルのアソシエーションも終わったしcontroller作ろう! ちょっと待ってください。controllerの可読性を上げるためにまずはモデルにお気に入り登録のギミックを定義していきましょう。すると驚くほどにcontrollerの実装が完結になりますよ!

controllerを作る前にモデルにBookmarkのギミックを定義する。

Userモデルにお気に入り登録のギミックとなるメソッドを定義していきましょう。

user.rb

  # お気に入り追加
  # <<で引数で渡した掲示板の情報がbookmark_boardsに入っている
  def bookmark(board)
    bookmarks_boards << board
  end

  # お気に入りを外す
  def unbookmark(board)
    bookmarks_boards.delete(board)
  end

  # お気に入り登録しているか判定するメソッド
  def bookmark?(board)
    bookmarks_boards.include?(board)
  end

早速先ほど定義したbookmarks_boardsが使われていますね。 一つずつメソッドを見ていきます。

bookmarkメソッド

  # お気に入り追加
  # <<で引数で渡した掲示板の情報がbookmark_boardsに入っている
  def bookmark(board)
    bookmarks_boards << board
  end

掲示板の情報のレコードが引数boardに格納されbookmarks_boards<<で追加されています。

<<は指定されたオブジェクトの末尾に破壊的に追加できるメソッドです。 強制的に追加されて保存もされているのでsaveメソッドなどは必要ありません。

<<メソッドについて詳しくはこちら

Array#<< (Ruby 2.7.0 リファレンスマニュアル)

unbookmarkメソッド

  # お気に入りを外す
  def unbookmark(board)
    bookmarks_boards.delete(board)
  end

bookmarks_boardsからboardの引数に入っている掲示板idが入ったレコードを探し出して削除(delete)するメソッド。

bookmark?メソッド

  # お気に入り登録しているか判定するメソッド
  def bookmark?(board)
    bookmarks_boards.include?(board)
  end

bookmarks_boardsにboardの引数に入っている掲示板idが含まれているレコードがあるかどうか判定するメソッド。

def bookmark?(board)
    Bookmark.where(user_id: id, board_id: board.id).exist?
end

このように書くこともできますが、include?の方が直感的でわかりやすいです。

bookmarks_controllerの実装

ここまできたら、bookmarks_controllerを作っていきましょう。 viewも必要ないのでcontrollerファイルだけ作って記載します。

bookmarks_controller.rb

class BookmarksController < ApplicationController


  def create
    board = Board.find(params[:board_id])
    current_user.bookmark(board)
    redirect_back fallback_location: root_path, success: 'ブックマークしました'
  end

  def destroy
    board = current_user.bookmarks.find_by(params[:id]).board
    current_user.unbookmark(board)
    redirect_back fallback_location: root_path, success: 'ブックマークを外しました'
  end
end

お気に入り登録のギミックをモデルに定義したことによって、かなり直感的なcontrollerになりました!

redirect_back fallback_location : root_path

redirect_backはユーザーが直前にリクエストを送ったページに戻すことができます。

fallback_locationは直前にリクエストを送ったページがない場合のデフォルトのリダイレクト先を指定しています。

Routingの設定

controllerも書けたので、次はRoutingの設定を行っていきます。

routes.rb

Rails.application.routes.draw do
  root 'static_pages#top'

  resources :users

  get 'login', to: 'user_sessions#new'
  post 'login', to: 'user_sessions#create'
  delete 'logout', to: 'user_sessions#destroy'

  resources :boards, shallow: true do
    resources :comments, only: %i[create destroy]
    resource :bookmarks, only: [:create, :destroy]
    collection do
      get :bookmarks
    end
  end
end

BoardとBookmarkは親子の関係なのでbookmarks_controllerのRoutingはboards_controllerにネストするように記載しています。

collectionルーティング

railsの基本的なアクションはresourcesで定義した時に作られる7つのアクションですが、更に別のアクションを追加したい時があります。

その時に使えるのがcollectionルーティングです。boards_controllerに新しくbookmarksというアクションを追加することができます。 ちなみに、controllerのメンバーに対してアクションを追加する場合(idが伴う場合)はmemberルーティングを使います。

collection以外を抜いたルーティングがこちら。

resources :boards, shallow: true do
    collection do
      get :bookmarks
    end
  end

bookmarks_boards GET /boards/bookmarks(.:format) boards#bookmarks 上記でこのようなルーティングが出来上がります。 このルーティングとアクションはお気に入りされた掲示板一覧を表示するページとして使っていきます。

Boards_controllerにbookmarksアクションを追記

ルーティングが書けましたので、Boards_controllerにbookmarksアクションを追加していきます。

boards_controller.rb

def bookmarks
    @bookmark_boards = current_user.bookmarks_boards.includes(:user).order(created_at: :desc)
end

無駄にSQL文を発行させない様にincludes(:user)を記載して関連するuserの情報も取得しています。(n + 1問題の解消)

irb(main):001:0> user = User.first

irb(main):002:0> user.bookmarks_boards
  Board Load (2.2ms)  SELECT "boards".* FROM "boards" INNER JOIN "bookmarks" ON "boards"."id" = "bookmarks"."board_id" WHERE "bookmarks"."user_id" = ? LIMIT ?  [["user_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Board id: 1, title: "hogehoge", body: "3333333", user_id: 1, created_at: "2020-11-19 10:27:20", updated_at: "2020-11-19 10:27:20", board_image: "8965-1-20151228165149_b5680ea1512891.jpg">, #<Board id: 3, title: "aaaaaaaaaa", body: "aaaaaaaaaaa", user_id: 2, created_at: "2020-11-20 01:56:08", updated_at: "2020-11-20 01:56:08", board_image: nil>]>

irb(main):003:0> user.bookmarks_boards.includes(:user)
  Board Load (0.3ms)  SELECT "boards".* FROM "boards" INNER JOIN "bookmarks" ON "boards"."id" = "bookmarks"."board_id" WHERE "bookmarks"."user_id" = ? LIMIT ?  [["user_id", 1], ["LIMIT", 11]]
  User Load (0.5ms)  SELECT "users".* FROM "users" WHERE "users"."id" IN (?, ?)  [["id", 1], ["id", 2]]
=> #<ActiveRecord::AssociationRelation [#<Board id: 1, title: "hogehoge", body: "3333333", user_id: 1, created_at: "2020-11-19 10:27:20", updated_at: "2020-11-19 10:27:20", board_image: "8965-1-20151228165149_b5680ea1512891.jpg">, #<Board id: 3, title: "aaaaaaaaaa", body: "aaaaaaaaaaa", user_id: 2, created_at: "2020-11-20 01:56:08", updated_at: "2020-11-20 01:56:08", board_image: nil>]>

Viewの実装

ここまで仕組みを実装したら、あとはViewを作るだけです! お気に入りボタンとお気に入りした掲示板一覧ページを作成していきます。

お気に入りボタンの作成

まずはパーシャルでブックマークするボタンとブックマーク解除ボタンを切り替えるページを作ります。

bookmarks/_bookmark_area.html.erb

<% if current_user.bookmark?(board) %>
  <%= render 'bookmarks/unbookmark', board: board %>
<% else %>
  <%= render 'bookmarks/bookmark', board: board %>
<% end %>

user.rbで定義されたbookmark?メソッドがここで使われます。 掲示板がブックマークされていたら掲示板解除ボタン、掲示板がブックマークされていなかったらブックマーク登録ボタンに切り替わる仕組みです。

次に、お気に入り登録ボタンを実装していきます。 bookmarks/_bookmark.html.erb

<%= link_to board_bookmarks_path(board_id: board.id), id: "js-bookmark-button-for-board-#{board.id}", method: :post do %>
  <%= icon 'far', 'star' %>
<% end%>

次に、お気に入り解除ボタンを実装していきます。 bookamrks/_unbookmarks.html.erb

<%= link_to board_bookmarks_path(current_user.bookmarks.find_by(board_id: board.id)), id: "js-bookmark-button-for-board-#{board.id}", method: :delete do %>
  <%= icon 'fas', 'star' %>
<% end %>

current_userに紐づいているbookmarkインスタンスの中から掲示板idが含まれているものを探し、取得しています。

これで、お気に入りボタンが完成しました。これを掲示板のパーシャルに組み込んでいきます。

boards/_board.html.erb

<% if current_user.own?(board) %>
   <div class='mr10 float-right'>
      <%= render 'crud_menus', board: board %>
<% else %>
     <%= render 'bookmarks/bookmark_area', board: board %>
   </div>
<% end %>

掲示板がログインしているユーザーのものだったらcrud_menusボタンが表示され、ユーザーのものではなかった場合はお気に入りボタンに切り替わる様になっています。

お気に入り掲示板一覧機能ページの作成

最後に、お気に入りした掲示板一覧ページを作成していきます。 掲示板一覧ページとレイアウトはあまり変わらないため、ほとんど掲示板一覧ページから引っ張ってきています。

boards/bookmarks.html.erb

<% content_for(:title, t('.title')) %>
<div class="container pt-3">
  <div class="row">
    <div class="col-lg-10 offset-lg-1">
      <!-- 検索フォーム -->
      <form>
        <div class="input-group mb-3"><input class="form-control" placeholder="検索ワード" type="search"/>
          <div class="input-group-append"><input type="submit" value="検索" class="btn btn-primary"/></div>
        </div>
      </form>
    </div>
  </div>

  <div class="row">
    <div class="col-12">
      <div class="row">
      <% if @bookmark_boards.present? %>
        <%= render partial: "board", collection: @bookmark_boards %>
      <% else %>
        <p>ブックマーク中の掲示板がありません</p>
      <% end%>
      </div>
    </div>
  </div>
</div>

これで完成です!お疲れ様でした!

おまけ

n + 1問題を解消するためにコードをリファクタリングしていきます。

boards_controller.rb

def index
# userのみキャッシュしている。
  @boards = Board.all.includes(:user).order(created_at: :desc)

 #bookmarkも取得できる様になる。
  @boards = Board.all.includes([:user, :bookmarks]).order(created_at: :desc)
  end

user.rb

# userを起点にしてSQLを走らせてしまっているためレコードを取得する時に毎回SQLが走ってしまう。
def bookmark?(board)
  bookmarks_boards.include?(board)
end

# boardを起点にしてSQLが走り、検索をかける。無駄なSQLが走らない。
def bookmark?(board)
  bookmarks_boards.pluck(:user_id).include?(id)
end

_unbookmark.html.erb

<%= link_to bookmark_path(board.bookmarks.find { |b| b.user_id == current_user.id }),
            id: "js-bookmark-button-for-board-#{board.id}",
            class:"float-right",
            method: :delete,
            remote: true do %>
  <%= icon 'fas', 'star' %>
<% end %>

なるべくfindを使う様にして無駄にSQLを走らせない様にする。

ネストしたルーティングのフォームの書き方について

以前に課題をやって理解できていなかった親子構造であるネストしたルーティングのフォームの書き方について復習していきます。

実装概要

掲示板アプリを作成しています。

掲示板詳細ページにて掲示板に対したコメント投稿を行えるようにする。

コメント投稿フォームは掲示板詳細ページ内に実装。

コメント一覧も掲示板詳細ページに実装。

前提

ユーザー登録機能、ログイン機能、掲示板作成機能は実装済み。 UserモデルとBoardモデルは作成しており、アソシエーションを繋げています。

Commentモデルを作成

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

上記をターミナルで打ち、UserモデルとBoardモデルをアソシエーションしたCommentモデルを作成していきます。

Comment.rb

class Comment < ApplicationRecord
  belongs_to :user
  belongs_to :board

  validates :body, length: { maximum: 65535 }, presence: true
end

UserモデルともBoardモデルとも1対多なのでbelongs_toで繋げていく。

掲示板詳細ページにてコメント投稿フォームとコメント一覧を記載

掲示板詳細ページを作り、そこにコメント投稿フォームとコメント一覧ページを追加していく。

コメント投稿フォーム

ここが一番難しく、理解に時間がかかるところでした。

boards/show.html.erb

<% content_for(:title, @board.title) %>
<div class="container pt-5">
  <div class="row mb-3">
    <div class="col-lg-8 offset-lg-2">
      <h1>掲示板詳細</h1>
      <!-- 掲示板内容 -->
      <article class="card">
        <div class="card-body">
          <div class='row'>
            <div class='col-md-3'>
              <%= image_tag @board.board_image.url, class: "card-img-top img-fluid", width: 300, height: 200 %>
            </div>
            <div class='col-md-9'>
              <h3 style='display: inline;'><%= @board.title %></h3>
              <ul class="list-inline">
                <li class="list-inline-item">by <%= @board.user.decorate.full_name %></li>
                <li class="list-inline-item"><%= l @board.created_at, format: :short %></li>
              </ul>
            </div>
          </div>
          <p><%= @board.body %></p>
          <% if current_user.own?(@board) %>
            <div class='mr10 float-right'>
              <%= link_to edit_board_path(@board), id: 'button-edit-#{board.id}' do %>
                <%= icon 'fa', 'pen' %>
              <% end %>
              <%= link_to board_path(@board), id: 'button-delete-#{board.id}', method: :delete, data: {confirm: '削除してもいいですか?'} do %>
                <%= icon 'fas', 'trash' %>
              <% end %>
            </div>
          <% end %>
        </div>
      </article>
    </div>
  </div>

  <!-- コメントフォーム -->
  <%= render 'comments/form', {board: @board, comment: @comment} %>

  <!-- コメントエリア -->
  <% if @comments.present? %>
    <%= render @comments %>
  <% end %>
</div>

comments/form.html.erb

<div class="row mb-3">
  <div class="col-lg-8 offset-lg-2">
    <%= form_with model: comment, url: [board, comment], local: true do |f|%>
    <%= render 'shared/error_messages', object: f.object %>
      <%= f.text_field :body, class: 'form-control mb-3', placeholder: 'コメント' %>

      <%= f.submit '投稿', class: 'btn btn-primary' %>

    <% end %>
  </div>
</div>

まず、show.html.erbでパーシャル化したフォームをレンダリングしています。 どの掲示板にコメントしているのかわからないとどの掲示板にコメントしているのかわからなくなり、困ります。

そのため、commentインスタンスにはboard_idが必要です。

なのでurlでboard_idを取得するためboardインスタンスをコメント投稿フォームのurlに組み込む必要があります。

上記のform_withは省略された形で、省略せずに書くとこのような形になります。

<%= form_with model: comment, url: board_comment_path(board, comment), local: true do |f| %>

board_comment_path/boards/:board_id/commentsというurlになるため、boardインスタンスとcommentインスタンスを渡してあげるようにしないとエラーが起こります。 これを簡略化したものがこちらのフォームの書き方となります。

<%= form_with model: comment, url: [board, comment], local: true do |f|%>
コメント一覧フォーム

コメント一覧はパーシャルである_comment.html.erbを作成し、<%= render @comments %>レンダリングしています。

controllerの実装

この部分も理解に苦労しました。

boards_controller showアクションの実装

boards_controller.rb

class BoardsController < ApplicationController
before_action :set_board, only: [:show, :edit, :update, :destroy]
  def index
    @boards = Board.all.order(created_at: :desc)
  end

  def new
    @board = Board.new
  end

  def create
    @board = current_user.boards.create(board_params)
    if @board.save
      redirect_to boards_path, success: '掲示板作成しました'
    else
      flash.now[:danger] = '掲示板作成に失敗しました'
      render :new
    end
  end

  def show
    @comments = @board.comments.all.order(created_at: :desc)
    @comment = Comment.new
  end

  def edit; end

  def update
    if @board.update(board_params)
      redirect_to board_path(@board), success: '掲示板を更新しました'
    else
      render :edit
      flash[:danger] = '掲示板を更新できませんでした'
    end
  end

  def destroy
    @board.destroy!
    redirect_to boards_path, success: '掲示板を削除しました'
  end

private

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

  def set_board
    @board = current_user.boards.find(params[:id])
  end
end

え?なんでboards_controllerのshowアクションに@commentsや@commentを書いてるの?

なぜかというとコメント投稿フォームやコメント一覧ページはboardsのshowページに書かれているからです。いくらcomments_controllerに@commentのインスタンス変数を定義していても、comments/show.html.erbというページに書かれているわけではないのでboards/show.html.erbのページには反映されないというわけです。

@coomentsは指定されたboardのコメントだけを取得して表示させたいので@board.comments.allで取得しています。order(created_at: :desc)は作成日時を降順に並べ替えています。

comments_controllerの実装

comments_controller.rb

class CommentsController < ApplicationController

  def create
    comment = current_user.comments.create(comment_params)

    if comment.save
      redirect_to board_path(comment.board), success: 'コメントを投稿しました'
    else
      redirect_to board_path(comment.board)
      flash.now[:danger] = 'コメントの投稿に失敗しました'
    end
  end

  def destroy
    comment = Comment.find(params[:id])
    comment.destroy
    redirect_to board_path(comment.board), success: 'コメントを削除しました'
  end

private

  def comment_params
    params.require(:comment).permit(:body).merge(board_id: params[:board_id])
  end

end

まず、知らなかったのがストロングパラメーターにmergeメソッドがあるということ。mergeメソッドを使うことで直接的にユーザーから受け取った値でなくとも値の情報を取得して追加して処理できます。

私はこのメソッドを知らずにわざわざ@comment.board_id = (params[:board_id])で取得していました。mergeメソッドの方が使いやすくて綺麗で見やすくていいですね。

current_userメソッドからログインしているuser_idを引っ張ってきてコメントを作成しています。

参考にした記事

【Ruby on Rails】ストロングパラメータとは何か? また記載方法は? - Qiita

VueRouterを使ってVue.jsのルーティング作成

実装したいこと

Vue.jsのTodoアプリを作成しようとしています。 VueRouterを使ってトップページとタスク管理ページを遷移するリンクを実装していきます。

前提

トップページ(../pages/top/index.vue)とタスク管理ページ(../pages/tasks/index.vue)は作成済み。

routes.rb

Rails.application.routes.draw do
  root to: 'home#index'
  get '*path', to: 'home#index' # ブラウザで直接タスクページにアクセスするとエラーが発生するためサーバーサイドのルーティングを集約する記述を追加する。
end

home/index.html.erb

<%= javascript_pack_tag "hello_vue" %> # javascriptファイルにあるhello_vueを読み込む

VueRouterをインストールする

ターミナルにてyarnを使ってインストールしました。

% yarn add vue-router

VueRouterの作成

javascript/routerディレクトリを新たに作り、その配下にindex.jsファイルを作成する。これがVueRouterの仕組み全体のファイルとなり、Railsroutes.rbと同じような役割を担うことになる。

router/index.js

import Vue from 'vue';
import VueRouter from 'vue-router';

// ルート用のコンポーネントを読み込む
import TopIndex from "../pages/top/index.vue";
import TaskIndex from "../pages/task/index.vue";

Vue.use(VueRouter)

const router = new VueRouter({
  mode: 'history',
  routes: [
    {
      path: '/',
      component: TopIndex,
      name: "TopIndex"
  },
  {
    path: '/tasks',
    component: TaskIndex,
    name: "TaskIndex"
  }
]
})

export default router

import ◯ from '××'javascriptのインポートが行われるコード。

まずVueとVueRouterをindex.jsにインポートしていく。

pagesディレクトリ配下に作られたtop/index.vueファイルとtask/index.vueファイルをコンポーネントとしてインポートしていく。

モジュールシステムを使う場合はVue.use(VueRouter)で明示的にモジュールをインストールする必要があるため記述しています。

これはグローバルなscriptタグを使っている時は必要ない記述だそうです。

const router = new VueRouterで新しくVueRouterのインスタンスを作成して、HTML5 Historyモードを使用してルーティングを記述していきます。 ルーティングを記述していく際に名前付きルートを使って記述していきます。

routes: [
    {
      path: '/',
      component: TopIndex,
      name: "TopIndex"
  },
  {
    path: '/tasks',
    component: TaskIndex,
    name: "TaskIndex"
  }
]

上記のように記述していくと、<router-link>に名前で指定することができるようになります。

pages/top/index.vue

<router-link :to="{ name: 'TaskIndex' }" class="btn btn-darkmt-5">はじめる</router-link>

pages/tasks/index.vue

<router-link :to="{ name: 'TopIndex' } class="btn btn-dark mt-5">戻る</router-link>

export default routerjavascriptでモジュールを他のモジュールに渡すことができる記述です。ここではVueRouterのインスタンスであるrouterをエクスポートしています。

VueRouterのセッティング

先ほど作成したrouter/index.jshello_vue.jsにインポートしてrouterというインスタンスを作成した後、Vueインスタンスの生成時のオプションの中に組み込んでいきます。 packs/hello_vue.js

import Vue from 'vue'
import App from '../app.vue'
import 'bootstrap/dist/css/bootstrap.css'
import router from '../router/index.js' // ここで読み込み
Vue.config.productionTip = false

document.addEventListener('DOMContentLoaded', () => {
  const app = new Vue({
    router, // routerをVueインスタンスに渡している
    render: h => h(App)
  }).$mount()
  document.body.appendChild(app.$el)
})