PublicIPをEC2インスタンスに紐付ける
AWSを使いこなしたいと思い、基本からおさらい中です。
前回EC2インスタンスの作成方法について書いたので
前回作成したEC2インスタンスをPublicIPアドレスに紐づける方法を書き留めておきたいと思います。
新しいパブリックIPアドレスを作る
ネットワーク上のコンピューターはIPアドレスで識別することができるのですが、AWSで元々割り当てられているIPアドレスは、起動するたびに変化してしまいます。
そのため、固定のIPアドレスを紐付けてやる必要があります。
マネジメントコンソールの左側に、「Elastic IP」の文字があるのでクリックします。
「新しいアドレスの割り当て」をクリックします。
関連づけるか聞かれるのので、「関連づける」ボタンを押しましょう。
次にLinux仮想マシンに関連づけてやります。
「アクション」と書いてあるプルダウン をクリックし、「アドレスの関連づけ」をクリックします。
ポップアップが表示されるので、前回作成したLinux仮想マシンのインスタンスIDを選択し、
先ほど作ったElasticIPアドレスと紐付けます。
うまく結びつくと、画面左のメニューから「インスタンス」を選択すると、
インスタンスのパブリックIPアドレスが、先ほど紐づけたものに置き換わっていることが確認できます。
次にセキュリティグループを設定するので、セキュリティグループの「launch-wizard-2」をクリックします。
「アクション」のプルダウン を押し、「インバウンドルールの編集」を選択します。
ポップアップが表示されます。
「ルールの追加」をクリックし、「HTTP」を選択します。
ここでHTTPを選択しないと、デフォルトでSSH接続しかできないようになっているので注意です。
とりあえずこれで紐付け完了。
まとめ
前回やったの全然覚えてないな・・何回かやったら覚えられるのかな・・とにかくまずはEC2とS3をマスターするために頑張ります!
EC2インスタンスの作り方
最近AWSを本格的に勉強したいと思い、
書籍とpaizaラーニングで学習しています。
AWSについてはプログラミングスクールでもデプロイに使用した際に軽く触れたのですが、
正直全然理解できませんでした。
でも実はたくさん機能があって素晴らしいサービスだ!との記事をたくさん拝見しまして、
そんなに素晴らしいなら仲良くなりたい!と思い
AWSを味方につけるべく学習を決意しました。
書籍では使い方まで詳しく説明しているものが少ないようなので、
今回学んだ使い方を記録しておきたいと思います。
環境:AWSへのアカウント登録を終えている。
東京リージョンに登録している。
インスタンスって何?
AWSクラウドに作る仮想サーバーのことをいいます。何度でも同じ構成の仮想サーバーを作ることができるので、インスタンスというそうです。
モデルとインスタンスの関係と同じってことですかね。
ちなみにインスタンスの元になるモデルの役割をしているものを、「AMI」といいます。
EC2インスタンスの作成
AWSマネジメントコンソールにログインし、
左上の「サービス」から「EC2」を選び、左端のメニュー群から
「EC2ダッシュボード」を選択する。
「インスタンスの作成」という青いボタンをクリック。
するとステップ1 AMIの選択画面が表示されますので、
「Amazon Linux AMI 2018.03.0 (HVM), SSD Volume Type」 を選択します。
今度はステップ2 インスタンスのタイプを聞かれますので、t2.microを選択します。
次はいきなりステップ7に飛んで、確認画面が出ますので、「起動」をクリックします。
するとキーペアのダウンロードのためのポップアップが出ますので、
「新しいキーペアの作成」を選択し、キーペア名を入力し、ダウンロードします。
このキーペア名と、ダウンロードファイルは、あとでインスタンスに接続するときの鍵として使用しますので、
キーペア名は控えておき、ファイルはわかりやすい場所に保存しておきます。
キーペアのダウンロード後、「インスタンスの作成」ボタンが押せるようになりますので
それを押せばインスタンスの作成自体は完了です!
まとめ
インスタンスの作成自体は、2回目なので簡単に行うことができました!
また、インスタンス、AMI、キーペアの役割に関しても整理しながら学べたと思います。
herokuでデプロイしてみた!
TECH::CAMPの最終課題発表会が終わり、卒業しました。
早いもんでもう半年か・・・という感じです。
TECH::CAMPは卒業しましたが、私のプログラミング人生は始まったばかり。
これからもっと頑張ります💪!!
現在ポートフォリオとして、PHPを使ったWebサイトを作成しています。
書籍を使用して学習しているのですが、
本番環境への移行を教えてくれる書籍がなく、
ネットで調べるぞーと思ったら、Railsに比べてPHPでのデプロイの記事が少ない。
laravelは結構あるんですが・・
まる1日ぐらいかかってしまいましたが、なんとかデプロイとDB紐付けまでできたので、
記録したいと思います。
参考サイト
https://qiita.com/kazukimatsumoto/items/a0daa7281a3948701c39
https://qiita.com/nanshi99/items/baad35db1d1cde1fdba2
https://qiita.com/ak25562983/items/c6bd1ec88e06a1f7f954
開発環境
- PHP7.0
- MySQL(PHPMyadmin)
- XAMMP
- Git紐付け済み
- herokuユーザー登録済み
Gitとherokuを紐付け
めちゃめちゃ時間かかりました。
Railsでやっていた時はコマンドでコードを保存しているディレクトリにいって操作していたので、
今回も同じだろう。よし、コマンド打って移動だ。
Githubに紐付けているディレクトリはXAMPP内のhtdocsに保存しているから・・
ん?このディレクトリってどういう位置にいるんだ?!?!
XAMPPの仮想サーバーってどうやってコマンドでアクセスするの?!?!?
色々試してみましたが、どこにいるのかわからない・・
いろんな記事を見ていると、「本番用のディレクトリ」という記述があったので、
みんな仮想サーバーとは別のところに保存してるのか?!ミスったああああ!!!
でもGithub紐付けちゃってるし・・・
解決→GithubDesktopからアクセスできるんです!!!!(GithubDesktop派です)
Githubデスクトップの左上、「current repository」のプルダウン をクリック
↓
対象のリポジトリを右クリック
↓
「open in terminal」の文字があります!
これをクリックすると・・・ターミナルが開いて、XAMPPの仮想サーバーにアクセスできています!!!
やった!!!
herokuで登録されていない方はこちらで登録を済ませてください。
https://signup.heroku.com/login
登録したら、cilをダウンロード
https://devcenter.heroku.com/articles/heroku-cli
私はMacなので下記コマンドを打ってダウンロードしました。
brew tap heroku/brew && brew install heroku
パスを通してください、と出たら下記のコマンドを打ちます。
$echo 'PATH="/usr/local/heroku/bin:$PATH"' >> ~/.profile
下記コマンドを打って、herokuのバージョンが出てきたら、ダウンロード成功です!
$heroku --version
>>heroku/7.0.47 darwin-x64 node-v10.1.0
herokuでデプロイ!
$ heroku loginherokuのホームページがブラウザで開きますので、ログインします。
heroku上に新しいアプリを作成するコマンドを打ちます。
現在ターミナルでアクセスしているディレクトリをherokuに認識させ、デプロイ時のアプリ名をつけてあげるという仕組みです。
$ heroku apps:create アプリの名前
$ git remote -v
このコマンドでheroku、gitのアプリの名前が表示されればOK。
$ heroku addons:create cleardb:ignite
データベースを切り替え
ローカルで開発しているときは、PHPMyadminというところからMySQLに保存したデータをみていたと思います。
本番環境では本番環境用にDBを切り替えてやる必要があります。
herokuではデフォルトでpostagreSQLというのを使用する設定になっているので、
herokuのclearDB MySQLというアドオンをインストールする必要があります。
下記コマンドを打ち込むか、herokuのHPからもインストールすることが出来ます。
$ heroku addons:create cleardb:ignite※因みにこのアドオンはherokuにクレジットカードを登録しないと使用できません。
freeプランでも、形式上登録の必要がありますので、登録してからコマンドを使用しましょう!
clearDBのインストールが終わったら、下記コマンドでclearDBのURLを確認することが出来ます。
この内容はclearDBに接続する際にも使用しますので、内容を確認しておきます。
$ heroku config
こんな感じで表示されます。
CLEARDB_DATABASE_URL = mysql://ユーザ名:パスワード@ホスト名/DB名?reconnect=true
次に、DBを可視化できるworkbenchというものをダウンロードします。
下記HPからダウンロードできます。
https://www.mysql.com/jp/products/workbench/
workbenchが立ち上がったら、「MySQLConnections」の横辺りにある「+」から、下記の通り入力します。
ConnectionName:任意のDB名
HostName:ホスト名
UserName:ユーザー名
Default Schema:DB名
Password:パスワード
入力したら、TestConnectionボタンをクリックします。
次に、PHPMyadminで作成した、テーブル・カラムとその中身をエクスポート→clearDBにインポートします。
PHPMyadminにアクセスし、出力したいDBを選択→
画面上のタブから「エクスポート」を選択→
エクスポート方法の「詳細」を選択し、必要なテーブルが全て選択されているか確認→実行ボタン
次にMySQLworkbenchからインポートします。
さっきConnectionをしたDBから、Administrationタブ選択→
DataImport/Restoreを選択→
DefaultTargetSchema→インポートしたDBを選択。ない場合はnewschema
Inport from self contained file を選択→インポートしたいファイルを選択し、「実行」をクリック。
インポートを確認するには
Administratetionの右の「schema」タブをクリックし、tablesをクリックすると開発環境で作成したテーブルが表示されていればOK!
見たいテーブルのうえにポインタを乗せると、テーブルのアイコンが出てくるので、
それをクリックすればテーブルの中身が見れます!
本番環境用にデータベース関係の記述を書き換え
下記ファイルを用意し、sqlに接続する必要のあるページでrequireを使用して読み込むようにします。
また、開発環境用のSQL文はコメントアウトするなどして、使えないようにしておきます。
<?php $host = getenv('DB_HOSTNAME'); //MySQLがインストールされてるコンピュータ $dbname = getenv('DB_NAME'); //使用するDB $charset = "utf8"; //文字コード $user = getenv('DB_USERNAME'); //MySQLにログインするユーザー名 $password = getenv('DB_PASSWORD'); //ユーザーのパスワード $options = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, //SQLでエラーが表示された場合、画面にエラーが出力される PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, //DBから取得したデータを連想配列の形式で取得する PDO::ATTR_EMULATE_PREPARES => false, //SQLインジェクション対策 ]; //DBへの接続設定 <?php $host = getenv('DB_HOSTNAME'); //MySQLがインストールされてるコンピュータ $dbname = getenv('DB_NAME'); //使用するDB $charset = "utf8"; //文字コード $user = getenv('DB_USERNAME'); //MySQLにログインするユーザー名 $password = getenv('DB_PASSWORD'); //ユーザーのパスワード $options = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, //SQLでエラーが表示された場合、画面にエラーが出力される PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, //DBから取得したデータを連想配列の形式で取得する PDO::ATTR_EMULATE_PREPARES => false, //SQLインジェクション対策 ]; //DBへの接続設定 <?php $host = getenv('DB_HOSTNAME'); //MySQLがインストールされてるコンピュータ $dbname = getenv('DB_NAME'); //使用するDB $charset = "utf8"; //文字コード $user = getenv('DB_USERNAME'); //MySQLにログインするユーザー名 $password = getenv('DB_PASSWORD'); //ユーザーのパスワード $options = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, //SQLでエラーが表示された場合、画面にエラーが出力される PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, //DBから取得したデータを連想配列の形式で取得する PDO::ATTR_EMULATE_PREPARES => false, //SQLインジェクション対策 ]; //DBへの接続設定 $dsn = "mysql:host=$host;dbname=$dbname;charset=$charset"; try { //DBへ接続 $dbh = new PDO($dsn, $user, $password, $options); } catch (\PDOException $e) { throw new \PDOException($e->getMessage(), (int) $e->getCode()); }
require_once('dbconnect.php');
ここまできたら、masterブランチにマージ。
herokuでデプロイします。
$ git push heroku masterページを開きます。
$ heroku open
課題
デプロイするのにやることが多すぎて、うろ覚え部分がかなりある。
何回もやれば覚えられるのか・・・
後、DB接続の部分、本番環境と開発環境でコメントアウトつけたり外したり、外し忘れたり・・
なんてもさいことしているんだ、と思う。
切り替える方法がきっとあるはずなので、調べたいと思います!
Rspecでテストコード を書く③ 〜2つのテーブルを同時に保存編〜
TECH::CAMP卒業まで残すところあと2週間となりました。
入学当初はできなかった事が、継続する事で本当にいろいろできるようになったと思っています。
できるようになったことは、できるだけアウトプット!
ということで、本日はテストコード の記事3つ目を書きたいと思います。
- 今回の記事の内容
- 参考記事
- 作業の前に(前回の復習)
- DB design
- itemモデルのバリデーション
- カテゴリーのFactoryBot、rspecの書き方
- imagesテーブルの処理の仕方
- テストコード
- まとめ
今回の記事の内容
テストコード を描けるようになった後でも結構詰まってしまった、2つのテーブルを同時保存するときのテストです。
メルカリのクローンサイトを作っているのですが、商品の出品時に、下記の2テーブルにデータを同時に保存しなければなりません。
- itemsテーブル(カテゴリーも保存しなければならない)
- imagesテーブル(itemsテーブル保存時に同時に保存しなければならない)
カテゴリーはgem 'ancestry'を使用しており、
親、子、孫カテゴリーを作成しているのですが、
itemsテーブルには必ず孫カテゴリーを保存するような設計にしているので、
そこも実現したい。
参考記事
下記記事を参考にさせていただきました!!!【Rails、RSpec】Ancestryを用いている時のFactoryBotの作り方 - Qiita
世界一わかりやすい!!複数画像投稿時のRSpecテストの書き方 | 目指せ、スーパーエンジニア
作業の前に(前回の復習)
- gemのインストール
group :test do gem 'rspec-rails' gem 'factory_bot_rails' gem 'faker', "~> 2.8" gem 'rails-controller-testing' #コントローラーのテストをする場合 end
- specディレクトリの作成(コマンド)
$ rails g spec:install
- .rspec ファイルへの追記
--format documentation
- rails.helper.rb ファイルへの追記
RSpec.configure do |config| config.include FactoryBot::Syntax::Methods #ここを追記 #省略 end
DB design
DB設計は下記の通り。アソシエーションも記載しています。
- itemsテーブル
Column | Type | Options |
------ | ---- | ------- |
name | string | null: false |
price | integer | null :false |
content | text | null :false |
category_id | references | null: false, foreign_key: true |
condition | integer | null: false |
brand | string | |
size | string | |
preparation_day | integer | null: false |
postage_type | integer | null: false |
postage_payer | integer | null: false |
seller_id | references | null: false, foreign_key: true |
buyer_id | references | foreign_key: true |
prefecture_id(active_hash) | integer | null: false |
- Association
- belongs_to :user
- belongs_to :category
- has_many :images
- imagesテーブル
Column | Type | Options |
------ | ---- | ------- |
item_id | references | null: false, foreign_key: true |
image_url | string | null: false |
- Association
- belongs_to :item
- categoriesテーブル
Column | Type | Options |
------ | ---- | ------- |
name | string | null: false |
ancestry | string |
- Association
- has_many :items
itemモデルのバリデーション
validates_associated :images validates :name, presence: true, length: { maximum: 40 } validates :content, presence: true, length: { maximum: 1000 } validates :category_id, presence: true validates :condition, presence: true validates :postage_payer, presence: true validates :postage_type, presence: true validates :prefecture_id, presence: true validates :preparation_day, presence: true validates :price, presence: true validates :seller_id, presence: true validate :images_number //カスタムバリデーション。画像は1個以上10個以下存在しなければならない。 def images_number errors.add(:images, "を1つ以上指定してください") if images.size < 1 errors.add(:images, "は10個までです") if images.size > 10 end
カテゴリーのFactoryBot、rspecの書き方
カテゴリーは、一旦親カテゴリーをFactoryBotで作ってやります。(ancestryカラムがnilなのが親カテゴリ)
FactoryBot.define do factory :category do name {"category"} ancestry {nil} end end ||<< 次にitemsテーブルのFactoryBotのなかで、ancestryで使えるメソッドを使用して 孫カテゴリを作り、保存するという動作を書きます。 >|ruby| FactoryBot.define do factory :item do name {"商品"} content {"good"} brand {"NIKE"} condition {1} postage_payer {1} postage_type {1} prefecture_id {1} preparation_day {1} price {1} buyer_id {} //出品時は空なので空にしています after(:build) do |item| parent_category = create(:category) child_category = parent_category.children.create(name: "child") grand_child_category = child_category.children.create(name: "grandchild") item.category_id = grand_child_category.id end end end
ここで初めて使ったのが、after(:build) という書き方。
rspecでitemモデルをbuildした後に加える動作を指示できるようです。
親カテゴリモデルを作成
↓
子カテゴリモデルを作成
↓
孫カテゴリモデルを作成
↓
孫カテゴリのidをitemsテーブルのcategory_idカラムに保存
という流れを記載しています。
imagesテーブルの処理の仕方
imagesテーブルのFactoryBotファイルは作りません。imagesテーブルの場合は、ダミーの画像をspec/fixturesディレクトリに保存しておき、
Rack::Test::UploadedFileモジュールで呼び出してやります。
その上で、カテゴリーと同様、itemモデルをを(:build)するときに、imageモデルも(:build)し、
item.imagesという配列に入れてやるという処理をします。
FactoryBot.define do factory :item do name {"商品"} content {"good"} brand {"NIKE"} condition {1} postage_payer {1} postage_type {1} prefecture_id {1} preparation_day {1} price {1} buyer_id {} //出品時は空なので空にしています after(:build) do |item| parent_category = create(:category) child_category = parent_category.children.create(name: "hello") grand_child_category = child_category.children.create(name: "world") item.category_id = grand_child_category.id item.images << FactoryBot.build(:image, item_id: item.id) end end factory :image do image_url { Rack::Test::UploadedFile.new(File.join(Rails.root, "spec/fixtures/sample.png"), 'image/png') } end end
これで準備完了!!
テストコード
カテゴリーと画像関連以外は省略しています。require 'rails_helper' describe Item do describe "#create" do let(:user) { create(:user) } let(:item) {build(:item, seller_id: user.id)} //seller_idが空やでとエラーが出たので常に足すようにしています。 it "category_idがないと出品できないこと" do item.category_id = nil item.valid? expect(item.errors[:category_id]).to include("を入力してください") end it "imageがないと出品できないこと" do item.images = [] //item.imagesの配列が空であるということにしています。 item.valid? expect(item.errors[:images]).to include("を1つ以上指定してください") end it "imageが10を超えると出品できないこと" do 10.times {|num| item.images << build(:image, item_id: item.id) //itemsモデルのFactoryBotで行った配列への画像挿入を、追加で10回行っています。 } item.valid? expect(item.errors[:images]).to include("は10個までです") end end
これで無事にテストが通りました・・・!!
まとめ
テストをする事で、つけ忘れているバリデーション に気づく、なんていう順番が逆じゃないかと言われそうなこともありました。
ですが、つけ忘れたバリデーション に気づくのも、きっとテストコード をかく役割なんじゃないかと思っています!
実際、最初にテストコード を書いて、それが通るようなコードを書くという方の記事を見つけました。
このコードをメンターさんに見てもらった時に、
ここまで書ける人は受講生の中でもなかなかいませんよ〜と言っていただき、
苦労が報われた気がして泣きそうになりました( ; ; )
最初わからない事でも、諦めずに頑張ればできるようになる!
これが本当にプログラミングの魅力・楽しみであると思います。
さらに世の中に役立つ物を作れれば、最高ですね!!!
卒業間近ですが、これからも楽しむことを忘れずに学習を続けていきたいと思います。
Rspecでテストコード を書く② 〜テストの準備とProjectモデル編〜
TECH::CAMP卒業まであと20日となりました。
今日は、前回書いたテストコード についての記事の続きを書こうと思います。
前回テストを通したuserモデルとアソシエーションを組んでいる、projectモデルというものをテストしようと思います。
作業の前に
- gemのインストール
group :test do gem 'rspec-rails' gem 'factory_bot_rails' gem 'faker', "~> 2.8" gem 'rails-controller-testing' #コントローラーのテストをする場合 end
- specディレクトリの作成(コマンド)
$ rails g spec:install
- .rspec ファイルへの追記
--format documentation
- rails.helper.rb ファイルへの追記
RSpec.configure do |config| config.include FactoryBot::Syntax::Methods #ここを追記 #省略 end
DB design
DB設計は下記の通り。アソシエーションも記載しています。
- users table
Column | Type | Options |
username | string | null: false |
string | null: false, unique: true | |
password_digest | string | null: false |
- Association
- has many projects
- projects table
Column | Type | Options |
name | string | null: false |
user_id | integer | null: false, foreign_ley: true |
- Association
- belongs_to user
- belongs_to user
FactoryBotの準備
アソシエーションからわかることとして、
projectはuserに依存している = userの情報がないと、projectが作られないのです。
これはテストの世界でも一緒です。
なので、「projectを保存できる」という条件でテストを通すためには、
事前にFactoryBotで、userの情報と、userの情報を入れたprojectの情報を作ってやる必要があります。
- spec/factories/user.rb
FactoryBot.define do factory :user do password = Faker::Internet.password(min_length:8) username {"username"} email {Faker::Internet.email} password {password} password_confirmation {password} end end
- spec/factories/project.rb
FactoryBot.define do factory :project do name {"project"} user end end
projectモデルのFactoryBotに上記のように「user」と記載することで、
FactoryBotで作ったuserの情報が入るようになっています。
ただ、ユーザーの情報が入るカラムが2つあったりする場合は、この方法だとうまくいきませんでした。
その場合はprojectsモデルのFactoryBotにはuserとは書かず、
rspecで
user = create(:user) project = build(:project, user_id: user.id)
という風に、userを生成のうえ、そのユーザーはprojectsテーブルのどのカラムに入るのかを指定してやるとうまくいきました。
rspecを書く
では、rspecのテストコードを書いていきましょう!require 'rails_helper' describe Project do describe '#create' do it "nameがないと登録できないこと" do project = build(:project, name: nil) project.valid? expect(project.errors[:name]).to include("can't be blank") end it "nameが31文字以上だと登録できないこと" do project = build(:project, name: "abcdefghijklmnopqrstuvwxyzabcde") project.valid? expect(project.errors[:name]).to include("is too long (maximum is 30 characters)") end it "user_idがないと登録できないこと" do project = build(:project, user_id: nil) project.valid? expect(project.errors[:user]).to include("must exist") end it "name,user_id があれば登録できること" do expect(build(:project)).to be_valid end it "nameが30文字以内であれば登録できること" do expect(build(:project, name: "abcdefghijklmnopqrstuvwxyzabcd")).to be_valid end end end
上記テストですが、userがないとエラーになってしまいます。
実際の環境でもuserがないとprojectsが保存できないのですから、
テストでしっかり反映されているということですね。
まとめ
前回userモデルのテストを行いましたが、今回アソシエーションを組んでいるモデルのテストを書いたことで、
rspecの理解が深まりました!
わかってくるとテスト楽しい!
rspecに関する書籍がってあまり見つからない(電子書籍はありました!)ため、
学んだことはしっかりと書き留めて忘れた時に見直せるようにしておきたいです。
Rspecでテストコード を書く① 〜テストの準備とUserモデル編〜
TECH::CAMP卒業まであと26日になりました。
1ヶ月を切ると焦りを覚えてきますが、
やるべき優先順位を自分なりに考えながら勉強していこうと思う今日この頃です。
TECH::CAMPのカリキュラム内では、「???」しか浮かばなかったテストコード ですが、
テストは大事!と言われているので、今回は個人アプリでテストコード を練習した記録を書いていこうと思います。
テスト用のgemをダウンロード
テストコード の記載に使う下記のgemをダウンロードしました。
(Gemfileのgroup: test do ~ endの中に記入します)
また、これは後からダウンロードしてみたのですが
一部のモデルにしかまだ使用していません。(理解不足なので練習中)
Rspecを使う準備
gem 'rspec'は、gemをインストールするだけでは使えません。
まずは専用のディレクトリを作ります。
コマンドで
$ rails g spec:install
と打ち込むと、ターミナルに下記のように表示されます。
create .rspec create spec create spec/spec_helper.rb create spec/rails_helper.rb
4つファイル(ディレクトリも含む)作ったでーと言われました。
.rspec(Gemfileの3つぐらい上にある)に下記を追記します。
--format documentation
FactoryBotを使う準備
※FactoryBotを使うと、rspecで使う擬似的なユーザーや投稿などを、FactoryBotのファイルにまとめて記載しrspecで使うことができます。
ここで作られるユーザーや投稿はあくまでテスト用のものであるため、
sequelProなどで保存を確認することはできません。
(確認するときはbinding.pryで動作を止めて確認します)
たくさんテストを書くときには、同じようなコードをたくさん書かずに済むため重宝するgemです。
spec/rails_helper.rb
上記を記載すると、
user = FactoryBot.build(:user)
と記載していたのを
user = build(:user)
と省略記法で書けるようになります。
たくさんテストを書くことを想定すると、大文字小文字が混ざった記載を省けるのはありがたい!
ではFactoryBotで擬似的なユーザー、プロジェクト、コードを作成します。
factories/user.rb
とりあえずuserのみFakerを使用しています。
まず、passwordを最初に定義しているのは、ユーザー登録時に2回同じものを登録する必要があるからです。
usernameは15文字を超えるとバリデーションが作動して、テスト中にエラーになることがあるため、15文字指定にしています。(max_length: 15にするとエラーが出たんです・・・undefined methodだって。なんでや)
Fakerを使うと何がいいかというと、ランダムな文字の組み合わせで必要な情報を作成してくれることです。
テスト用の名前とかメールアドレスって、単純なもの(aaa@gmail.comとか)を作りがちですが、Fakerを使うと毎回違うダミーを作成してくれるので、厳密なテストが可能になります。
テストコード を書いてみる
ようやく本題のrspecのコードを書いていきます。
specディレクトリ配下にmodelsディレクトリを作成し、
その中にuser_spec.rbファイルを作成します。
そしてまずは
のみを記載し、テストを実行してみます。
テストの実行コマンドは
$ bundle exec rspec
だけでも良いですが、たくさんファイルが増えてきた場合は
bundle exec rspec spec/models/user_spec.rb
という風にファイルを指定してやると良いです。
上記のコードだけだと、条件を指定していないので、
1 example, 0 failures
とでて、無条件にテストを通過します。
ではテストの条件を書いていきます。
)
userを保存するときの様々な条件を想定して書いていきます。
先にバリデーションをかけた内容について書くとわかりやすいかなと思います。
簡単に解説を。
例えばこれだと、2行目で保存したいユーザーを定義しています。
ユーザーはFactoryBotで定義したものを使うのですが、
「build(:user)」 に追加で「username: nil」を入れることで
FactoryBotで作ったusernameをなしにすることができます。
ターミナルでいうと、下記のようなコマンドを打ったことになります。
user = User.new(
username: nil,
email: "Fakerで作ったデタラメアドレス",
password: "Fakerで作ったデタラメパスワード",
password_confirmation: "passwordに入れたものと同じ文字列")
上記をいちいち書かなくてよくて、欲しい条件だけ上書きすればいいのですから、
FactoryBotとFaker超便利!ってことですね〜
そして3行目の「valid?」は「マッチャ」と呼ばれる条件を示しています。
valid?は「バリデーションされれば」
4行目のincludeは「含んでいれば」
と読み替えることができます。
3行目のexpect(~~~).to は、望まれる内容(こう返ってくるだろうなという予想)を記入します。
実際にターミナルで、先ほど入力したuser=User.new(~~)を打ち込み、
確認してみます。(rails c でコンソールにしてから実行します)
user = User.new(
username: nil,
email: "email@email.com",
password: "12345678",
password_confirmation: "12345678")
下記のようにuserが定義されます。
=> #<User:0x00007f900ced95b0
id: nil,
username: nil,
email: "email@email.com",
password_digest: "$2a$12$KilRDXdXFXnfqM.9POfILeHaf4S.eiIkepb8GQqphOoSs55RN9zBi",
created_at: nil,
updated_at: nil>
そこで下記のコマンドを打つと
user.valid?
ターミナルに下記のように表示されます。
User Exists (15.5ms) SELECT 1 AS one FROM `users` WHERE `users`.`email` = 'email@email.com' LIMIT 1
=> false
「false」 と出ました。
バリデーションがかかっていると、falseになります。
バリデーション にかからないuserを定義するとtrueになります。
次に下記コマンドを打つと、バリデーション にかかった際のエラー文を教えてくれます。
user.errors
下記のように出ました。
=> #<ActiveModel::Errors:0x00007f900d13bb28
@base=
#<User:0x00007f900ced95b0
id: nil,
username: nil,
email: "email@email.com",
password_digest: "$2a$12$KilRDXdXFXnfqM.9POfILeHaf4S.eiIkepb8GQqphOoSs55RN9zBi",
created_at: nil,
updated_at: nil>,
@details={:username=>[{:error=>:blank}]},
@messages={:username=>["can't be blank"]}>
赤字の部分が大事!
usernameに対してエラー文"can't be blank"が出ていますよ、という意味です。
この一連の流れをテストで仮定して実行し、確認しているわけです。
上記の流れを当てはめて、他の条件についても書いていきます。
「登録できる場合」に関しては、
コードの記載は簡単です。
エラー文が出ないような条件を与えてやり、 be_validマッチャ(バリデーションをクリアする)を書いてやります。
まとめ
正直最初にテストコード をみたときは、
テストコード ってなんか意味あるの?ぐらいの考えでした。
ですが自分でアプリを作っているうちに、
いろいろな条件でバリデーションを考えるようになり、
バリデーションの動作を確認できるテストコード の意味がようやくわかってきました。
特にFakerでランダムに文字列を与えてくれるのはとてもありがたい機能だと思います。
ただしここで問題が。
emailには特定の記号しか含んではいけないというバリデーションをかけています。
Fakerの方には「この記号を含んではいけない」という規制をかけていないため、
たまにuserを生成できないメールアドレスを作ってくれちゃう時があります。
なので何回かテストしてると、通ったり通らなかったり。
Faker に規制をかける方法があるらしいので、後日調べて実装しようと思います。
また、もう一つ課題が。
重複したemailが登録できないことを確認するテストで、
ターミナルで確認したエラー文でテストを実行すると通らないという事態です。
テスト実行時には、ターミナルには「エラー文は空白のはずやで」と出ます。
なので一旦空白にしているのですが、これは解決しないとなあと思っていますので、
解決できたらまた更新したいと思っています。
→解決できました!
最初にcreateで作ったユーザーがのemailが、既にローカルのデータベースにあるemailと重複してしまっていたので、そもそも最初にダミーで入れるはずだったuserがcreateできてなかったみたい? です。
メールアドレスを別のものにしたらテスト通りました。
サインイン機能にバリデーションを追加
TECH::CAMPを卒業するまで残すところ後1ヶ月。
平日は帰宅後、休日は1日中最終課題と個人アプリ作成を進める日々です。
今日は、前回実装した、deviseを使用しないサインイン・サインアップ機能に、
バリデーションを追加していこうと思います。
Usersテーブルは
・username
・password_digest
で成り立っています。(created_at などは省略)
以下のようにバリデーションをかけます。
・username
入力必須
15文字以内であること
入力必須
一意性あり
メールアドレスの形になっていること
・password
入力必須
8文字以上であること
実際のバリデーションコード
入力必須は簡単です。モデル(user.rb)に
と書けば、username は入力必須になります。
上記の続きで文字数を制限するには
という風に記入します。
次にメールアドレスの一意性
大文字小文字の区別なく一意でなくてはならないということです。
(今回のアプリでは保存時にメールアドレスを小文字に変換するため)
次にメールアドレスの形になっているかどうか。
こちらは正規表現を用います。(勉強中・・)
\w ・・・英数字
+ ・・・直前の文字が1回以上繰り返される
\-_. ・・・「-」「_」「.」の記号は使用できる
i ・・・大文字小文字を区別しないというオプション
次はこのバリデーション 達についてのテストコード を書いていきます。
まとめ
バリデーションについては、正規表現が一番難しいと思いました。
正規表現理解できればフォーム作成の幅が広がりそう!
しかも、理系っぽくてかっこいい!(わたしは文系)
ので、しっかり勉強しようと思います。
ただ、サイト色々探しては見たもののサンプルコードがワンパターンでどうもなあ・・
お盆休みはステイホームなのでAMAZONで正規表現の本があるかどうか探してみます。