20190816のRailsに関する記事は20件です。

rails_helper.rb内でrake taskを実行する方法

環境

  • Rails 5.2.3

やり方

  • その一: 特定のファイルを読み込んで、それを実行する。
spec/rails_helper.rb
require 'rake'

load Rails.root.join('lib', 'tasks', 'foo.rake') # 実行したいrakeファイル
Rake::Task.define_task(:environment)
Rake::Task['foo:bar:buz'].invoke
  • その二: 全てのrakeタスクを読み込んで、特定のタスクを実行する。
spec/rails_helper.rb
require 'rake'

Rails.application.load_tasks
Rake::Task['foo:bar:buz'].invoke
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

現役ポールダンサー初めてのwebサービスをRuby on railsで作りました

qiita初投稿です!
私の本業はポールダンス、エアルリアルダンスをメインとするパフォーマンス業です。(時々インストラクター的なこともやってます)
そんな私がこの度Rubyonrailsでwebサービスを初めて作って公開しました。
なぜダンサーがプログラミングを?わざわざ、、、?などなどはこちらのブログに書いてますが、最初は単純にキャリアチェンジを考えていたところからプログラミング学習は始まりました。そこからやはり自分でサービスを作ってみよう!ということになって作ったサービスがこちらです。
https://poletricks-world.herokuapp.com/
デザインや機能共にまだまだ修正すべきところはありますし、目新しい機能というものはありませんが
機能、デザイン、エラーで困ったところなどを綴ります。
スクリーンショット 2019-07-14 19.23.09.png

アイディア

練習するときに見ることができるアプリがあればいいな(インスタだと、検索が面倒、技で検索できない、達成度が感じれられない、動画止められない)
・動画が止められて
・達成感が味わえて
・わかりやすい動画
・且つ無料
私が練習を始めた頃に欲しいなと思った機能です。これらを網羅したアプリを作りたかったのです。
プロトタイプを作ることを始めました。

開発環境

Rails 5.2
ruby 2.4.0
cloud9(途中からAWSのcloud9に移行)

プロトタイプを作る

プロトタイプは結構大まかでもいいのかなーと思っていましたが、私のメンターさんはできるだけ細かくデザインして、その機能をあとは乗せていくだけの方がいいよ、とのことで
イラレを使って作成していきました。
ちなみにイラストレーターを使ったのは今年のお正月が初めてです。ここは独学でアドビのイラストレーター初心者の動画をひたすら見ながら練習したり、ググったりして覚えました。すごいたくさんのことは全然できませんが
プロトタイプを作るくらいまでならサクサクできるように1ヶ月ほどでなりました。

大まかなページ展開

・トップページ
・サインイン、ログインページ
・動画一覧ページ
・動画詳細ページ
・ブログページ
サインイン、のところはgemのdeviseを使いました。
動画は最初、AWSを使う予定でしたが、こんがらがるのでまずはYoutubeにアップロードして動画リンクを入れていくことにしました。動画一覧にはyoutubeのサムネイル表示がされるようにしています。

Topページ

ナビゲーションバーのなかにリンク(ブログとコンタクト)、ログインログアウト、サインアップページ
ヘッダー画像とアカウント作成ボタン
動画例
使い方
アバウト
プロフィール
フッター(リンク、SNS)という構成です。

動画一覧ページ

ユーザーができた技数が表示される(マスターできた数/存在する技数 の計算式)
動画リンク(サムネイル+マスターボタン+カテゴリーラベル+動画リンク)
ここの動画は全てadminの私がnewで追加していくように実装しました。(newにアクセスすれば動画を自分で足すことも可能だけど今の所私のみ、動画をアップできるようにする)
スクリーンショット 2019-07-14 19.26.22.png
ブックマークの色が変わるところはAjax
検索機能はgemのransackを使いました。

動画詳細ページ

動画のyoutube
コメント欄(削除可能)←技のポイントなどを書き込めたら面白いなあと思い。
関連動画表示(ここはマスターボタンなどは乗せないで、カテゴリーで紐づかせて表示されるようにしました)
動画自体はループで表示されるようにした方がいいか、迷いどころなのでテストユーザーのフィードバックで都度変更していこうと思います。

Bookmarkページ

スクリーンショット 2019-08-16 20.13.55.png

ここはかなり時間がかかりました。動画一覧ページでブックマークしたものをユーザーごとのマイページとしてブックマークされるようにしました。masterボタンをキャンセルすると動画一覧ページも色がかわります。
同じくajaxを使い、masterした技のボタンがmaster→cancelと変更される様にしました。

主につまづいたところ

つまづいたことが多すぎてもうわからないくらいですが

デザインがうまくいかない(現時点でもまだ修正中!!)

Bootstrapを使ってますが、使ったら使ったで自由度がない部分もあり、折り合いが難しいです。
ヘッダーの画像は最初イラレのみで私の写真を切りはりしたのですが、汚く見えてしまったので
Photosopで加工→イラレでサイズ、バックの修正などを行いました。多分もっといい方法はあると思いますが現時点ではこれが一番でした。

herokuデプロイできない問題

テックアカデミーをやっていた時に使ったことがあるので余裕や!と思っていたけれど、
ふつーにエラーでなかなかデプロイできませんでした。
そもそもですが、別のブランチをmasterブランチにマージしていなかったため、まずはそこがエラーが出ていたようです。
とりあえずメンターに相談したらherokuの設定をする順番が間違っていたみたい。

Herokuでよくエラーが出た時の解決方法

何度かテストユーザーさんに使ってもらう上で、cloud9上で問題なく表示され、動作できたのでherokuにアップするとよく
We're sorry, but something went wrong.
表示が出ていました。
大体が以下のことで解決です。
・git commit してないのでする
・heroku db:migrationする
・heroku restartする
でほぼほぼ解決しました、一回表示するべきコードが消えていた時があって、その時はコードを付け足し直してエラーは解決しました。

AWSのcloud9

途中から以前のcloud9からAWSのcloud9に移行しました。
移行自体はスムーズに行ったのですが、、、
ある日急に容量がいっぱいであるというエラーが出て、ターミナルが使えなくなりました。
スクリーンショット 2019-08-16 20.06.05.png

githubにコードはギリギリまであげていたので、再度git cloneして新たなワークスペースで作成。
そしてなんとかまたできるようになったもののまたある程度コードを書くと警告が出て、
またもやターミナルが使えなくなり、サポートに問い合わせるも具体的なことはわかりかねるよでした。
次にAWSでもう一度ワークスペースを作る際、変更したのはEC2インスタンスです。(ここがキモでした)今までmicroを使っていたのですがこれをsmallに変更
サーバーはubuntuです。
これで無事AWSに新たなワークスペースが作ることができました。
そのあと、AWSのアクセスキー、シークレットキーを登録しエラーと格闘しながら無事にまたheroku上で公開する事ができました。

テストユーザーに利用してもらう

わたしの周りはインストラクターやショーにすでに出ている人たちだったのでポールダンスを習ってそこまで間もない生徒さんが必要でした。
友人の経営しているスタジオの生徒さんに協力してもらいフィードバックをいただくことになりました。
特に自分で作ってると裏側がわかるので導線などは、当たり前にここからログインするよね?
探しにいくよね?という前提で作っていたので、
動画ページに移動の方法に迷った、削除ボタンの位置がわかりにくい、戻り方がわからない、、
などなど導線をわかりやすくする工夫が必要になりました。
あとは重要なチェックボタンを押したあと、取り消しボタンがないこと、取り消しボタンがないため、同じボタンを押すと、Master数がどんどん同じ技で計上されてしまうようになっていたのでそこを修正する必要がありました。

まとめ

細かいエラーなどはまた別途ブログで書いていこうと思いますが
6か月弱かけてなんとかここまで作る事ができました。
プロの方からするとはっきり言ってかなり単純なwebサービスとなっているように思われるかもしれませんが、個人的にはかなり頑張りました。

自分で初めてプロダクトを作って見て、何度も投げ出しそうになったけど(というかまだデザイン終わってない)形にできて本当によかったし、もっと色々手を出して見たくなりました。
引き続き改良を試みつつ、技術のアップデートしたいです!
https://poletricks-world.herokuapp.com/
※2019年8月現在、エラー復旧中にAWSのcloud9のターミナルが使えなくなり、諸々修正が不可になりgit cloneするもバージョンコンフリクトなどで解消せず、、ローカルでぽちぽち作り直してます。一難去ってまた一難。。とりあえずこのurlとは別で新たにappを作っていこうと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails ActiveRecordの結合時のクエリの内容について

参考

以下のページを参考にさせていただきました。ありがとうございます。
ActiveRecordのjoinsとpreloadとincludesとeager_loadの違い
【Rails】テーブル結合

データ構造

companiesテーブル

id name
1 A社
2 B社

servicesテーブル

id company_id name
1 1 Aアプリ1
2 1 Aアプリ2
3 1 Aアプリ3
4 2 Bの野望

パターン1 all(pluckなし)

companies = Company.all
companies.each do |company|
    company.services
end
SELECT `companies`.* FROM `companies`
SELECT `services`.* FROM `services` WHERE `services`.`company_id` IN (1,2)

パターン2 : all

companies = Company.all
companies.each do |company|
    model.services.pluck(:name).join(',')
end
SELECT `companies`.* FROM `companies`
SELECT `services`.`name` FROM `services` WHERE `services`.`company_id` = 1
SELECT `services`.`name` FROM `services` WHERE `services`.`company_id` = 2

パターン3 : join

companies = Company.joins(:services)
companies.each do |company|
    model.services.pluck(:name).join(',')
end
SELECT `companies`.* FROM `companies` INNER JOIN `services` ON `services`.`company_id` = `companies`.`id`
SELECT `services`.`name` FROM `services` WHERE `services`.`company_id` = 1
SELECT `services`.`name` FROM `services` WHERE `services`.`company_id` = 1
SELECT `services`.`name` FROM `services` WHERE `services`.`company_id` = 1
SELECT `services`.`name` FROM `services` WHERE `services`.`company_id` = 2

パターン4 : preload

companies = Company.preload(:services)
companies.each do |company|
    model.services.pluck(:name).join(',')
end
SELECT `companies`.* FROM `companies`
SELECT `services`.* FROM `services` WHERE `services`.`company_id` IN (1,2)

パターン5 : includes

companies = Company.includes(:services)
companies.each do |company|
    model.services.pluck(:name).join(',')
end
SELECT `companies`.* FROM `companies`
SELECT `services`.* FROM `services` WHERE `services`.`company_id` IN (1,2)

パターン6 : eager_load

companies = Company.includes(:services)
companies.each do |company|
    model.services.pluck(:name).join(',')
end
SELECT **省略** FROM `companies` LEFT OUTER JOIN `services` ON `services`.`company_id` = `companies`.`id`
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby on Railsでプロジェクト管理WEBアプリを開発

はじめに

UdemyでRuby on Railsのコースを学習したので、そこで学んだ知識を生かしてWEBアプリを開発しました。

ソースコード

GitHubで公開しています
https://github.com/Ryota7101/milestone.git

主な機能 

  • miliaによるマルチテナント機能
  • タスク機能
  • タスクへのカテゴリ付け
  • ファイルアップロード機能
  • プレミアム会員機能
  • 管理者機能

アプリ詳細

 サインアップ

スクリーンショット 2019-08-16 15.52.29.png

サインアップ時にフリープランかプレミアムプランかを選択できます。
フリープランの場合はカード情報入力欄が表示されません。
スクリーンショット 2019-08-16 15.56.47.png

プレミアムプランに切り替えると、クレジットカード情報入力欄が表示されます。
※情報を登録して処理する機能を設定してないので、ここで入力した情報は保存されません。
スクリーンショット 2019-08-16 16.00.33.png

プロジェクト作成

スクリーンショット 2019-08-16 16.02.46.png

プロジェクトを作成すると、プロジェクト一覧に表示されます。
スクリーンショット 2019-08-16 16.04.00.png

プロジェクト詳細ページ
スクリーンショット 2019-08-16 16.04.59.png

ファイルアップロード機能

テキストファイルや画像をアップできます。
※ただし、クラウドなど保存先を設定していないので、現状ではどこにもファイルは保存されません。
スクリーンショット 2019-08-16 16.09.37.png

プロジェクトページにアップしたファイルが表示されます。
スクリーンショット 2019-08-16 16.10.12.png

タスク

タスク作成時にカテゴリ(後述)も指定できます。
スクリーンショット 2019-08-16 16.15.46.png

スクリーンショット 2019-08-16 16.16.22.png

作成したタスクがプロジェクトページに表示されます。
スクリーンショット 2019-08-16 16.16.51.png

管理者機能

Organization作成者は自動的に管理者として登録されます。
管理者がログインしてる場合は、ナビゲーションバーに
* メンバー追加
* プラン変更
* タスクのカテゴリ管理
のボタンが表示されます。

スクリーンショット 2019-08-16 16.17.30.png

タスクのカテゴリ

タスクのカテゴリ管理(作成や削除)は管理者のみが行えます。
タスクを作成したり、タスクにカテゴリを設定するのは非管理者でも行えます。

スクリーンショット 2019-08-16 16.18.17.png

カテゴリを作成すると、カテゴリ一覧ページに表示されます。
使用する場合は、タスク作成時にチェックを入れます。
スクリーンショット 2019-08-16 16.18.44.png

プラン変更

プランを途中で変更できます
スクリーンショット 2019-08-16 16.19.46.png

メンバーを招待する

管理者はメンバーをプロジェクトに招待できます。
招待したい人の氏名やメールアドレスを入力すると、リンク付きのメールが相手に送信されます。
スクリーンショット 2019-08-16 16.21.21.png

届いたメールのリンクをクリックすると、パスワード設定画面に移動するので、パスワードを入力します
スクリーンショット 2019-08-16 16.21.57.png

管理者がプロジェクトページを見ると、招待した相手の情報が表示されるので、Addボタンを押すと招待が完了します。
スクリーンショット 2019-08-16 16.26.27.png

招待が完了すると、メンバー欄にメンバーの情報が表示されます。
スクリーンショット 2019-08-16 16.27.01.png

招待された相手がログインすると、管理者が作成したプロジェクトやファイル、タスクなどが表示されます。
※招待されたメンバーは管理者でないので、ナビゲーションバーにカテゴリ管理などのボタンは表示されません。
スクリーンショット 2019-08-16 16.28.00.png

スクリーンショット 2019-08-16 16.29.23.png

別の組織を作成

試しに別の組織でサインアップして、これまで作成した情報が表示されないことを確認します。
スクリーンショット 2019-08-16 16.30.59.png

これまでに作成した情報が表示されず、初期画面となっているため、組織ごとにデータが分かれてることがわかります。
スクリーンショット 2019-08-16 16.32.01.png

終わりに

  • Udemyのコースの内容が2015年ごろのもので、その通りにコードを描いても動かないことが多く、苦戦しました。(コースではRails4、著者は5で作成)

  • 相変わらずコード書いて実際にweb上で動かして確認して・・とやってしまい、テストはおざなりになってしまった。。

  • 実戦でRails使いたいです

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby pn Railsでプロジェクト管理WEBアプリを開発

はじめに

UdemyでRuby on Railsのコースを学習したので、そこで学んだ知識を生かしてWEBアプリを開発しました。

ソースコード

GitHubで公開しています
https://github.com/Ryota7101/milestone.git

主な機能 

  • miliaによるマルチテナント機能
  • タスク機能
  • タスクへのカテゴリ付け
  • ファイルアップロード機能
  • プレミアム会員機能
  • 管理者機能

アプリ詳細

 サインアップ

スクリーンショット 2019-08-16 15.52.29.png

サインアップ時にフリープランかプレミアムプランかを選択できます。
フリープランの場合はカード情報入力欄が表示されません。
スクリーンショット 2019-08-16 15.56.47.png

プレミアムプランに切り替えると、クレジットカード情報入力欄が表示されます。
※情報を登録して処理する機能を設定してないので、ここで入力した情報は保存されません。
スクリーンショット 2019-08-16 16.00.33.png

プロジェクト作成

スクリーンショット 2019-08-16 16.02.46.png

プロジェクトを作成すると、プロジェクト一覧に表示されます。
スクリーンショット 2019-08-16 16.04.00.png

プロジェクト詳細ページ
スクリーンショット 2019-08-16 16.04.59.png

ファイルアップロード機能

テキストファイルや画像をアップできます。
※ただし、クラウドなど保存先を設定していないので、現状ではどこにもファイルは保存されません。
スクリーンショット 2019-08-16 16.09.37.png

プロジェクトページにアップしたファイルが表示されます。
スクリーンショット 2019-08-16 16.10.12.png

タスク

タスク作成時にカテゴリ(後述)も指定できます。
スクリーンショット 2019-08-16 16.15.46.png

スクリーンショット 2019-08-16 16.16.22.png

作成したタスクがプロジェクトページに表示されます。
スクリーンショット 2019-08-16 16.16.51.png

管理者機能

Organization作成者は自動的に管理者として登録されます。
管理者がログインしてる場合は、ナビゲーションバーに
* メンバー追加
* プラン変更
* タスクのカテゴリ管理
のボタンが表示されます。

スクリーンショット 2019-08-16 16.17.30.png

タスクのカテゴリ

タスクのカテゴリ管理(作成や削除)は管理者のみが行えます。
タスクを作成したり、タスクにカテゴリを設定するのは非管理者でも行えます。

スクリーンショット 2019-08-16 16.18.17.png

カテゴリを作成すると、カテゴリ一覧ページに表示されます。
使用する場合は、タスク作成時にチェックを入れます。
スクリーンショット 2019-08-16 16.18.44.png

プラン変更

プランを途中で変更できます
スクリーンショット 2019-08-16 16.19.46.png

メンバーを招待する

管理者はメンバーをプロジェクトに招待できます。
招待したい人の氏名やメールアドレスを入力すると、リンク付きのメールが相手に送信されます。
スクリーンショット 2019-08-16 16.21.21.png

届いたメールのリンクをクリックすると、パスワード設定画面に移動するので、パスワードを入力します
スクリーンショット 2019-08-16 16.21.57.png

管理者がプロジェクトページを見ると、招待した相手の情報が表示されるので、Addボタンを押すと招待が完了します。
スクリーンショット 2019-08-16 16.26.27.png

招待が完了すると、メンバー欄にメンバーの情報が表示されます。
スクリーンショット 2019-08-16 16.27.01.png

招待された相手がログインすると、管理者が作成したプロジェクトやファイル、タスクなどが表示されます。
※招待されたメンバーは管理者でないので、ナビゲーションバーにカテゴリ管理などのボタンは表示されません。
スクリーンショット 2019-08-16 16.28.00.png

スクリーンショット 2019-08-16 16.29.23.png

別の組織を作成

試しに別の組織でサインアップして、これまで作成した情報が表示されないことを確認します。
スクリーンショット 2019-08-16 16.30.59.png

これまでに作成した情報が表示されず、初期画面となっているため、組織ごとにデータが分かれてることがわかります。
スクリーンショット 2019-08-16 16.32.01.png

終わりに

  • Udemyのコースの内容が2015年ごろのもので、その通りにコードを描いても動かないことが多く、苦戦しました。(コースではRails4、著者は5で作成)

  • 相変わらずコード書いて実際にweb上で動かして確認して・・とやってしまい、テストはおざなりになってしまった。。

  • 実戦でRails使いたいです

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS】開発環境では動くが本番だと�動かない事例集

Qiita初投稿になります。
何か不備や誤りがあったらご指摘をいただけるととてもありがたいです!

経緯

スクールでの課題や個人アプリの開発でAWSを使っており、デプロイも双方担当をさせていただいておりました。
その中で、苦戦をした大きな比率を占めるのが、開発環境だと動くのに何故か本番環境だと動かない事例です。
本番で動かないと実際のサービスだと何も意味がないにも関わらず、このパターンが結構ありましたので、これから対処をする方がどういう方法や考え方で解いていけばいいのかをまとめます。
デプロイで苦戦をすると本当にイライラしてしまうので、イライラを少しでも沈めるのに貢献をできたらなと思います。

開発環境(個人アプリもスクールの課題も同一です)
言語:Ruby 2.5.1
フレームワーク:Rails 5.2.3
データベース:MySQL5.6
ライブラリ:JQuery
自動デプロイ:capistrano
Webサーバー(本番):nginx
APサーバー(本番):unicorn
Web・APサーバー(開発):puma
今回は双方ともにEC2、S3を用いております。

前提とエラーの基本の見方

EC2インスタンスの再起動が1番今まで救われたケースが多いです。まずは試してみましょう。(MySQLとnginxの再起動も必要です)

そして、エラーを解決をする基本はエラーログを見て忠実に動くことです。これは本番環境でも開発環境でも一緒です。
エラーが出る場合、開発環境だとターミナルやビューに出てきます。ただ、本番環境だと出てこないため、本番環境だと別でコマンドで見ていきます。
エラーログの本番環境の場所はlinuxコマンドでログインをして確認をします。注意をするのは自動デプロイの前後でエラーが吐き出される場所が異なることです。自動デプロイをしたにも関わらず元のままの位置でファイルを開いても意味がないです。

#手動デプロイ後
/var/www/アプリ名/log/production.log
#capistranoでの自動デプロイ後
/var/www/アプリ名/current/log/production.log

logにまで移動をしたらcat等のコマンドでエラーログを見ることができます。

①pumaとの性能の違いで、拡張子がついていなかった時

ActionView::Template::Error (The asset "bannar_image" is not present in the asset pipeline.):
※本番環境のエラーログです。
これが一番はじめのエラーでした。こちらに関しては上記のエラー文を見ると、bannar_imageが間違っているのが何となく分かると思います。
このbannar_imageという記述があったのが、image_tagになるので、image_tagに関して調べてみました。

index.erb
#エラーが起きたコードのイメージ
<%= image_tag 'bannar_image' %>
#他のサイトで見た見本のコード
<%= image_tag 'flower.png' %>

実際にビューファイルを比べてみると何か違和感が、、、上のコードは実は拡張子がなかったのです。
そのため、本番環境では動かなかったのです。

ちなみにこの時はそれまで自動デプロイが初めてできた瞬間に出たエラーだったため、自動デプロイのcapistranoのエラーだと勘違いをしてしまっておりました。
自動デプロイのエラーばかり見ていたので、何もエラーログがないのに原因不明のエラーが起こっていると勘違いして、だいぶハマりました。
デプロイがどういう流れになっていてどういう仕組みになっているのかを理解をしていないとこういうところで非常に苦戦をします。

というかpumaは何故拡張子がないのに動くのか、、、

②application.jsにJQueryが2つあった時

こちらはそこまで難しくないエラーです。お互いに干渉してしまうため、エラーになるようです。
今出せないのですが、エラーログも出ていて、そんなに複雑なエラーログではありませんでした。

application.js
=require rails-ujs
=require jquery-ujs

下のコードはヴァージョンが古い時に使っていたようでして、Rails5.1以降は上のコードを使うみたいです。
これはスクールの発表で先輩グループの発表の際に触れていたので、すぐに分かりました。
①もだけどそもそもpumaの時点でエラー起きてくれないのかな。。。

③データベース関連で開発環境とmigrationの状況が異なってしまった時

ActionView::Template::Error (Unknown database 'freemarket_sample_54c_production'):

チーム開発でmearge済みのmigrationファイルを変更をしてその後に再度meargeをしたり、
rake db:reset等を繰り返しているといつか起きてしまうかと思います。
私たちのチームではどうしようもなくなってしまい、結局一から本番環境のデータベースを構築をし直したのですが、これは本来あまりやっていいことではないかと思っています。
もちろん開発の初期段階ですので正直困りはしなかったのですが、実際のサービスだと既に重要な顧客データが多数入っているためです。
こちらは他に何かやり方あれば教えていただけると非常に嬉しいです。

ちなみにcapistrano導入後はデータベースをcreateしたり、dropをさせるときもエラーを見るのと同じくcurrent上でコマンドは打ちます。
rake:db:seedも同様で、capistrano導入後はディレクトリの位置が変更をされるためです。

④credentials.yml関連

ActiveSupport::MessageEncryptor::InvalidMessage

このエラーは序盤結構多かったです。こちらに関しては他に詳しい記事も多数あるかと思いますので割愛します。
credentials.ymlに関しては最初は結構苦戦をしましたが、慣れるとAPIkeyの管理がすごく簡単でした。
怖がるものではないかと思います。

⑤データベース内に数値が入っていなかった時

ActionView::Template::Error (undefined method `id' for nil:NilClass):

すごく単純なようで起きがちなエラーです。
特にテーブルを増やしてカラムに新しく外部キーを増やすと起こりがちです。
私はデータベースをSQLで直接いじっていましたが、Sequelpro等で本番でも見れるようにすることができるので、入れたら楽に設定をできると思います。
※ベーシック認証をしてからだと思うのですが、途中からSequelproは使えなくなりました。おそらく弾かれてしまったのだと思います。もしかするとベーシック認証を使っていてもできるのかもしれませんが、私は調べきれませんでした。

⑥JQueryで発火をしなかった※データが入っていない時

エラーログは出ません。
インクリメンタルサーチをしようとしたのですが、本番環境のデータで名前を検索をする際に、そもそも該当の名前のデータが入っていませんでした。。。

これは完全に単なる勘違いです。即解決できましたが、全くエラーログが出てこないので原因が不明になるので少しの時間、不安になりました。
これも上記の⑤と一緒でSequelproとか入れたらわかりやすそうです。

まとめ

基本はエラーログをしっかりと見ていけば何とかなりました。ただ、データベース関連は視覚化ができていなかったので、苦労をしました。
これに関してはエラーログの構造等を理解しだしてから、格段に理解が深まったので、細かいWebアプリの仕組みに関しては理解をしといたほうがいいなと感じました。
今回挙げた事例に関しては正直かなり簡単なほうだと思っています。これから先も出てくる事例は多いと思うので、また何かあれば記事に挙げていきます。

参考記事

https://www.javadrive.jp/rails/template/index11.html
https://www.bokukoko.info/entry/2017/10/27/231129
https://qiita.com/scivola/items/cc06ddbfd94d3118f693

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS】開発環境では動くが本番だと動かない事例集

Qiita初投稿になります。
何か不備や誤りがあったらご指摘をいただけるととてもありがたいです!

経緯

スクールでの課題や個人アプリの開発でAWSを使っており、デプロイも双方担当をさせていただいておりました。
その中で、苦戦をした大きな比率を占めるのが、開発環境だと動くのに何故か本番環境だと動かない事例です。
本番で動かないと実際のサービスだと何も意味がないにも関わらず、このパターンが結構ありましたので、これから対処をする方がどういう方法や考え方で解いていけばいいのかをまとめます。
デプロイで苦戦をすると本当にイライラしてしまうので、イライラを少しでも沈めるのに貢献をできたらなと思います。

開発環境(個人アプリもスクールの課題も同一です)
言語:Ruby 2.5.1
フレームワーク:Rails 5.2.3
データベース:MySQL5.6
ライブラリ:JQuery
自動デプロイ:capistrano
Webサーバー(本番):nginx
APサーバー(本番):unicorn
Web・APサーバー(開発):puma
今回は双方ともにEC2、S3を用いております。

前提とエラーの基本の見方

EC2インスタンスの再起動が1番今まで救われたケースが多いです。まずは試してみましょう。(MySQLとnginxの再起動も必要です)

そして、エラーを解決をする基本はエラーログを見て忠実に動くことです。これは本番環境でも開発環境でも一緒です。
エラーが出る場合、開発環境だとターミナルやビューに出てきます。ただ、本番環境だと出てこないため、本番環境だと別でコマンドで見ていきます。
エラーログの本番環境の場所はlinuxコマンドでログインをして確認をします。注意をするのは自動デプロイの前後でエラーが吐き出される場所が異なることです。自動デプロイをしたにも関わらず元のままの位置でファイルを開いても意味がないです。

#手動デプロイ後
/var/www/アプリ名/log/production.log
#capistranoでの自動デプロイ後
/var/www/アプリ名/current/log/production.log

logにまで移動をしたらcat等のコマンドでエラーログを見ることができます。

①pumaとの性能の違いで、拡張子がついていなかった時

ActionView::Template::Error (The asset "bannar_image" is not present in the asset pipeline.):
※本番環境のエラーログです。
これが一番はじめのエラーでした。こちらに関しては上記のエラー文を見ると、bannar_imageが間違っているのが何となく分かると思います。
このbannar_imageという記述があったのが、image_tagになるので、image_tagに関して調べてみました。

index.erb
#エラーが起きたコードのイメージ
<%= image_tag 'bannar_image' %>
#他のサイトで見た見本のコード
<%= image_tag 'flower.png' %>

実際にビューファイルを比べてみると何か違和感が、、、上のコードは実は拡張子がなかったのです。
そのため、本番環境では動かなかったのです。

ちなみにこの時はそれまで自動デプロイが初めてできた瞬間に出たエラーだったため、自動デプロイのcapistranoのエラーだと勘違いをしてしまっておりました。
自動デプロイのエラーばかり見ていたので、何もエラーログがないのに原因不明のエラーが起こっていると勘違いして、だいぶハマりました。
デプロイがどういう流れになっていてどういう仕組みになっているのかを理解をしていないとこういうところで非常に苦戦をします。

というかpumaは何故拡張子がないのに動くのか、、、

②application.jsにJQueryが2つあった時

こちらはそこまで難しくないエラーです。お互いに干渉してしまうため、エラーになるようです。
今出せないのですが、エラーログも出ていて、そんなに複雑なエラーログではありませんでした。

application.js
=require rails-ujs
=require jquery-ujs

下のコードはヴァージョンが古い時に使っていたようでして、Rails5.1以降は上のコードを使うみたいです。
これはスクールの発表で先輩グループの発表の際に触れていたので、すぐに分かりました。
①もだけどそもそもpumaの時点でエラー起きてくれないのかな。。。

③データベース関連で開発環境とmigrationの状況が異なってしまった時

ActionView::Template::Error (Unknown database 'freemarket_sample_54c_production'):

チーム開発でmearge済みのmigrationファイルを変更をしてその後に再度meargeをしたり、
rake db:reset等を繰り返しているといつか起きてしまうかと思います。
私たちのチームではどうしようもなくなってしまい、結局一から本番環境のデータベースを構築をし直したのですが、これは本来あまりやっていいことではないかと思っています。
もちろん開発の初期段階ですので正直困りはしなかったのですが、実際のサービスだと既に重要な顧客データが多数入っているためです。
こちらは他に何かやり方あれば教えていただけると非常に嬉しいです。

ちなみにcapistrano導入後はデータベースをcreateしたり、dropをさせるときもエラーを見るのと同じくcurrent上でコマンドは打ちます。
rake:db:seedも同様で、capistrano導入後はディレクトリの位置が変更をされるためです。

④credentials.yml関連

ActiveSupport::MessageEncryptor::InvalidMessage

このエラーは序盤結構多かったです。こちらに関しては他に詳しい記事も多数あるかと思いますので割愛します。
credentials.ymlに関しては最初は結構苦戦をしましたが、慣れるとAPIkeyの管理がすごく簡単でした。
怖がるものではないかと思います。

⑤データベース内に数値が入っていなかった時

ActionView::Template::Error (undefined method `id' for nil:NilClass):

すごく単純なようで起きがちなエラーです。
特にテーブルを増やしてカラムに新しく外部キーを増やすと起こりがちです。
私はデータベースをSQLで直接いじっていましたが、Sequelpro等で本番でも見れるようにすることができるので、入れたら楽に設定をできると思います。
※ベーシック認証をしてからだと思うのですが、途中からSequelproは使えなくなりました。おそらく弾かれてしまったのだと思います。もしかするとベーシック認証を使っていてもできるのかもしれませんが、私は調べきれませんでした。

⑥JQueryで発火をしなかった※データが入っていない時

エラーログは出ません。
インクリメンタルサーチをしようとしたのですが、本番環境のデータで名前を検索をする際に、そもそも該当の名前のデータが入っていませんでした。。。

これは完全に単なる勘違いです。即解決できましたが、全くエラーログが出てこないので原因が不明になるので少しの時間、不安になりました。
これも上記の⑤と一緒でSequelproとか入れたらわかりやすそうです。

まとめ

基本はエラーログをしっかりと見ていけば何とかなりました。ただ、データベース関連は視覚化ができていなかったので、苦労をしました。
これに関してはエラーログの構造等を理解しだしてから、格段に理解が深まったので、細かいWebアプリの仕組みに関しては理解をしといたほうがいいなと感じました。
今回挙げた事例に関しては正直かなり簡単なほうだと思っています。これから先も出てくる事例は多いと思うので、また何かあれば記事に挙げていきます。

参考記事

https://www.javadrive.jp/rails/template/index11.html
https://www.bokukoko.info/entry/2017/10/27/231129
https://qiita.com/scivola/items/cc06ddbfd94d3118f693

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker環境でHeadless Chromeを使ったSystemt Specを実行する

この記事なんやねん

みなさん、Rspecで統合テスト(System Spec)書いてますか?

すっかりSeleniumを使ってヘッドレスブラウザを使った統合テストが主流になりましたが、DockerHeadless Chromeを使ったやり方が意外とヒットしなかったので投稿します。

手順

ここから早速始めていきましょう。因みに、以下の環境は揃っているという前提で進めます。

  • Dockerとdocker-composeをインストール済み
  • docker-composeを使ってRails環境を構築済み
  • Rspec実行環境を構築済み

もし、 環境構築まだやねん って状態だったら、こちらの記事を参考に開発環境を作ってみてください。

イメージの準備

Dockerfileにheadless chromeドライバをインストールしてもいいのですが、正直めんどくさいです。
brewで簡単にインストールできるのにDocker使った方が環境構築めんどくさいのも本末転倒な感じするので、Dockerhubにイメージ上がってないか検索したら案の定ありました

https://hub.docker.com/r/selenium/standalone-chrome

seleniumがイメージを提供してくれてるようですね。因みに、googleはイメージ提供していないようなので、seleniumさんのイメージをありがたく使いましょう。

サービス追加

docker-composeに以下のように既存サービスの一番下にサービスを追加してください。

docker-copompose.yml
version: '3'
services:
  ### 省略 ###
  chrome:
    image: selenium/standalone-chrome
    ports:
      - "4444:4444"

これだけです。やっぱりイメージ使えば楽ですなあ。

Rspecの設定

次に、Rspecの設定をしていきます。Capybaraとrails_helperを設定するだけで済むので簡単です。

Gemfileの修正

もしまだライブラリをインストールしていなければ以下を追記してから、bundle installしてください。

Gemfile
group :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem "database_cleaner"
  gem "factory_bot_rails"
  gem "rspec-rails"
 # 下記を追加
  gem 'capybara' 
  gem 'selenium-webdriver'
  gem 'rspec-retry'
end

Capibaraの設定

下記のファイルを作成してください。ポート番号は他でもいいですよ。

spec/support/capybara.rb
Capybara.default_driver    = :selenium_chrome
Capybara.javascript_driver = :selenium_chrome
Capybara.server_host = Socket.ip_address_list.detect(&:ipv4_private?).ip_address
Capybara.server_port = 3001
Capybara.default_max_wait_time = 5
Capybara.ignore_hidden_elements = true

Capybara.register_driver :selenium_chrome do |app|
  opts = {
    desired_capabilities: :chrome,
    browser:              :remote,
    url:                  "http://chrome:4444/wd/hub",
  }
  Capybara::Selenium::Driver.new(app, opts)
end

rails_helperの修正

下記のようにrails_helperを修正してください。
なお、spec/support/system_support.rbというSystemスペック用のヘルパーを
作成しなければ任意ってコメントしてる設定は不要です。

spec/rails_helper.rb
  ### 省略 ###

# コメントアウトされてるので、コメントアウトを外してください
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }

RSpec.configure do |config|
  ### 省略 ###

  # Systemスペック用のヘルパー。任意です。
  config.include SystemSupport, type: :system

  # 名称を一意にするために設定。任意かな。
  config.before(:all, type: :system) do
    timestamp!
  end

  # Systemスペックは不安定なのでリトライ用の設定。
  config.verbose_retry = true
  config.display_try_failure_messages = true
  config.default_retry_count = 3

  # 下記を追加
  config.before(:each, type: :system) do
    driven_by Capybara.default_driver
  end

  config.before(:each, type: :system, js: true) do
    driven_by Capybara.javascript_driver
    host! "http://#{Capybara.server_host}:#{Capybara.server_port}"
  end
end

ヘルパーの作成(任意)

Systemスペックは共通の処理(ログイン処理とか)が多いので、ヘルパーを用意しておくと便利です。
任意と書いていますが、作成しておいて損はないと思います。

spec/support/system_support.rb
module SystemSupport
  # 一意の名称を作成するために実行時のtimestampを、
  # 数字でインスタンス変数に格納するsetterです。地味に便利。
  def timestamp!(timestamp = Time.now.to_i)
    @timestamp = timestamp
  end

  # getterです
  def timestamp
    @timestamp
  end

  # ブロックの結果がtrueになるまでループするメソッド。すげえ使う。
  def wait_until(wait_time = Capybara.default_max_wait_time)
    Timeout.timeout(wait_time) do
      loop until yield
    end
  end

  # 特定のcssが登場する、もしくは、なくなるまでループするメソッド
  def wait_for_css(selector, wait_time = Capybara.default_max_wait_time, non_display: false)
    Timeout.timeout(wait_time) do
      loop until send((non_display ? :has_no_css? : :has_css?), selector)
    end
    yield if block_given?
  end

  # 非同期通信が終わるまでループするメソッド
  def wait_for_ajax(wait_time = Capybara.default_max_wait_time)
    Timeout.timeout(wait_time) do
      loop until page.evaluate_script("jQuery.active").zero?
    end
    yield if block_given?
  end
end

System Specの作成

ここまでで設定は完了してるので実際にSystem Specを書いていきましょう。
私のサンプルソースではこんな感じで書いてます。自分のソースコードに合わせて適宜書き直してください。

require "rails_helper"

# typeはsystemを設定、Javascriptも使うのでjsもtrueにしておく。
RSpec.describe "HelloWorlds", type: :system, js: true do
  # 最初にテストデータ作成
  before(:all) {
    create(:hello_world, country: "JP", hello: "こんにちわ世界", priority: 1, file_name: "jp.jpeg")
    create(:hello_world, country: "US", hello: "Hello World", priority: 2)
    create(:hello_world, country: "CN", hello: "你好 世界", priority: 3)
  }

  before(:each) {
    visit root_path

    # コンテンツが全て表示されるまで待つ
    wait_until { (page.all("div.portfolio-item").count == 3) }
  }

  context "when go to index page" do
    it "show contents" do
      expect(page).to have_css("h1", text: "Hello World")

      content = page.first("div.portfolio-item")
      expect(content.first("p.card-text").text).to eq("こんにちわ世界")
      expect(content.first("h4.card-title")).to have_link("日本")
      expect(content.first("img.card-img-top")[:src]).to match(/jp\.jpeg/)
    end
  end

  context "when go to Create page" do
    it "create content" do
      page.first("#new_hello_world").click

      # タイトル出るまで待つ
      wait_until { page.has_css?("h3", text: "New Helloworld") }

      select "ドイツ", from: "hello_world_country"
      # 一意の名称で検索してテストデータを作成
      fill_in "hello_world_hello", with: "Hallo Welt #{timestamp}"
      fill_in "hello_world_priority", with: 4

      click_on "Submit"

      # メッセージ出るまで待つ
      wait_until { page.has_content?("Hello world was successfully created.") }

      content = page.first("div.form").all("label.form-control")
      expect(content[0].text).to eq("ドイツ")
      expect(content[1].text).to eq("Hallo Welt #{timestamp}")
      expect(content[2].text).to eq("4")
    end
  end
end

テスト実行

早速テストを実行してみましょう。
下記のwebはサービス名なので適宜自分の設定してるサービス名に変更して実行してください。

% docker-compose run --rm web rspec spec/system/hello_worlds_spec.rb
Starting hr_chrome_1 ... done
Starting hr_db_1     ... done
Capybara starting Puma...
* Version 4.1.0 , codename: Fourth and One
* Min threads: 0, max threads: 4
* Listening on tcp://192.168.176.4:3001
..

Finished in 1 minute 1.58 seconds (files took 3.71 seconds to load)
2 examples, 0 failures

通りましたね、やったぜ

まとめ

Dockerでも簡単にHeadless Chromeを使った統合テスト環境を実現できました。
テストだけじゃなくてスプレイピングにも利用できるので試してみてください。

因みに、今回使ったサンプルソースはこちらのリポジトリになるので参考までにどうぞ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails Tutorialの知識から【ポートフォリオ】を作って勉強する話 #10 リメンバーミー機能編

こんな人におすすめ

  • プログラミング初心者でポートフォリオの作り方が分からない
  • Rails Tutorialをやってみたが理解することが難しい

前回:#9 永続セッション, cookie編

こんなことが分かる

  • リメンバーミー機能の実装方法

一緒に勉強していこう:bow:
注意:リメンバーミー機能は前回(#9)の導入を済ませた上で機能します。

今回の流れ

  1. ログイン画面にリメンバーミーのチェックボックスを表示する
  2. リメンバーミー機能を実装する

チェックボックスを表示する

まずはログイン画面にチェックボックスを表示する。
クラス名をつける際、Bootstrap4と3では異なるので注意する。
スタイリングはいい感じだったのでほぼそのまま。

app/views/sessions/new.html.erb
<% provide(:title, "ログイン") %>
<div class="container form-container login-container">
  <div class="row">
    <div class="col">
      <div class="form-logo-img">
        <%= link_to image_tag('lantern_lantern_logo.png', width: 100), root_path, class: "logo-img" %>
      </div>
      <h1 class="form-title">ログイン</h1>
      <%= form_with(scope: :session, url: login_path, local: true) do |form| %>

        <div class="form-group">
          <%= form.email_field :email, class: 'form-control', placeholder: "メールアドレス" %>
        </div>

        <div class="form-group">
          <%= form.password_field :password, class: 'form-control', placeholder: "パスワード" %>
        </div>

        <div class="form-group form-check">
          <%= form.check_box :remember_me, class: 'form-check-input' %>
          <%= form.label :remember_me, '次から保存(ログイン省略)', class: 'form-check-label' %>
        </div>

        <div class="form-group">
          <%= form.submit "ログイン", class: "btn btn-info btn-lg form-submit" %>
        </div>
      <% end %>

      <p class="form-go-to-signup-or-login">新しくはじめる方は<%= link_to "こちら", signup_path %></p>
    </div>
  </div>
</div>
/app/assets/stylesheets/application.scss.erb
// 中略
.form-submit {
  width: 100%;
  // margin-top: 1rem; // 削除
}

lantern_lantern_checkbox.png
(header省略)

リメンバーミー機能を実装する

チェックボックスはparams[:session][:remember_me]に保存される。
それぞれオン→'1'、オフ→'0'。
だから実装は超簡単。

app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
# 中略
  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      params[:session][:remember_me] == '1' ? remember(user) : forget(user)
      redirect_to user
    else
      flash.now[:danger] = 'メールアドレスかパスワードが正しくありません'
      render 'new'
    end
  end
# 中略

これでリメンバーミー機能の実装は完了。

今日は短いけどここまで。

前回:#9 永続セッション, cookie編

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RSpecでchromedriverとChromeのバージョンが合わない

はじめに

RSpecでsystem specを書いている時にエラーが出て解決までに2時間ぐらいかかりました。
普段から使っているgemを把握していたり、ログを見ていれば、すぐ解決出来たのはずなのに...
いい勉強になりました。

エラー

rspecを実行すると...

 1.2) Failure/Error: e = error

          Selenium::WebDriver::Error::SessionNotCreatedError:
            session not created: Chrome version must be between 71 and 75
              (Driver info: chromedriver=2.46.628411 (3324f4c8be9ff2f70a05a30ebc72ffb013e1a71e),platform=Mac OS X 10.14.6 x86_64)

このようなエラーが出ます。

Chromeとchromedriverのバージョンが合っていないみたいです。
とりあえず、chromedriverのバージョンを上げてみたり、homebrewで新たにインストールして見ましたが解決しませんでした。

インストール時の参考記事↓
MacにChromeDriverを入れる
How to Install Chrome Driver on Mac (2019 Update)

解決策

次にgemで関係するものないかなと思い、gem listでgemを覗いてみるとchromedriver-helperというそれぽいのがあったのでググってみるとお目当ての記事にたどり着きました。

以下記事参照↓
サポートが終了したchromedriver-helperからwebdrivers gemに移行する手順-Qiita

そもそもデフォルトでchromedriver-helperが入っているみたいです。gemの存在すら知りませんでした。
chromedriver-helperのサポートが終了しいるため、chromedriverとChromeのバージョンが合わないというエラーが出たみたいです。
また、chromedriver-helperのインストール時にちゃんと警告も出ているみたいです。ちゃんと見ていなかったので気づきませんでした?

警告通りgemをgem 'webdrivers'に入れ替えたらあっさり解決しました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【備忘録】Rails5でモデル名を変更する方法

前提条件

OS:Mac OS X
Rails:5.2.3
groupモデルをroomモデルに変更します。

モデル名の変更方法

最初に結論から。
そのあとそれぞれのコマンドの解説をしています。
※置換で一気に変更する際は、migrationファイルなど変更したくないファイルには気をつけてください。

1. 変更するモデルに対応するDBのテーブル名を変更

20190815230119_rename_groups.rb
  def change
    # :groupsが変更前
    rename_table :groups, :rooms
  end

変更内容をDBに反映

ターミナル
   $ rails db:migrate

2.モデルに関連するファイル名を全て変更

ターミナル
   # find 対象ディレクトリ | xargs rename -s 変更前のファイル名 変更後のファイル名
   $ find ./ | xargs rename -s group room
   $ find ./ | xargs rename -s Group Room

■ 実行結果の例 ■
groups_controller.rb
    ↓
rooms_controller.rb

3.モデルが使用されてるファイルの中身を全て変更

ターミナル
   # find 対象ディレクトリ -name "検索する文字列" | LC_ALL=C xargs sed -i -e "s/変換前/変換後/g"
   $ find ./ -name "*room*" | LC_ALL=C xargs sed -i -e "s/group/room/g"
   $ find ./ -name "*room*" | LC_ALL=C xargs sed -i -e "s/Group/Room/g"

■ 実行結果の例 ■
class Admin::GroupsController < ApplicationController
           ↓
class Admin::RoomsController < ApplicationController

4.バックアップとしてできたファイルを削除

なぜかファイル名の末尾に-eとつくバックアップのファイルが生成されたため削除する。

ターミナル
   # find 対象ディレクトリ -name "検索する文字列" | xargs rm
   $ find ./ -name "*.*-e" | xargs rm

■ 実行結果 ■
下記のようなファイル名の末尾に-eとつくファイルが削除された。
groups_controller.rb-e

使用したコマンドの解説

findコマンド

ファイルやディレクトリを検索できる。

オプション コマンドの説明
-name ファイル名を指定して検索できる

xargsコマンド

前に実行したコマンドの結果を引き継げる。

ターミナル
   # find 対象ディレクトリ | xargs rename -s 変更前のファイル名 変更後のファイル名
   $ find ./ | xargs rename -s group room

上記の例だと、「find ./」で自分がいる階層のファイルとディレクトリを検索している。
xargsを使ってその検索結果を引き継ぎ、引き継いだ検索結果の中からファイル名がgroupの
ものをroomに変更している。

sedコマンド

ファイルの中身のテキストを置換できる。

オプション コマンドの説明
-i ファイルを上書きする
s 正規表現で置換処理をする
g マッチした全ての文字列を置換する

-eコマンドはちょっとよくわかんない。
ご存知の方、ご教授いただけると助かります。

LC_ALL=C

出力する言語や時刻などのフォーマットを設定する。
LC_ALL=Cはデフォルトの言語(英語)で出力される。

デフォルトの言語を日本語にしているせいかこれつけないとillegal byte sequence
っていうエラーがでるからつけてます。

終わりに

モデル名の変更に時間がかかったので自分用のメモとしてこの記事を書きました。
皆様のモデル名の変更するときの参考になれば幸いです。

なぜバックアップのファイルが出力されたのか謎。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsチュートリアル 第3章 - ほぼ静的なページの実装を題材に、テスト駆動開発してみる

前提

Railsチュートリアル 第3章 - rails createの裏側で何が行われているのかの続きです。待ちに待ったテストの実装パートがやってきました。

テストの実装事始め

Railsには、テストの実装を支援するための仕組みが実装されています。「rails generate controllerを実行すると、コントローラテストも一緒に生成される」という動作もその一つです。前のエントリで作成下StaticPagesコントローラにも、現時点できちんとテストは作成されています。test/controllers/static_pages_controller_test.rbというファイルがそれです。

test/controllers/static_pages_controller_test.rb
require 'test_helper'

class StaticPagesControllerTest < ActionDispatch::IntegrationTest
  test "should get home" do
    get static_pages_home_url
    assert_response :success
  end

  test "should get help" do
    get static_pages_help_url
    assert_response :success
  end

end

内容については、Railsチュートリアルにおける説明を読んでもらいたいと思います。現時点では「テストは2つある」ということを認識しておきましょう。

いずれにせよ、「現時点でテストスイートを実行すると、問題なくパスする」という状態になっています。実際にテストを走らせてみましょう。開発環境でrails testというコマンドを実行するのです。

bash
# rails test
...略

# Running:

..

Finished in 1.759569s, 1.1366 runs/s, 1.1366 assertions/s.

2 runs, 2 assertions, 0 failures, 0 errors, 0 skips

「テストが2つ実行され、2つとも成功した」という意味のメッセージが表示されています。

初めてのテスト駆動開発

テスト駆動開発のサイクルは、以下のプロセスの積み重ねで進んでいきます。

  1. 失敗するテストを最初に書く
  2. アプリケーションのコードを書き、テストを成功させる
  3. 必要ならばリファクタリングする

失敗するテストを書く

実際に、失敗するテストを最初に書いてみましょう。テーマは「Aboutページの実装」です。test/controllers/static_pages_controller_test.rbに、テストコードを書いていきます。前述のコードとの差分は以下のとおりです。

test/controllers/static_pages_controller_test.rb
class StaticPagesControllerTest < ActionDispatch::IntegrationTest
     assert_response :success
   end

+  test "should get about" do
+    get static_pages_about_url
+    assert_response :success
+  end
+
 end

見事テストが失敗する

開発環境上で、改めてrails tastを実行します。

bash
# rails test
...略

# Running:

.E.

Finished in 0.860237s, 3.4874 runs/s, 2.3249 assertions/s.

  1) Error:
StaticPagesControllerTest#test_should_get_about:
NameError: undefined local variable or method `static_pages_about_url' for #<StaticPagesControllerTest:0x000055649d2c1ee0>
    test/controllers/static_pages_controller_test.rb:15:in `block in <class:StaticPagesControllerTest>'

3 runs, 2 assertions, 0 failures, 1 errors, 0 skips

めでたく(?)テストが失敗しました。「1 errors」というのがその結果を示していますね。さあここから、「3 runs, 3 assertions」にしていきましょう。

テストを成功させるまで

undefined local variable or method `static_pages_about_url'...略というエラーメッセージが表示されていますね。「url」という文字列があるということは、config/routes.rbを変更するのでしょう。というわけで、以下の変更を加えていきます。

config/routes.rb
diff --git a/config/routes.rb b/config/routes.rb
index 56f06f7..c9da274 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -3,5 +3,7 @@ Rails.application.routes.draw do

   get 'static_pages/help'

+  get 'static_pages/about'
+
   root 'application#hello'
 end

開発環境にて、再びrails testを実行してみます。

bash
# rails test
...略

# Running:

E..

Finished in 1.112838s, 2.6958 runs/s, 1.7972 assertions/s.

  1) Error:
StaticPagesControllerTest#test_should_get_about:
AbstractController::ActionNotFound: The action 'about' could not be found for StaticPagesController
    test/controllers/static_pages_controller_test.rb:15:in `block in <class:StaticPagesControllerTest>'

3 runs, 2 assertions, 0 failures, 1 errors, 0 skips

やはりテストは失敗しますね。

ただ、エラーメッセージの中身には変化がありました。The action 'about' could not be found for StaticPagesControllerというやつです。「StaticPagesController」という文字列があるということは、app/controllers/static_pages_controller.rbを変更するのでしょう。というわけで、以下の変更を加えていきます。

app/controllers/static_pages_controller.rb
diff --git a/app/controllers/static_pages_controller.rb b/app/controllers/static_pages_controller.rb
index c76b925..19f79a9 100644
--- a/app/controllers/static_pages_controller.rb
+++ b/app/controllers/static_pages_controller.rb
@@ -4,4 +4,7 @@ class StaticPagesController < ApplicationController

   def help
   end
+
+  def about
+  end
 end

開発環境にて、再びrails testを実行してみます。

bash
# rails test
...略

# Running:

E..

Finished in 1.832907s, 1.6367 runs/s, 1.0912 assertions/s.

  1) Error:
StaticPagesControllerTest#test_should_get_about:
ActionController::UnknownFormat: StaticPagesController#about is missing a template for this request format and variant.

request.formats: ["text/html"]
request.variant: []

NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot.
    test/controllers/static_pages_controller_test.rb:15:in `block in <class:StaticPagesControllerTest>'

3 runs, 2 assertions, 0 failures, 1 errors, 0 skips

また失敗です。

ただ、またエラーメッセージが変わりましたね。今度はActionController::UnknownFormat: StaticPagesController#about is missing a template for this request format and variant.というメッセージになっています。

「missing a template」というのは、「コントローラが要求するビューがない」という趣旨のメッセージです。今回の場合であれば、「app/views/static_pages/about.html.erb」があればOKなはずです。早速当該ファイルを作成しましょう。Railsチュートリアル本文に倣い、内容は以下の通りとします。

app/views/static_pages/about.html.erb
<h1>About</h1>
<p>
  <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
  is a <a href="https://railstutorial.jp/#ebook">book</a> and
  <a href="https://railstutorial.jp/#screencast">screencast</a>
  to teach web development with
  <a href="http://rubyonrails.org/">Ruby on Rails</a>.
  This is the sample application for the tutorial.
</p>
bash
# rails test
...略

# Running:

...

Finished in 0.719320s, 4.1706 runs/s, 4.1706 assertions/s.

3 runs, 3 assertions, 0 failures, 0 errors, 0 skips

ついにテストが成功しました!

この時点でRailsのdevelopmentサーバーを起動し、/static_pages/aboutをWebブラウザで表示すると、結果は以下のとおりになりました。

スクリーンショット 2019-08-14 15.36.02.png

リファクタリングしてみよう

Railsチュートリアル本文においては、ここで「Home、Help、Aboutそれぞれのページを編集し、各ページ異なるタイトルを表示する」という機能追加を行います。そのときの具体的な実装手順として、以下の手順で行うことが示されています。

  1. ページタイトルの簡単なテストを書く(RED)
  2. 3つのページにタイトルを追加する(GREEN)
  3. レイアウトファイルを活用してコードの重複を解決する(REFACTOR)

「テストとリファクタリングという一連の手順を実際にやってみよう」ということですね。「テストとリファクタリング」という考え方は大変重要ですし、実際に手を動かすことは覚えることへの早道なので、その手順をなぞってみることにします。

まず、この後の手順を進めるため、ソースコード内にある既存レイアウトファイルを一旦リネームします。

zsh
>>> pwd
~/docker/rails_tutorial_test/sample_app

>>> mv app/views/layouts/application.html.erb layout_file

ページタイトルの追加 - テストを成功させるまで

続いて、テストを以下のように書き換えます。

test/controllers/static_pages_controller_test.rb
class StaticPagesControllerTest < ActionDispatch::IntegrationTest
   test "should get home" do
     get static_pages_home_url
     assert_response :success
+    assert_select "title", "Home | Ruby on Rails Tutorial Sample App"
   end

   test "should get help" do
     get static_pages_help_url
     assert_response :success
+    assert_select "title", "Help | Ruby on Rails Tutorial Sample App"
   end

   test "should get about" do
     get static_pages_about_url
     assert_response :success
+    assert_select "title", "About | Ruby on Rails Tutorial Sample App"
   end

 end

この時点におけるrails testの結果は以下のとおりです。

bash
# rails test
...略

# Running:

FFF

Finished in 0.573597s, 5.2302 runs/s, 10.4603 assertions/s.

  1) Failure:
StaticPagesControllerTest#test_should_get_home [/var/www/sample_app/test/controllers/static_pages_controller_test.rb:7]:
Expected at least 1 element matching "title", found 0..
Expected 0 to be >= 1.

  2) Failure:
StaticPagesControllerTest#test_should_get_about [/var/www/sample_app/test/controllers/static_pages_controller_test.rb:19]:
Expected at least 1 element matching "title", found 0..
Expected 0 to be >= 1.

  3) Failure:
StaticPagesControllerTest#test_should_get_help [/var/www/sample_app/test/controllers/static_pages_controller_test.rb:13]:
Expected at least 1 element matching "title", found 0..
Expected 0 to be >= 1.

3 runs, 6 assertions, 3 failures, 0 errors, 0 skips

失敗が3つ出ていますね。「Home、About、Helpそれぞれのビューにおいて、title要素が存在しない」という趣旨のメッセージが書かれています。

Home、About、Helpそれぞれのビューを以下のように書き換えます。

app/views/static_pages/home.html.erb
-<h1>Sample App</h1>
-<p>
-  This is the home page for the
-  <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
-  sample application.
-</p>
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Home | Ruby on Rails Tutorial Sample App</title>
+  </head>
+  <body>
+    <h1>Sample App</h1>
+    <p>
+      This is the home page for the
+      <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
+      sample application.
+    </p>
+  </body>
+</html>
app/views/static_pages/help.html.erb
-<h1>Help</h1>
-<p>
-  Get help on the Ruby on Rails Tutorial at the
-  <a href="https://railstutorial.jp/help">Rails Tutorial help page</a>.
-  To get help on this sample app, see the
-  <a href="https://railstutorial.jp/#ebook"><em>Ruby on Rails Tutorial</em>
-  book</a>.
-</p>
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Help | Ruby on Rails Tutorial Sample App</title>
+  </head>
+  <body>
+    <h1>Help</h1>
+    <p>  Get help on the Ruby on Rails Tutorial at the
+      <a href="https://railstutorial.jp/help">Rails Tutorial help
+      page</a>.
+      To get help on this sample app, see the
+      <a href="https://railstutorial.jp/#ebook">
+      <em>Ruby on Rails Tutorial</em> book</a>.
+    </p>
+  </body>
+</html>
app/views/static_pages/about.html.erb
-<h1>About</h1>
-<p>
-  <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
-  is a <a href="https://railstutorial.jp/#ebook">book</a> and
-  <a href="https://railstutorial.jp/#screencast">screencast</a>
-  to teach web development with
-  <a href="http://rubyonrails.org/">Ruby on Rails</a>.
-  This is the sample application for the tutorial.
-</p>
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>About | Ruby on Rails Tutorial Sample App</title>
+  </head>
+  <body>
+    <h1>About</h1>
+    <p>
+      <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
+      is a <a href="https://railstutorial.jp/#ebook">book</a> and
+      <a href="https://railstutorial.jp/#screencast">screencast</a>
+      to teach web development with
+      <a href="http://rubyonrails.org/">Ruby on Rails</a>.
+      This is the sample application for the tutorial.
+    </p>
+  </body>
+</html>

再びrails testを実行すると、今度はテストが成功します。

bash
# rails test
...略

# Running:

...

Finished in 0.557244s, 5.3836 runs/s, 10.7673 assertions/s.

3 runs, 6 assertions, 0 failures, 0 errors, 0 skips

演習 - テストケースの追加と、テストが成功することの確認

Railsチュートリアル本文によれば、演習の内容は以下のとおりです。

StaticPagesコントローラのテスト (リスト 3.24) には、いくつか繰り返しがあったことにお気づきでしょうか? 特に「Ruby on Rails Tutorial Sample App」という基本タイトルは、各テストで毎回同じ内容を書いてしまっています。そこで、setupという特別なメソッド (各テストが実行される直前で実行されるメソッド) を使って、この問題を解決したいと思います。まずは、リスト 3.30のテストが green になることを確認してみてください (リスト 3.30では、2.2.2で少し触れたインスタンス変数や文字列の式展開というテクニックを使っています。それぞれ4.4.5と4.2.2で詳しく解説するので、今はわからなくても問題ありません)。

まず、test/controllers/static_pages_controller_test.rbを書き換えます。

test/controllers/static_pages_controller_test.rb
 require 'test_helper'

 class StaticPagesControllerTest < ActionDispatch::IntegrationTest
+
+  def setup
+    @base_title = "Ruby on Rails Tutorial Sample App"
+  end
+
   test "should get home" do
     get static_pages_home_url
     assert_response :success
-    assert_select "title", "Home | Ruby on Rails Tutorial Sample App"
+    assert_select "title", "Home | #{@base_title}"
   end

   test "should get help" do
     get static_pages_help_url
     assert_response :success
-    assert_select "title", "Help | Ruby on Rails Tutorial Sample App"
+    assert_select "title", "Help | #{@base_title}"
   end

   test "should get about" do
     get static_pages_about_url
     assert_response :success
-    assert_select "title", "About | Ruby on Rails Tutorial Sample App"
+    assert_select "title", "About | #{@base_title}"
   end

 end

再びrails testを実行します。

bash
# rails test
...略

# Running:

...

Finished in 0.487921s, 6.1485 runs/s, 12.2971 assertions/s.

3 runs, 6 assertions, 0 failures, 0 errors, 0 skips

Railsチュートリアル本文記載のとおり、テストが成功することが確認できました。

ERBを使って、ページタイトルの一部を変数で与えるようにしてみる

今度は、「Home、Help、Aboutそれぞれのビューを編集し、ページタイトルの一部を変数で与えるようにする」という機能を実装してみます。各ビューの内容の差分は以下のとおりです。

app/views/static_pages/home.html.erb
+<% provide(:title, "Home") %>
 <!DOCTYPE html>
 <html>
   <head>
-    <title>Home | Ruby on Rails Tutorial Sample App</title>
+    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
   </head>
   <body>
     <h1>Sample App</h1>
app/views/static_pages/help.html.erb
+<% provide(:title, "Help") %>
 <!DOCTYPE html>
 <html>
   <head>
-    <title>Help | Ruby on Rails Tutorial Sample App</title>
+    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
   </head>
   <body>
     <h1>Help</h1>
app/views/static_pages/about.html.erb
+<% provide(:title, "About") %>
 <!DOCTYPE html>
 <html>
   <head>
-    <title>About | Ruby on Rails Tutorial Sample App</title>
+    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
   </head>
   <body>
     <h1>About</h1>

rails testを実行します。

bash
# rails test
...略

# Running:

...

Finished in 0.694081s, 4.3223 runs/s, 8.6445 assertions/s.

3 runs, 6 assertions, 0 failures, 0 errors, 0 skips

テストも無事成功していますね。

リファクタリング - HTMLの重複した構造をDRYにする

まず、項目「リファクタリングしてみよう」の冒頭で退避させていたapp/views/layouts/application.html.erbの内容を元に戻します。

zsh
>>> pwd
~/docker/rails_tutorial_test/sample_app

>>> mv layout_file app/views/layouts/application.html.erb

app/views/layouts/application.html.erbを以下の内容で書き換えます。

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
    <%= csrf_meta_tags %>

    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>

以降、Sampleアプリはapp/views/layouts/application.html.erbでHome、Help、AboutそれぞれのビューのHTML構造を定義する動作になります。各ビューにbodyの内容以外は不要になるので、不要な部分は削除します。差分は以下のとおりです。

app/views/static_pages/home.html.erb
 <% provide(:title, "Home") %>
-<!DOCTYPE html>
-<html>
-  <head>
-    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
-  </head>
-  <body>
-    <h1>Sample App</h1>
-    <p>
-      This is the home page for the
-      <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
-      sample application.
-    </p>
-  </body>
-</html>
+<h1>Sample App</h1>
+<p>
+  This is the home page for the
+  <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
+  sample application.
+</p>
app/views/static_pages/help.html.erb
 <% provide(:title, "Help") %>
-<!DOCTYPE html>
-<html>
-  <head>
-    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
-  </head>
-  <body>
-    <h1>Help</h1>
-    <p>  Get help on the Ruby on Rails Tutorial at the
-      <a href="https://railstutorial.jp/help">Rails Tutorial help
-      page</a>.
-      To get help on this sample app, see the
-      <a href="https://railstutorial.jp/#ebook">
-      <em>Ruby on Rails Tutorial</em> book</a>.
-    </p>
-  </body>
-</html>
+<h1>Help</h1>
+<p>  Get help on the Ruby on Rails Tutorial at the
+  <a href="https://railstutorial.jp/help">Rails Tutorial help section</a>.
+  To get help on this sample app, see the
+  <a href="https://railstutorial.jp/#ebook"><em>Ruby on Rails Tutorial</em>
+  book</a>.
+</p>
app/views/static_pages/about.html.erb
 <% provide(:title, "About") %>
-<!DOCTYPE html>
-<html>
-  <head>
-    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
-  </head>
-  <body>
-    <h1>About</h1>
-    <p>
-      <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
-      is a <a href="https://railstutorial.jp/#ebook">book</a> and
-      <a href="https://railstutorial.jp/#screencast">screencast</a>
-      to teach web development with
-      <a href="http://rubyonrails.org/">Ruby on Rails</a>.
-      This is the sample application for the tutorial.
-    </p>
-  </body>
-</html>
+<h1>About</h1>
+<p>
+  <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
+  is a <a href="https://railstutorial.jp/#ebook">book</a> and
+  <a href="https://railstutorial.jp/#screencast">screencast</a>
+  to teach web development with
+  <a href="http://rubyonrails.org/">Ruby on Rails</a>.
+  This is the sample application for the tutorial.
+</p>

テストが成功することを確認するのも忘れてはいけません。

# rails test
...略

# Running:

...

Finished in 0.651065s, 4.6078 runs/s, 9.2157 assertions/s.

3 runs, 6 assertions, 0 failures, 0 errors, 0 skips

ソースコードに対する変更をGitリポジトリにコミットするのも忘れずに。

zsh
>>> git add -A
>>> git commit -am "Add application.html.erb"
>>> git push

演習 - サンプルアプリケーションにContact (問い合わせ先) ページを作成してみる

Railsチュートリアル本文によれば、演習の内容は以下のとおりです。

サンプルアプリケーションにContact (問い合わせ先) ページを作成してください16 (ヒント: まずはリスト 3.15を参考にして、/static_pages/contactというURLのページに「Contact | Ruby on Rails Tutorial Sample App」というタイトルが存在するかどうかを確認するテストを最初に作成しましょう。次に、3.3.3でAboutページを作ったときのと同じように、Contactページにもリスト 3.40のコンテンツを表示してみましょう。)。

まず、test/controllers/static_pages_controller_test.rbに、Contactビューに対するテストコードを追加します。

test/controllers/static_pages_controller_test.rb
 class StaticPagesControllerTest < ActionDispatch::IntegrationTest
     assert_select "title", "About | #{@base_title}"
   end

+  test "should get contact" do
+    get static_pages_contact_url
+    assert_response :success
+    assert_select "title", "Contact | #{@base_title}"
+  end
+
 end

この時点でテストを実行します。

bash
# rails test
...略

# Running:

.E..

Finished in 1.085848s, 3.6838 runs/s, 5.5256 assertions/s.

  1) Error:
StaticPagesControllerTest#test_should_get_contact:
NameError: undefined local variable or method `static_pages_contact_url' for #<StaticPagesControllerTest:0x000055b6c4f0ba88>
    test/controllers/static_pages_controller_test.rb:28:in `block in <class:StaticPagesControllerTest>'

4 runs, 6 assertions, 0 failures, 1 errors, 0 skips

当然テストは失敗します。undefined local variable or method `static_pages_contact_url'というメッセージがポイントですね。urlとあるだけに、config/routes.rbstatic_pages/contactへのルーティングが設定されていないことが問題のようです。早速config/routes.rbを書き換えます。

config/routes.rb
   get 'static_pages/about'

+  get 'static_pages/contact'
+
   root 'application#hello'
 end

再びテストを実行します。

bash
# rails test
...略

# Running:

.E..

Finished in 1.048572s, 3.8147 runs/s, 5.7221 assertions/s.

  1) Error:
StaticPagesControllerTest#test_should_get_contact:
AbstractController::ActionNotFound: The action 'contact' could not be found for StaticPagesController
    test/controllers/static_pages_controller_test.rb:28:in `block in <class:StaticPagesControllerTest>'

4 runs, 6 assertions, 0 failures, 1 errors, 0 skips

失敗しましたが、メッセージの内容が変わっています。The action 'contact' could not be found for StaticPagesControllerとありますね。StaticPagesコントローラにcontactというアクションが存在しないことが原因のようです。app/controllers/static_pages_controller.rbに、StaticPages#contactというアクションの定義を追加します。

app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController

   def about
   end
+
+  def contact
+  end
 end

再びテストを実行します。

bash
# rails test
...略

# Running:

..E.

Finished in 1.480814s, 2.7012 runs/s, 4.0518 assertions/s.

  1) Error:
StaticPagesControllerTest#test_should_get_contact:
ActionController::UnknownFormat: StaticPagesController#contact is missing a template for this request format and variant.

request.formats: ["text/html"]
request.variant: []

...略

4 runs, 6 assertions, 0 failures, 1 errors, 0 skips

また失敗しましたが、またメッセージの内容が変わっています。StaticPagesController#contact is missing a template for this request format and variant.というメッセージですね。内容が定義されたapp/views/static_pages/contact.html.erbが必要になる、ということなのでしょう。早速当該ファイルを作成し、内容を定義します。

zsh
>>> pwd
~/docker/rails_tutorial_test/sample_app

>>> touch app/views/static_pages/contact.html.erb
app/views/static_pages/contact.html.erb
<% provide(:title, "Contact") %>
<h1>Contact</h1>
<p>
  Contact the Ruby on Rails Tutorial about the sample app at the
  <a href="https://railstutorial.jp/contact">contact page</a>.
</p>

再びテストを実行します。

bash
# rails test
Running via Spring preloader in process 473
/var/www/sample_app/db/schema.rb doesn't exist yet. Run `rails db:migrate` to create it, then try again. If you do not intend to use a database, you should instead alter /var/www/sample_app/config/application.rb to limit the frameworks that will be loaded.
Run options: --seed 32838

# Running:

....

Finished in 0.908276s, 4.4039 runs/s, 8.8079 assertions/s.

4 runs, 8 assertions, 0 failures, 0 errors, 0 skips

テストが成功しました。Webブラウザで/static_pages/contactを表示した結果は以下のとおりです。

スクリーンショット 2019-08-14 22.19.21.png

ルーティングの設定

config/routes.rbを書き換え、/にアクセスした際に/static_pages/homeが表示されるようにします。

config/routes.rb
 Rails.application.routes.draw do
+  root 'static_pages#home'
   get 'static_pages/home'
-
   get 'static_pages/help'
-
   get 'static_pages/about'
-
   get 'static_pages/contact'
-
-  root 'application#hello'
 end

この時点でWebブラウザで/を表示すると、結果は以下のとおりになります。

スクリーンショット 2019-08-14 22.39.49.png

演習 - rootルーティングに対するテスト

1.rootルーティングのテストを書いてみてください。

テストコードは以下のとおりです。

test/controllers/static_pages_controller_test.rb
class StaticPagesControllerTest < ActionDispatch::IntegrationTest
     @base_title = "Ruby on Rails Tutorial Sample App"
   end

+  test "should get root" do
+    get root_url
+    assert_response :success
+  end
+
   test "should get home" do
     get static_pages_home_url
     assert_response :success

テストを実行します。

# rails test
...略

# Running:

.....

Finished in 1.082391s, 4.6194 runs/s, 8.3149 assertions/s.

5 runs, 9 assertions, 0 failures, 0 errors, 0 skips

テストは成功しました。

2. rootルーティングをコメントアウトして見て、 redになるかどうか確かめてみましょう。

config/routes.rb
 Rails.application.routes.draw do
-  root 'static_pages#home'
+  #root 'static_pages#home'
   get 'static_pages/home'
   get 'static_pages/help'
   get 'static_pages/about'
   get 'static_pages/contact'
 end
# rails test
...略

# Running:

..E..

Finished in 0.862515s, 5.7970 runs/s, 9.2752 assertions/s.

  1) Error:
StaticPagesControllerTest#test_should_get_root:
NameError: undefined local variable or method `root_url' for #<StaticPagesControllerTest:0x0000556f1b7f69c8>
    test/controllers/static_pages_controller_test.rb:10:in `block in <class:StaticPagesControllerTest>'

5 runs, 8 assertions, 0 failures, 1 errors, 0 skips

テストは失敗しました。undefined local variable or method `root_url' ...略 `block in <class:StaticPagesControllerTest>'というメッセージが表示されていますね。「rootへのルーティングが定義されていない」という趣旨のエラーメッセージなので、確かに先ほどrootルーティングをコメントアウトしたのが原因のようです。

演習が終わったら、rootルーティングのコメントアウトを元に戻すのを忘れずに。

最後に、本番環境へのデプロイ

まず、ブランチstatic-pagesの内容をmasterにマージする

zsh
>>> git checkout master
>>> git merge static-pages
>>> git push

以上のコマンドを順に実行します。以下の操作が順に実行されます。

  1. 作業対象のブランチをmasterに変更する
  2. ブランチstatic-pagesに適用された変更を、現在の作業対象のブランチにマージする
  3. 現在の作業対象のブランチに適用された変更をGitHubにプッシュする

ローカルのmasterに適用された変更をHerokuにプッシュする

ここまで実行し、現在の作業対象のブランチがmasterであることを確認したら、いよいよHerokuにここまでの内容をデプロイします。

zsh
>>> git branch
* master
  static-pages

>>> git push heroku
Total 0 (delta 0), reused 0 (delta 0)
...略
To https://git.heroku.com/warm-woodland-62915.git
   ffe2338..2ffdc50  master -> master

Herokuで/にアクセスしてみます。

スクリーンショット 2019-08-16 9.43.30.png

/homeの内容が表示されました。

余談…リモートブランチを削除する

間違ってブランチstatic-pagesをHerokuにプッシュしてしまったという場合は、当該リモートブランチを削除しておきましょう。「ローカルブランチを指定せずにリモートブランチにプッシュする」という操作を行うことによって、リモートブランチを削除することができます。

zsh
>>> git push heroku :static-pages
remote: Pushed to non-master branch, skipping build.
To https://git.heroku.com/warm-woodland-62915.git
 - [deleted]         static-pages
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pay.jpのキー

Ruby 2.5.1
Rails 5.2.3

スクールの課題(チーム開発で)デプロイ担当となってしまった私は、Pay.jpのAPIのキーがないというエラーに直面しましたので、少しでも参考になればとやったことを書いておきます。

Pay.jp利用のための公開鍵、秘密鍵の登録

local環境ではdotenv-railsを利用して環境変数を管理していましたが、本番でAPIキーがないことを示すエラーが出ていました。環境変数の読み込みがうまくいっていないということですね。

何はともあれ.bash_profileに書き込む

ローカル、リモート共にキーの情報をbashに直書き!

$ vim ~/.bash_profile

APIの秘密鍵、公開鍵の情報をコピー&ペースト。

$ source ~/.bash_profile

有効化するには上記コマンドが必要。記述が変だとエラーが出るので注意しましょう。

結局失敗。

.env.developmentを作成しなかったためでは?

envのメリットは環境ごとに扱う変数の管理ができること。

.env.development
.env.test
.env.developmen
と種類がある。

私のチームでは.env.developmentしかなかった。よって、prodductionを作成。

①作成 失敗
②touch コマンドで本番(EC2)current以下に作成 失敗

リモートの/env.environment以下に書き込む

ターミナルで以下の操作

$ sudo vim /etc/environment

環境変数を有効にするため一旦ログアウト、サイドリモートに接続し直す。

$ env | grep キーの名前

これで値が取れているか確認できます。

私の場合はさらにunicornを再起動したらうまくいきました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails Tutorialの知識から【ポートフォリオ】を作って勉強する話 #9 永続セッション, cookie編

こんな人におすすめ

  • プログラミング初心者でポートフォリオの作り方が分からない
  • Rails Tutorialをやってみたが理解することが難しい
  • ポートフォリオを作成しながら勉強したい

前回:#8 ログイン/ログアウト, FactroyBot編
次回:#10 リメンバーミー機能編

こんなことが分かる

  • 永続化するメソッドが知れる
  • cookieについて分かる
  • attr_accessorについて分かる
  • クラスメソッドとインスタンスメソッドの違いが分かる

一緒に勉強しませう:bow:

今回の流れ

  1. 永続化の手順をざっくり解説
  2. 各単語やメソッドを確認
  3. 永続化を実装
  4. バグを除去

こういう流れでやって行きます。

どうやってログインを永続化させる?

ログインを永続化させるリメンバーミー機能。
(リメンバーミー機能の実装は次回)
流れ的にはこうらしい(以下Tutorial 9.1 Remember me 機能を引用)

  1. 記憶トークンにはランダムな文字列を生成して用いる
  2. ブラウザのcookiesにトークンを保存するときには、有効期限を設定する
  3. トークンはハッシュ値に変換してからデータベースに保存する
  4. ブラウザのcookiesに保存するユーザーIDは暗号化しておく
  5. 永続ユーザーIDを含むcookiesを受け取ったら、そのIDでデータベースを検索し、記憶トークンのcookiesがデータベース内のハッシュ値と一致することを確認する

なんのこっちゃ。
それによく分からない言葉やメソッドやらがいっぱいある。

  • cookie
  • remember_digest
  • remember_token
  • User.new_token
  • User.digest
  • remember
  • attr_accessor ← これはRubyの話だけど

これも1つずつ解説しよう。

永続化の仕組みをざっくり解説

これから以下の工程で永続化を行います。
超ざっくりにまとめるとこんな感じ。

  1. サーバに記憶トークンとIDの暗号作ってもらう
  2. パソコンに保存する
  3. 次来た時保存した暗号をサーバと照らし合わせる
  4. OKだったら勝手にログインする

クッキー(cookie)とは?

クッキーはパソコンとサーバの橋渡しです。
...質素すぎますね。

クッキーちゃんというキャラを想像してみよう(強引)
:cookie: ...ここにいました。

:cookie:はサーバと私たちのパソコンを繋ぐメモ係です。
私たちが「次もログインしたままがいいなあ、サーバさんに伝えといてくれる?」
とお願いすると、:cookie:は「分かった!」と言ってくれました。

しかし:cookie:はか弱いです。
サーバに重要な情報を渡す途中で盗まれたりしたら大変!
なので名前をつけた情報(remember_token)を暗号(remember_digest)にしちゃいました。

これで安全!:cookie:はサーバさんに情報を伝えるおつかいに行ったのでした。

分かりやすい解説↓
クッキー(cookie)とは?初心者でも分かるように図解

remember_token / remember_digestとは?

remember_tokenを暗号化 → remember_digestに代入
こういう関係性。

User.new_token / User.digest / rememberとは?

User.new_token → remember_tokenを作るメソッド
User.digest → remember_tokenとかの値を暗号にするメソッド
remember → 暗号をremember_digestに代入するメソッド

User.digestと関連する解説↓
ハッシュ値 (hash value)とは

remember_digestに保存するまでを実装する

ここまでくると各メソッドがどういう風に動くかが見えてくる。

  1. User.new_tokenメソッドでremember_tokenを作る
  2. User.digestメソッドでremember_tokenを暗号化する
  3. rememberメソッドで暗号化したremember_tokenをremember_digestに保存する

では早速実装しよう。
その前に1つ。
User.digestやUser.new_tokenはこんな風に書き換えできる。

class << self
  def digest(string)
  end

  def new_token
  end
end

これを踏まえた上で実装する。
まずUserモデルにremember_digest属性を追加。

bash
$ rails generate migration add_remember_digest_to_users remember_digest:string
$ rails db:migrate

その後user.rbにメソッドを書く。

app/models/user.rb
class User < ApplicationRecord
  attr_accessor :remeber_token  
  # 中略

  class << self
    def digest(string)
      cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                    BCrypt::Engine.cost
      BCrypt::Password.create(string, cost: cost)
    end

    def new_token
      SecureRandom.urlsafe_base64
    end
  end

  def remember
    self.remember_token = User.new_token
    update_attribute(:remember_digest, User.digest(remember_token))
  end
end

attr_accessorは何をしているのか

勘のいい方は分かるかもしれませんが、
本来remember_tokenなんて属性はUserモデルにありません。
(あるのはさっきmigrationで作ったremember_digestだけ)

だからremember_token属性に代入できるわけがない。
それをいい感じにやってくれるのがattr_accessor。

attr_accessor :remember_token

こうするとremember_token属性を仮で作ってくれる。
だから存在しないはずのremeber_tokenに代入することができる。

分かりやすい解説↓
Rubyのattr_accessorって何?[和訳]
Railsから入った人へ【attr_accessor】って?
永続cookiesでガチセッションするRailsチュートリアル9章

どうしてUser.digestやUser.new_tokenにはUser.がつくのか

これも素朴な疑問です。
これらはクラスメソッドと呼ばれるもので、今まで定義してきたインスタンスメソッドとは少々異なります。

  • クラスメソッド → クラスオブジェクトからでしか呼び出せない
  • インスタンスメソッド → インスタンスオブジェクトからでしか呼び出せない

例えばよくこんなことをしますよね。

user = User.new

ここでのuserはインスタンスオブジェクト。
この時、↓できるのがインスタンスメソッド。

user.hoge

今のができず、↓するのがクラスメソッド。

User.hoge

どうしてTutorialでは使い分けているのか

User.digestやUser.new_tokenはユーザーオブジェクトが不要です。
つまり暗号化するだけ、トークンを作るだけの処理にユーザ情報は不要
必要なのは作ってからremember_digestで固有のユーザに代入するときだけ。
(だからrememberはインスタンスメソッドです)

それを明示的に示すためにクラスメソッドを使用したのです。

クラスメソッドはクラスそのものの変更や参照する役割にも使用される。
そのような場合にもクラスメソッドを使用する余地がありそう。

分かりやすい解説↓
【Ruby】クラスメソッドとインスタンスメソッドについてザクッと分かりやすく説明してみる
Rubyのクラスメソッドとインスタンスメソッドの例

クッキーに保存する

続いてIDとトークンをクッキーに保存する。
クッキーを保存するcookiesメソッドがあるので簡単。

クッキーの保存期間を20年にするpermanentメソッド、
IDは暗号化していないので暗号化するsignedメソッドも使用する。

cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:remember_token] = user.remember_token

そしてSessions内で保存までを一気に行うメソッドがあると便利。
だからここまで作ってきたメソッドをrememberメソッドとしてSessionsヘルパーに組み込もう。

app/helpers/sessions_helper.rb
module SessionsHelper

  def log_in(user)
  # 中略

  def remember(user)
    user.remember
    cookies.permanent.signed[:user_id] = user.id
    cookies.permanent[:remember_token] = user.remember_token
  end

  def current_user
    # 中略

さっき作ったusers.rbに作ったrememberメソッドとは違う。
メソッド内に入っているuser.rememberこそが、user.rbのrememberメソッドです。

たった今作ったSessionsヘルパーのrememberメソッドの方は、

  • remember_tokenとremember_digestと作成
  • IDとremember_tokenとクッキーに保存

これを一気に行うものなのですね。

クッキーのremember_tokenとUserモデル属性のremember_digestを照らし合わせる

じゃあSessionsコントローラに実装しよう。

def create
  user = User.find_by(email: params[:session][:email].downcase)
  if user && user.authenticate(params[:session][:password])
    log_in user
    remember user
    redirect_to user
  else
    # 中略
  end
end

これでひとまず新規ユーザ作成したらクッキーに保存されるようになった。
ただこのままだとcurrent_user(#8参照)がクッキーを参照しない。

そのためには、

  • 一時セッションの場合
  • 永続セッションの場合

これらを分けた上で処理を書く必要がある。

簡潔にまとめていただいている記事があるので、
35歳だけどRailsチュートリアルやってみた。[第4版 9章 9.1 Remember me 機能 まとめ&解答例]から内容をお借りします。

永続セッションの場合
session[:user_id]が存在すれば、一時セッションからユーザーを取得
cookies[:user_id]が存在すれば、永続セッションからユーザーを取得

if (user_id = session[:user_id])
  # 一時セッションからユーザーを取り出す
elsif (user_id = cookies[:user_id])
  # 永続セッションからユーザーを取り出す
  if # ユーザーが存在し、永続セッションの中の記憶トークンがDBの値と一致する
    # ログイン処理
  end
end

というわけで処理を書いていくわけだけど、

  • 記憶トークンがデータベース内の記憶ダイジェストの値が一致するか

このメソッドがない。
BCryptを参考にして書こう。

app/models/user.rb
class User < ApplicationRecord
  # 中略
  def authenticated?(remember_token)
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
  end
end

このメソッドの、
* 引数remember_tokenはあくまでただのローカル変数
* 引数remember_digestはUserモデルの属性(始めにmigrationで生成したもの)

なので混同しないよう注意が必要。

準備ができたのでcurrent_userを編集する。

app/helpers/sessions_helper.rb
module SessionsHelper
# 中略
  def current_user
    if (user_id = session[:user_id])
      @current_user ||= User.find_by(id: user_id)
    elsif (user_id = cookies.signed[:user_id])
      user = User.find_by(id: user_id)
      if user && user.authenticated?(cookies[:remember_token])
        log_in user
        @current_user = user
      end
    end
  end
# 中略

これで永続セッションのログインが完了になる。

永続セッションからログアウトする

今の状態だと、ログアウトが正しく機能しない。
log_outメソッドが一時セッションにしか対応していないからだ。
実装の手順はこう。

  1. Userモデルにremember_digestを破棄するメソッドを定義
  2. Sessionsヘルパーに実際のログアウトを行うメソッドを定義

というわけで実装しよう。

remember_digestを破棄するメソッドを定義する

remember_digestにnilを代入することでログイン情報を破棄できる。
それをforgetメソッドとして定義。

app/models/user.rb
class User < ApplicationRecord
  # 中略
  def forget
    update_attribute(:remember_digest, nil)
  end
end

実際にログアウトを行うメソッドを定義

Sessionsヘルパーのlog_outメソッドに永続セッションを破棄する処理を追加。
その処理にforgetメソッドを新たに定義する。

app/helpers/sessions_helper.rb
module SessionsHelper
# 中略
  def forget(user)
    user.forget
    cookies.delete(:user_id)
    cookies.delete(:remember_token)
  end

  def log_out
    forget(current_user)
    session.delete(:user_id)
    @current_user = nil
  end
end

Sessionsヘルパーのforgetメソッドもまた、Userモデルのforgetメソッドと異なるので注意。
(Userモデルのforgetメソッドは、Sessionsヘルパーのforgetメソッドの中のuser.forget)

これで一応ログアウトが可能になる。

細かなバグ修正

現状2つのバグが残っている。

  • 複数のタブで開いたサイトからログアウトが二重に行われる時のエラー
  • 複数のブラウザのうち1つはログアウト、もう1つはログアウトせず終了後再び画面を開く時のエラー

それぞれ対応しよう。

複数タブのエラー対処

エラーの理由:
1度目のログアウトでcurrent_userがnilなのにも関わらず、log_outでforget(current_user)しようと試みるから。

よって、ログインしているか確認した上でログアウトするように変更する。

app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  # 中略
  def destroy
    log_out if logged_in?
    redirect_to root_url
  end
end

複数ブラウザのエラー対処

エラーの理由:
1つ目のブラウザでremember_digestをnilしたのにも関わらず、もう1つのブラウザではクッキーが残っているので、authenticated?に例外が起こるから。

よってauthenticated?内でremember_digestがnilなら即座に認証を終了させる。

app/models/user.rb
class User < ApplicationRecord
  # 中略
  def authenticated?(remember_token)
    return false if remember_digest.nil?
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
  end
  # 中略

rubyの慣習的に、1文で済むif文は処理の後に書く。
よってreturn falseを先に、ifはその後。

これでようやくバグも取り除いた。

次回はリメンバーミー機能を実装

Tutorialは9.2 [Remember me] チェックボックス直前まできた。
次はリメンバーミー機能を実装する。
Tutorialの解説記事になっている?同じ工程だから仕方ない!!
(そのうちポートフォリオ感出てくることを信じて...:relaxed:

前回:#8 ログイン/ログアウト, FactroyBot編
次回:#10 リメンバーミー機能編

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker環境でAtomからRubocopを使う方法

この記事なんやねん

最近はDockerを使って環境構築をするケースが多いと思いますが、Atomなどのテキストエディタのコードフォーマットで使うRubocop系のライブラリを動作させるための日本語の記事が意外となかったので投稿します。

Atomのlinter-rubocopをDocker経由で動作させるのを目的に書いていきます。
docker-composeでも動きますよ。

手順

ここから早速始めていきましょう。因みに、以下の環境は揃っているという前提で進めます。

  • Dockerとdocker-composeをインストール済み
  • Atomをインストール済み
  • docker-composeを使ってRails環境を構築済み

もし、 環境構築まだやねん って状態だったら、こちらの記事を参考に開発環境を作ってみてください。

また、自分の使ってる.rubocop.ymlこちらになります。まだ用意してなければなければ使ってみてください。

ラッパースクリプトの配置

まずは、ラッパースクリプトを作成しましょう。
配置場所はどこでもいいですが、プロジェクト配下にbin/rubocopというパスで配置するのが分かりやすいかと思います。今回の例ではそうします。

docker execしていますがdocker runコマンドでも動作します。
ただ、execの方が早いので、コンテナを起動しておいてexecコマンドにてrubocopを叩く方が使い勝手がいいかと思います。
ここら辺はお好みで変えてください。

bin/rubocop
#!/bin/bash

CMD_ARGS=""
for arg in $@
do
if [ -f "$arg" ]
then
  CMD_ARGS="$CMD_ARGS ${arg#$PWD/}"
else
  CMD_ARGS="$CMD_ARGS $arg"
fi
done

docker exec -i <コンテナ名> rubocop $CMD_ARGS

<コンテナ名>にrubocopがインストールされてるコンテナ名を記載してください。docker-compose psコマンドで確認できます。
因みに、私の環境では下記のようにhr_web_1がrailsとrubocopがインストールされてるコンテナになります。

% dc ps
   Name                  Command                State     Ports
---------------------------------------------------------------
hr_chrome_1   /opt/bin/entry_point.sh          Exit 143        
hr_db_1       docker-entrypoint.sh postgres    Exit 0          
hr_web_1      bundle exec rails s -p 300 ...   Exit 1          

記述したら、chmod +x bin/rubocopで実行権限を付与するのを忘れないでください。

スクリプトの実行準備が整ったら、docker-compose up -dでコンテナを起動しておきましょう。

プラグインの設定

linter-rubocopからInstallボタンを押して、プラグインをAtomにインストールしてください。

次に、AtomのLinter Rubocopの設定画面から、Command欄に./bin/rubocopと入力してください。

rubocop.png

これで設定は終わりです。

まとめ

全てDocker内で完結できるとスッキリしていいですね。

因みに、いくつかRailsプロジェクトを掛け持ちしてると、あるプロジェクトではDocker使ってなくてローカルでRubocop使う場合もありますよね。そんな時はわざわざAtomの設定を変更しなくても、ラッパースクリプトの最後の行のコマンドをこのように変更すれば大丈夫です。

rubocop $CMD_ARGS

はい、単純にローカルにインストールしたrubocopを実行してるだけですね。

それではDockerと過ごすRubocopライフを楽しんでください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsのredirect_toとrenderの違い

redirect_toとは

redirect_toメソッドはHTTPレスポンスを返し、自動的にページを切り替えるメソッドになります。これによりRailsのコントローラーでURLにリダイレクトさせる処理を書くことが出来ます。

そもそもリダイレクトとは、指定されたURLに自動でアクセスするように返され、リダイレクト先のURLに永続的を示すステータスコード301か一時的を示すステータスコード307かを示すステータス情報を送信します。

renderとは

renderは文字列、HTML、XML、JSONなどを指定して出力し、主にRailsではコントローラーからviewを呼び出し、画面に表示します。このようにrederは表示するものを指定し画面に表示するメソッドになります。

redirect_toとrenderの違い

では実際にredirect_toとrenderがどのように違う働きをするのかを見ていきましょう。まず2つのメソッドが実際に画面に表示するまでの動きとして、

・render   : controller → view
・redirect_to : controller → URL → route → controller → view

上の図を見ると分かりますが、renderはコントローラーからそのまま画面を描画します。一方、redirect_toはブラウザでHTTPリクエストを受けたのと同じ処理がされます。

まとめ

単純に画面の遷移を行う場合はrenderを行い、HTTPリクエストがPOSTでデータを作成、更新、削除を行ったあとに画面を遷移する場合はredirect_toを使うというように使い分けると良いでしょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails技術者認定資格試験対策  Rails命名規則まとめ

数日後にRails技術者認定資格試験を受けるにあたって、基本的な部分ではありますがそういった基本を問うような問題も多いと思われるので、改めてRailsの命名規則をまとめたいと思います。

今回はコントーラーやモデルを仮にUserをベースに考えてみたいと思います。

Controller命名規則

Controllerは複数形になり、UsersControllerになります。この命名規則に従うとresourcesなどのデフォルトのルーティングジェネレータがそのまま利用出来て、名前のルールが決まっているとソースを確認しなくてもrouteファイルにどんどんルーティング情報を書いて行くことが出来ます。

Model命名規則

Modelは単数形になり、Userになります。モデルには作成するインスタンスの情報を定義する設計書のような役割があり、インスタンスは複数でも設計書は1つなので単数形となる、と覚えます。

Routingの命名規則

resources :users

複数のルーティングを一度に作成するため複数形と覚えると良いかもしれません。

データベース関連の命名規則

Railsと関連づけて、データベースの命名規則についても確認しますが、データベースに関してテーブル名、マイグレーションファイル名は複数形になります。データベースは基本的に複数のカラムを持つためテーブル名が複数形になり、そのテーブルを操作するマイグレーションファイルも複数形になると考えます。

最後に

命名規則は守らないと直ちにエラーが起こるという訳ではなく、実際にも意図があってコントローラー名が単数形になっていたりすることもありますが、せっかくRailsというシステムの恩恵を受けて効率良く開発が出来る部分が大きいと思いますので、よほどの意図がない限りは素直にRailsの命名規則には従った方が良いと思われます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails技術者認定資格試験対策 ActionPack

ActionPackとは

ActionPackはRailsの中でもかなり広範囲にわたる機能を提供している部分の1つです。主な機能としてはジェネレータ、セッションなどで、フラッシュ、ActionViewやキャッシュ機能などもActionPackが提供しています。

ジェネレータ

ActionPackが提供するジェネレータとしてcontrollerとscaffoldがあります。まずcontrollerのジェネレータコマンドとして、

rails g controller users

というコマンドを実行すると、

create app/controllers/user_controller.rb
invoke erb
create app/views/user
invoke test_unit
create test/controllers/user_controller_test.rb
invoke helper
create app/helpers/user_helper.rb
invoke test_unit
invoke assets
invoke coffee
create app/assets/javascripts/user.coffee
invoke scss
create app/assets/stylesheets/user.scss

というように大量のファイルが作成されます。もしこのコマンドを取り消したい時には、以下のコマンドを実行して下さい。

rails d(またはdestroy) controller users

続いてscaffoldの場合を見ていきましょう。scaffoldをジェネレータで作成するコマンドは以下になります。

rails g scaffold entries

こちらのコマンドを実行すると、

invoke active_record
create db/migrate/20190802064905_create_entries.rb
create app/models/entry.rb
invoke test_unit
create test/models/entry_test.rb
create test/fixtures/entries.yml
invoke resource_route
route resources :entries
invoke scaffold_controller
create app/controllers/entries_controller.rb
invoke erb
create app/views/entries
create app/views/entries/index.html.erb
create app/views/entries/edit.html.erb
create app/views/entries/show.html.erb
create app/views/entries/new.html.erb
create app/views/entries/_form.html.erb
invoke test_unit
create test/controllers/entries_controller_test.rb
create test/system/entries_test.rb
invoke helper
create app/helpers/entries_helper.rb
invoke test_unit
invoke jbuilder
create app/views/entries/index.json.jbuilder
create app/views/entries/show.json.jbuilder
create app/views/entries/_entry.json.jbuilder
invoke assets
invoke coffee
create app/assets/javascripts/entries.coffee
invoke scss
create app/assets/stylesheets/entries.scss
invoke scss
create app/assets/stylesheets/scaffolds.scss

controllerをジェネレートした場合より、さらに生成されるファイルが多いです。増えたファイルとしてはモデルファイルや ビューファイルがindex、edit、show、new、_formなど複数生成されていることです。今までなんとなく使っていたrailsのジェネレータコマンドですが元々はActionPackで定義された機能だったのです。

セッション

セッションとはHTTPとは状態を保持できないステートレスですが、セッションを使うと状態を保持するステートフルに変更することが出来ます。主にログイン情報を保持したりすることにWebアプリケーションでは使われています。これにより、ログインしている状態であることを保持することが出来て、ログインを毎回全て行う手間から解放されます。

問題としてはセッションで情報を識別する際にセッションIDというものを使用するのですが、このセッションIDを第三者に盗まれてしまうと第三者が自分としてログインすることが可能になったり、セキュティ的には注意して実装する必要がある部分になります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Active Recordまとめ1

Active Recordとは

Active RecordとはMVCフレームワークにおけるM、モデルに当たる部分で、データとロジックを表します。データベースに保存されたデータは削除しない限りは永続的に保存されます。

ORMフレームワークとしてのActive Record

ORM=オブジェクト・リレーショナル・マッピングとはアプリケーションがもつオブジェクトをMySQLなどのリレーショナルデータベースのテーブルに繋げることが出来ます。これによってMySQLなどでSQL文を直接記述する必要がなくなり、Rubyでアクセスコードを書くだけでアプリケーションのオブジェクトをデーターベースに保存したり、読み出すことが出来るようになります。またActive Recordの役割として以下のような点もあります。

・モデル同士の関連付け

・関連付けられているモデル間の継承階層を表現

・データをデータベースで保存する前にRailsモデルファイル内でバリデーションを実行

Active Recordの命名規則

Railsのモデル名は単数形でUser、Newsのように表現するルールですが、テーブル名はusersやpostsなど複数形で名前を作るルールになっている。また語が複数の単語から成り立つ場合、user_advicesのようにアンダースコアで区切られます。

またデータベースのテーブルで使うカラム名の命名ルールとしては主キーの場合はidという名前のintegerカラムがテーブルを作成すると自動で作成され、外部キーを設定する場合は「テーブル名の単数形id」という名前のidを設定する決まりになっています。よってuseridやpost_id、user_advice_idといった名前のid名になります。

Active Recordでのデータの読み書き

Active Recordでのデータの読み書きはCRUDと呼ばれるデータベースを操作する「Create」「Read」「Update」「Delete」メソッドの頭文字を繋げた言葉で表わされます。

・Create …. createメソッドを実行すると新しいオブジェクトが返され、データベースが保存されます。(newメソッドと似ていますがnewメソッドは単にオブジェクトを返すだけです。)

・Read …. データベース内の情報にアクセス出来る。以下は主なアクセスメソッドです。

user = User.all                   # 全てのユーザーを返す

user = User.first                 # 最初のユーザーを返す

user = User.find_by(name: 'toda') # todaという名前の最初のユーザーを返す

・Update …. オブジェクトの情報を取得して更新することが出来ます。

・Delete …. オブジェクトの情報を取得して削除してデータベースから削除することが出来ます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails技術者認定資格試験対策 ActionView

始めに

Rails 技術者認定資格を受験するにあたって今回は試験内容であるActionViewの項目をRailsチュートリアルを中心に自分なりにまとめていきたいと思います。ActionViewという言葉は聞いたことがありますが、実際にどの部分がActionViewであるのかも含めて今回は調べていきます。

ActionViewとは

RailsにおけるWebリクエストはActionControllerとActionViewで扱われます。ActionViewはActionControllerがデータベース処理やCRUD(Create/Read/Update/Delete)アクションを実行したレスポンスをwebページに反映する役割になります。ActionViewはテンプレートとしてデフォルトではERBを使用してビューコードを書くことが出来ます。またgemを組み合わせればERBだけでなく、Slimテンプレートを使用することが出来たりします。

あと自分が実際にActionViewという言葉をよく見かけていたのが、ビューファイルでの入力フォームをスッキリ書けるようにサポートしてくれるtext_field、datetime_selectメソッドなどのフォームヘルパーです。また文字列や日付にもヘルパーが提供されていて、ビューを現在のロケールによって、言語や日時などのローカライズを簡単に行うことが出来ます。またこれらのヘルパーは自分のアプリケーションに応じて機能拡大して独自のヘルパーを作成出来ます。

テンプレート、パーシャル、テンプレート

ActionViewが提供してくれるビューファイルの要素として、テンプレート、パーシャル、レイアウトがあり、ビューファイルはこれら3つの要素から成り立ちます。

テンプレート....ERBやSlimなどのテンプレートエンジンを利用してビューファイルの中にRubyコードでロジックを埋め込むことが出来ます。テンプレートの種類は拡張子で判断され、.erbや.slimの拡張子でRailsでどのテンプレートを使用しているかを表しています。

パーシャル....ビューファイルが肥大化するのを防ぐためにビューを部分的に分割して、複数のビューファイルで使い回すことも出来ます。よくある例としてはヘッダーやフッター、フォームなどをパーシャルとして分割したりします。またパーシャルファイル名はform.html.erbのように一文字目をにすることでパーシャルとして認識され、他のビューファイル内で以下のように記述して呼び出すことが出来ます。

<%= render "form" %>

レイアウト....複数のページで共有のデフォルトデザインを表現するための仕組みで、同じようなデザインの大枠を使い回すことが出来ます。デフォルトではapplicationビューファイルがテンプレートになりますが、applicationをテンプレートとして使いたくない場合は、変更したいページのコントローラー内の上部で、以下のように指定します。

layout 'admin_page'

最後に

以上が大まかにはなりますが、ActionViewの全体的な役割になります。実際にはActionViewのフォームヘルパーを使う機会が多いはずですので、業務でよく使用している主要なフォームヘルパーの紹介などを、また別の記事で紹介したいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む