はじめに
プログラミングはエラーと切っても切り離せない関係です。
素人からどんな玄人でもエラーと向き合いながら開発をしていきます!エラーと仲良くなり、乗り越えることを通してプログラミングの喜びを味うことができるでしょう。
この記事では主に初学者向けにRailsアプリ開発におけるエラーを取り上げます。
Goal
一般的なエラー解決の向き合い方と代表的なエラー解決のユースケースについて知る。
エラー対応の原則
エラー解決には、構造や因果関係を理解したうえで、様々なヒントから原因を推測し、変更を加える必要があります。
エラー解決できる能力は、確かに経験値に依る側面も大きいですが、吸収のスピードを上げることも大切です。構造や因果関係の理解に関しては、学習コンテンツを読み込んだりして理解に努めると良いでしょう。
原因の推測に関して、例えば、基本的な投稿機能やが上手く実装できないとなった場合、以下のように原因を推測してみましょう!
根っこのデータベースから順番に必要な仕組みが備わっているか確認していきます。
1. テブル、カラムがあるか
db/schema.rbを見て必要なテーブルとカラムがあるか確かめます。データ型もきちんとinteger(整数)とstring,text(文字)が区別されていることを確認します。
もしここに不十分な点が見つかれば、migrationで解決します。
2. パラメータを送れているか
例えば投稿機能であれば、newページのform_forの内容を見て、以下画像の部分の文字列が合っているか確認します。この文字列は後にパラメータのキーになります。
次にターミナル中のPOSTメソッドが実行されている箇所を見て、きちんとパラメータが送れているか確かめます。(中括弧で書かれたParametersのところ。ハッシュ型になっています)またデータベースの更新は緑色のINSERT文で書かれます。(このような緑色のINSERTや青色のSELECT文はデータベース言語のSQLで書かれます。興味がある方は調べてみてください!)もしエラーが起きている場合は、基本的にINSERT文が表示されません。つまりデータベースに投稿が登録されていません。
3. パラメータを受け取れているか
送られてきたパラメータを受け取る許可をしないと、unpermitted paramaterのように注意を受けます。
コントローラーのprivate以下を正しく記述し、パラメータから必要な文字列を受け取れるようにします。
4. データベースに保存できているか
ターミナルはエラー解決に向けたヒントの宝庫です。必ず目を通しておく習慣をつけましょう。徐々に慣れていければ大丈夫です!データベースに保存できているかどうかは、ターミナル中に緑色のINSERT文が出ていて、必要なカラムにレコードが登録されていることが一目でわかります。基本的にはここを見てデータベースに保存できているか確認します。しかし、たまに今まで挙げてきたチェック項目を全て満たしているのに、データベースに登録されないケースがあります。このケースではエラーが特定できそうな記述が出てこないので困りますが、このような場合は十中八九外部キー制約を疑うと良いでしょう。外部キー制約とは外部キーにnil(無、空)を許さないというものです。例えばtweetsテーブルのuser_idがnilになってしまうパターンです。まず、tweetsテーブルにinteger型のuser_idカラムがあることを確かめます。次にcurrent_user(deviseのメソッド)を取得するためにログインしていることを確認し、createアクション中に、user_idカラムにcurrent_user.idを代入する記述があることを確認します。
上記はRailsの基本となる投稿機能が上手くいかないときに確認すべきチェックポイントです。これらを見られるようになれば、大抵のエラーは解決できます。
もちろん、上記のチェックポイントを逸脱したエラーも多様にあるので、一般的なエラー対応の流れについてまとめます。
1. エラー文(ターミナル)を読む
兎にも角にもエラー文の意味を理解することはMustです。英語から背を向けないでください。
ここと向き合わない限り,自力でプログラミングができるようになる日は来ないと言っても良いでしょう。プログラミングの世界は英語で構築されているためです。
短い英文の場合、前置詞が重要だったりします。
2. 場所を特定
エラー文を読んでどの場面、どのファイル中にエラーが生じているかを把握します。場数を踏むとこの時点で大体の原因が分かってきますが、最低限場所の特定はするようにしましょう!
3. 原因を推測
エラー文等のヒントから原因を推測します。原因は無数にあります。上記の投稿機能のエラーのような形で原因を特定することもできれば、ひょんなスペルミスが原因だったり、Railsの根幹に関わる高度なところに原因がある場合もあります。もちろん最初は自力で原因を特定することは難しいので、勉強が必要です。
そんなときにまずGoogleやChatGPTが役に立ちます。Railsは一般的にも良く用いられている言語なので、エラー文をコピペして調べると色々な人(素人からエンジニアまで)が記事に残してくれています。これらを参考にすると思わぬヒントに遭遇するかもしれません。しかし、信頼のない他者の意見であることには変わりが無いのでそのまま鵜呑みにするのは危険な場合があります。本当の原因と異なる解決法を実行してしまい、新たなエラーの火種になる可能性があるためです。あくまで参考程度にしておきましょう。記事によってはエラー文と解決策のみが書いてあるものもあります。このような記事ではなく、きちんと原因にも言及している記事を読むようにしてください。また、Qiita記事の場合、更新日時が新しく(Railsのバージョン等が一致しているため)、LGTM(いいね数)が多いものほど信頼できる根拠にはなります。とはいえ調べて色々な考え方に触れること自体は良い勉強になるので、積極的に調べてみてください!
また、質問する力も大切です。世界的なIT企業であるGoogleには15分以上一人で考えて分からなかったら人に聞けというルールがあるそうです。エラー解決は気づきに左右されるものでもあるので、一人で考え続けて半日1日以上解決できないものが、人によっては3分で解決できたりします。一瞬で解決して、浮いた時間を他の勉強に費やしたり開発を進められれば、非常に生産的ですよね!もちろんエラー解決のためにあらゆる方法を試す過程にも学べることはたくさんあります。とはいえ効率を求めることも大事ですし、素直に人に聞く(聞ける人脈を持つ)力も能力のうちです。もちろん上手に質問する方が求める答えは返ってきやすくなります。
上手に質問するために、、、
上手な質問 = 共有できる情報量の多い質問と考えて差し支えないでしょう。
レベルが高くなってくると必要な情報のみを簡潔に、という考え方も出てきますが、初心者のうちはなるべく多くの情報を共有した方が解決のヒントに繋がります。
最も良い質問の仕方は対面で会って聞くことです。これは当たり前ですね!次にオンラインで聞くことです。例えばリアルタイムで画面共有したりLiveshareでVscodeの中身を共有したりします。テキストベースで質問するような場合も多くの人の目に留まるのでどこかで引っ掛かる可能性があります。
今度は、質問の内容についてです。よくありがちなのが、自分しか理解できていない文脈の共有を端折ってしまうケースです。なるべく前提知識を擦り合わせするようにしましょう。また、相手に伝わるように質問をします。(全く文脈を知らない第三者が見てもが質問内容を理解できるくらいを想定すると良いでしょう)もちろんテキストベースの場合、情報量が限られるので画像の見え方や文章を客観的に見てみましょう。また自分の成長のために仮説ドリブンをしてください。自分が推測するエラーの原因を常に考えておいて質問時に共有します。これをすることで比較ができるようになります。また、自分にとってもどこまで理解しているか把握でき、PDCAが回せるようになるので成長することができます。回答待ちの時間も有意義に過ごすことを心がけましょう。
エラーが生じた、色々試してみた、人に聞いてみた、色々試してみた、けどどうにも解決できない、エラー前の元に戻すことすらできない、どうしたら良いの???( ノД`)
という事態は避けたいですよね!
このようにエラーが起きる前に戻すことすらできず泥沼にはまってしまった場合、新しく作り直すことが最善の解決策になってしまいます。
これを未然に防ぐために、Git管理をしましょう。
これはゲームのセーブデータのように、進捗を管理できるツールです。
失敗しても途中からやり直せる安心感によって、とりあえず試行錯誤して色々やってみることができるといった学習面でのメリットを享受することができます。
詳細は他記事に譲りますが、Git、Githubはエンジニアでは必須スキルなので必ず身につけるようにしてください。
次から代表的なエラーの解決法についてのユースケースを見ていきます。
No method error、Syntax error
まず、少し時間を取ってみてよくある以下のエラー画面を正確に読解してみましょう!
undefined method each for nil:NilClass
method と eachは同格関係です。前置詞のforは「~に対して」という対象を表す意味ですね。
未定義のメソッド=each ← nil(無)に対して、といことはつまり、
「無に対してeachメソッドは定義されていませんよ~」もっと砕けて言うと
「無に対してeachメソッドは利用できませんよ~」ということです。
eachメソッドは配列から要素を一つずつ取り出すRubyのメソッドでしたね!
これらを踏まえると、本来存在すべきはずの何かが無になっていると推測できます。
では、何が無になっているのかを見つけるために、eachメソッドをかける対象を見ていきます。赤色のマーカが引かれている22行目に注目して@tweetsが無になっているのでは??と考えることができます。本来はこの@tweetsに投稿一覧が配列で入っていて、これにeachメソッドをかけたいというわけです。
そこで@tweetsはどこから由来しているのかというとコントローラーですね!
コントローラーは上記のようになっていました。viewでは@tweetsなのに、indexアクション内では@tweetと書いてしまっています。これが異なるのが原因だったわけですね!これを@tweetsに修正してあげると、viewの@tweetsにしっかり投稿一覧が入ってきてeachメソッドが利用できるようになります。
このように
undefined method系は、一般的には本来存在するはずの何かが無い(定義されていない)ことを意味する場合が多いです。
以上を踏まえてもう一つよくあるundefined method系のエラーを紐解いていきましょう。ここまで来れば、以下のエラー文を正確に読解できるようになっているはずです。
「already_liked?メソッドは無に対しては定義されていませんよ」ということですね!
何が無になっているのかというと、current_userが無になってしまっています。ここまでは英語を読むだけなのでRailsを知らない人でもなんとかついてこれます。
しかし、ここでcurrent_userとは何か知っているのと知らないのとでは、今後の解決に向けて大きな差が生じてしまいます。
構造や因果関係の理解がボトルネックにあるわけです。current_userは英語の意味的に覚えやすいですが、今ログインしているユーザーのidを取得するコードでしたね。ということは「もしかしてログインせずにページを見てしまっているのでは?」のように推測できるわけです。そこで、確実にログインした上でページを開くと正常に見れました。
めでたしめでたし、、、、
というわけにはいきません。
その場でログインして胡麻化しただけで一般ユーザーにとっては上記と同様のエラーに遭遇してページを利用できないということが起きかねませんね!根本的な解決策を講じる必要があります。次の視点はややプログラミングに慣れないとなかなか発想しにくいですが、このように考えます。「ログインユーザーとそうでないユーザーで表示を変えれば良いのでは?」
これを実装すると以下のようになります。
<% if user_signed_in? %>
<% if current_user.already_liked?(@tweet) %>
<%= link_to tweet_like_path(@tweet), method: :delete do %>
<i class="fas fa-heart"></i><%= @tweet.likes.count %>
<% end %>
<% else %>
<%= link_to tweet_likes_path(@tweet), method: :post do %>
<i class="far fa-heart"></i><%= @tweet.likes.count %>
<% end %>
<% end %>
<% else %>
<i class="far fa-heart"></i><%= @tweet.likes.count %>
<% end %>
if文が込み合っていてやや複雑ですが、ログインしているか、ログインユーザーはいいねをしているかの条件によって分岐しています。やや知識も絡んできますが、<% if user_signed_in? %>
はログインかどうかを判定してくれるメソッドです。deviseのgemが用意してくれています。上記のように書くことで、様々な条件によって実行するコードを分岐し、エラーを未然に防ぐことにつながるということですね。(createアクションのsaveできたかどうかで分岐するのも同様の話)
次にsyntaxエラーを見ていきましょう.
上記のエラー文はごちゃごちゃして見にくいですね。上記のようなエラー文はどのように見れば良いのか?なぜごちゃごちゃしているのでしょうか?後に説明します。
syntaxは「構文」という意味です。もともとは言語学の用語ですが、プログラミングの世界では、プログラミング言語にあらかじめ定められている文法ルールを指します。syntex errorとはこのプログラミング言語(今回の場合RubyやRails)の文法に違反していることを表しています。では上記の画像の場合何が原因なのでしょうか?画像全部の情報をよく見てみると、下の方に小さくunexpected end-of-input、expecting end
と書いてあるのが見えます。「予期せぬinputのend、endを期待している」やや難解ですね。これだけ見たところでピンとくることは少ないと思います。これは仕方ないですが、構造と因果関係の理解が深くかかわってきます。Rubyの文法ルールをある程度抑えておかないことにはどうにもならない問題です。そこでものすごく簡単にRubyの文法ルールを確認します。
理解しておくべきことは、ブロックは必ずendで閉じるというものです。ここでいうブロックとは以下に代表されるまとまりと考えて良いでしょう。
def index
~~
end
if a > b
~~
end
def create
~~
if tweet.save
~~
else
~~
end
end
上記のように必ず終点はendで閉じるというのがRubyのルールです。(他の言語には、これらを記述する際endを書かないものもあります)
もちろんHTML内に記述するRuby(<% ~ %>
)のif文等も必ずendで閉じる必要があります。(HTMLのdivタグ等も必ず</div>
で閉じるのと同様の話)
そこで、該当のHTMLファイル(show.html.erb)の中を調査し、endの忘れ物が無いかを探せばよいということになります。
これは頑張って探すしかありません。というのも、endが途中で一つ抜けていると、そこ以降のブロックのまとまりが一つずつ上方向にずれて対応してしまい、一番下の行にendが足りないというエラー出力にどうしてもなってしまうためです。そのためエラー文の出力がごちゃごちゃしていたのですね!「全体の構成が違くない?」というメッセージが隠れています。
頑張って探した結果、ついにendの忘れ物を見つけることができました。途中にendを追記して無事解決です。
めでたしめでたし、、、
このようなエラーに対応するかもしれない未来の自分のために、可読性の高いコードを書くことを覚えていきましょう!
以下2枚の画像を見比べてみてください。
明らかに下の画像の方が、見やすいですね!
それぞれのブロックを同じインデントに揃えることを当たり前にしていくだけで、他の開発者にとって読みやすくしたり未来の自分に役に立つはずです!
最近はインデント等を自動で整形してくれるlinterツールが色々あるので、積極的に活用してみてください。
No route matches ~、two routes with the same name
ルーティングとは何か?そしてどのように調べるか?さえ分かっていれば、全てのルーティングエラーは機械的に解決できます。
ルーティングに関してRailsガイドを見てみると以下のように記述されています。
https://railsguides.jp/routing.html
ざっくり言えば、URLを適切なコントローラー内アクションに割り当てるものです。これはconfig/routes.rbに書かれているのでしたね!
ルーティングエラーは十中八九このconfig/routes.rbを確認する必要があります。
また、ターミナル上でrails routes
すると、それぞれのルーティングがどのようなパス(URL)とコントローラー内アクションが対応しているかの一覧が表示されます.
ルーティングエラーの原因の大半は以下二つに分かれます。
1。必要なルーティングが記述されていない
2。ルーティングが重複している
ルーティングは重複があるとどちらを選択したら良いのかわからなくなるため、一意である必要があります。
また、config/routes.rbファイルの上から順番に読み込まれることにも注意すべき場合があります。
具体的なエラー画面を見ていきましょう。
1. 必要なルーティングが記述されていない
コメント機能をつけた後,投稿詳細ページを開いたところ、以下の画面が出てきました。
undefined method tweet_comments_path
なので、存在するはずのtweet_comments_pathのルーティングが存在していない?と推測します。
config/routes.rbとrails routesした結果が以下なので、自分で原因を見つけてみてください!
※tweet_comments_pathの記述に関して、~~_pathはrails routesした結果の一番左列(Prefix)が対応しています。
tweet_commentsは存在していないですね!
代わりにコメントの投稿時のルーティングは
comments POST /comments(.:format) comments#create
のように記述されています。
config/routes.rbをよく見てみると,
resources :tweets do
resources :likes, only: [:create, :destroy]
end
resources :comments, only: [:create]
のように記述されていて、Resources :tweetsがなにやらまとまりになっていてlikesが中に入っています。しかしcommentsは外側に記述されていますね。実はlikesやcommentsのルーティングは本来tweetsの中に入れ子にして記述されます。
確かにlikeが投稿されるルーティングを見てみると
tweet_likes POST /tweets/:tweet_id/likes(.:format) likes#create
のようにtweet_likes_path
になっていますね!
なので、以下のように記述してあげると、正しいルーティングが生成されます。
resources :tweets do
resources :likes, only: [:create, :destroy]
resources :comments, only: [:create]
end
とりあえずはそういうものだの認識でも大丈夫なのですが、なぜ入れ子で記述されているのでしょうか?
これにはリレーショナルデータベースの構造の理解が必要になってきます。
それぞれのtweetはたくさんのcommentsを持っていますね。またそれぞれのcommentは必ず一つのtweetに属しています。このような関係を1対多と言い、commentsテーブルにtweet_idカラムを設置してこれがtweetとcommentを紐づける外部キーとなります。つまり、commentが投稿されたとき、tweet_idの情報を取ってくる必要があるわけです。
tweet_idはどこにあるのかというと、URL中に存在します。
rails routes
からcommentのcreateアクションのルーティングを見てみると、以下のようになっています。
tweet_comments POST /tweets/:tweet_id/comments(.:format) comments#create
これの:tweet_idの箇所は実際には整数が入っていて,これこそ欲しいtweet_idの情報です。このtweet_idは投稿詳細ページ(tweets#show)におけるURL中の整数が由来になっています。
投稿詳細ページ(show)のURLは例えば以下です。
localhost:3000/tweets/5
このように、Railsはデータベースとルーティングの対応などは取り決めごと(規約)に則って開発を進めることになります。
2. ルーティングが重複している
rails s
しようとしたところターミナルに以下の画面が出てきました。
You may have defined two routes with the same name
英文にズバリ原因が書いてあります。そこで何が重複しているかをconfrg/routes.rbを見て探します。
以下から重複箇所を探し出してみてください。
見つけられましたか??
鍵になるのはResourcesの理解です。
Resourcesはindex、new、create、show、edit、update、destroy7つのアクションのルーティングを全て定義するものなので、これらのアクションに関するルーティングが他に記述されていれば重複になってしまいます。showアクションの
get 'tweets/:id' => 'tweets#show',as: 'tweet'
が余分でしたね!これを削除して正常に動かすことができました!
Pending Migration Error
rails s
してブラウザを開くと、以下の画面が生じるエラーです。
Migrations are pending。To resolve this issue、run: rails db:migrate...
このエラーはmigraion(データベース)の仕組みと上記の英文が読解できれば何も怖くはありません。
Railsにおけるデータベース(情報の保存場所)の構築はmigrationという仕組みで成り立っています。
migrationファイルにデータベースの設計図を記述し、これをrails db:migrate
コマンドにより適用させます。2段階あるわけですね!
上記の英文は「migrationがpending(保留)されています。これを解決するにはrails db:migrateコマンドを実行してください」という意味です。プログラミングの世界ではrun
はコマンドを実行するという意味を持ちます。pendingされているとは、migrationファイル中に適用されていないものがある(down)になっているものがあるという意味です。Railsの場合、これがあるとエラーが起きてしまう仕様になっているんですね!
これを確認するコマンドはrails db:migrate:status
でしたね!これを実行してみましょう。
一番下のmigrationファイルがdownになっていました。そこでこれを適用するためにrails db:migrate
コマンドを実行します。
※rails db:migrate RAILS_ENV=development
の後半部分は、リリースした後、開発環境でmigrationを実行しますというオプションなので、開発時点ではこれは記述しなくて大丈夫です。
上記のように一番下のmigrationファイルが無事適用されました。これにてエラー解決です。
しかしながら、migrationファイル中にミスがありrails db:migrate
コマンドが失敗するcaseがたまにあります。
そのようなときはターミナル中のエラー文を読み込み、該当のmigrationファイル中に原因を見つけてください。上記の場合はデータ型のstring
と記述すべきところにスペルミスがありました。
これを修正後、再度rails db:migrateコマンドで解決です!
他にもrails db:migrate
が失敗するcaseとして、重複する内容のものがあったり、親テーブルが無いのに外部キーを設置してしまったりがあります。過不足ないmigrationファイルを構成することが大切です。
※リリース時はこのmigrationファイルを基に本番サーバー上にデータベースを作成するため、ここにミスがあると大変です。
No template for interactive request
rails s
してブラウザを開くと以下のようなエラー画面が出てくる場合です。
上記の場合
UsersController#show is missing a template for request formats: text/html
は以下のように意訳されます。ぶっちゃけこのエラーは英文の意味だけで原因が即座に分かるものではなく、知識に依る側面が大きいです。
「Usersコントローラーのshowアクションに対応するhtmlファイルが無いよ!」という意味です。
このようなエラーはほぼすべてview中のしかるべき場所にhtml.erbファイルが無い、もしくはファイル名が異なることが原因です。
また、ルールとして例えばtweetsコントローラーのindexアクションのページは、view/tweets/index.html.erbのような階層構造で作成するというものがあります。必ずコントローラー名のフォルダー以下にhtml.erbファイルを作ります。
そこで上記のエラーに対応するファイルの構成を見ていきましょう。
上記に怪しいところは見つかりましたか??やや分かりづらいので今度はファイルの構成を別の視点から見ていきます。
Railsアプリの本体はフォルダーであり、ファイルの集まりなので上記のようにエクスプローラー(windows)やFinder(mac)から見ることもできます。実はVScodeはこれを開いていただけなんですね。見てみるとshow.html.erbはviewsフォルダーの真下に置いてしまっています。本来はusersの中に置く必要があるので、これを移動することで無事エラーを解決することができました!
※筆者は過去、階層構造やルーティング、コントローラー、ファイル名の全てが合っているのにこのエラーが出続ける奇妙なcaseに遭遇したことがあります。隈なく調べてみたところ、ファイル名の途中に半角スペースが入っていたことが原因でした...
おわりに
エラーはプログラミングの一部であり、それを解決することでスキルを向上させることができます。エラーメッセージを読み解く力、原因を推測する力、そして解決策を見つけ出す力は、日々の開発作業を通じて自然と身につけていきます。
この記事では、Rails開発における一般的なエラーとその解決策をいくつか紹介しました。しかし、これらはあくまで一例であり、実際の開発ではさまざまなエラーに遭遇することでしょう。そのたびにエラーメッセージを読み、原因を推測し、解決策を探すことで、あなたのプログラミングスキルは確実に向上していきます。
また、エラー解決にはGoogleやChatGPTなどのツールも大いに役立ちます。他の開発者が遭遇した同じエラーとその解決策を共有している記事やフォーラムも多く存在します。これらを活用することで、自分一人で解決策を見つけ出す時間を大幅に短縮することができます。
最後に、エラー解決は一人で抱え込まず、周囲の人々に助けを求めることも大切です。質問する力もまた、エンジニアとしての重要なスキルの一つです。
これからもエラーとの戦いを楽しみながら、スキルアップを続けていきましょう。
(おわりに、の章はChatGPTが書きました)