PublicIPをEC2インスタンスに紐付ける

AWSを使いこなしたいと思い、基本からおさらい中です。

前回EC2インスタンスの作成方法について書いたので
前回作成したEC2インスタンスをPublicIPアドレスに紐づける方法を書き留めておきたいと思います。

新しいパブリックIPアドレスを作る

ネットワーク上のコンピューターはIPアドレスで識別することができるのですが、
AWSで元々割り当てられているIPアドレスは、起動するたびに変化してしまいます。
そのため、固定のIPアドレスを紐付けてやる必要があります。

マネジメントコンソールの左側に、「Elastic IP」の文字があるのでクリックします。
f:id:atsukofu:20201004174541p:plain


「新しいアドレスの割り当て」をクリックします。
f:id:atsukofu:20201004174648p:plain

関連づけるか聞かれるのので、「関連づける」ボタンを押しましょう。


次にLinux仮想マシンに関連づけてやります。
「アクション」と書いてあるプルダウン をクリックし、「アドレスの関連づけ」をクリックします。
f:id:atsukofu:20201004175057p:plain


ポップアップが表示されるので、前回作成したLinux仮想マシンインスタンスIDを選択し、
先ほど作ったElasticIPアドレスと紐付けます。
f:id:atsukofu:20201004175211p:plain


うまく結びつくと、画面左のメニューから「インスタンス」を選択すると、
インスタンスのパブリックIPアドレスが、先ほど紐づけたものに置き換わっていることが確認できます。
次にセキュリティグループを設定するので、セキュリティグループの「launch-wizard-2」をクリックします。
f:id:atsukofu:20201004175658p:plain


「アクション」のプルダウン を押し、「インバウンドルールの編集」を選択します。
f:id:atsukofu:20201004175731p:plain


ポップアップが表示されます。
「ルールの追加」をクリックし、「HTTP」を選択します。
ここでHTTPを選択しないと、デフォルトでSSH接続しかできないようになっているので注意です。
f:id:atsukofu:20201004175853p:plain

とりあえずこれで紐付け完了。


まとめ

前回やったの全然覚えてないな・・何回かやったら覚えられるのかな・・

とにかくまずはEC2とS3をマスターするために頑張ります!

EC2インスタンスの作り方

最近AWSを本格的に勉強したいと思い、
書籍とpaizaラーニングで学習しています。
AWSについてはプログラミングスクールでもデプロイに使用した際に軽く触れたのですが、
正直全然理解できませんでした。

でも実はたくさん機能があって素晴らしいサービスだ!との記事をたくさん拝見しまして、
そんなに素晴らしいなら仲良くなりたい!と思い
AWSを味方につけるべく学習を決意しました。
書籍では使い方まで詳しく説明しているものが少ないようなので、
今回学んだ使い方を記録しておきたいと思います。

環境:AWSへのアカウント登録を終えている。
   東京リージョンに登録している。


インスタンスって何?

AWSクラウドに作る仮想サーバーのことをいいます。
何度でも同じ構成の仮想サーバーを作ることができるので、インスタンスというそうです。
モデルとインスタンスの関係と同じってことですかね。
ちなみにインスタンスの元になるモデルの役割をしているものを、「AMI」といいます。

EC2インスタンスの作成

AWSマネジメントコンソールにログインし、
左上の「サービス」から「EC2」を選び、左端のメニュー群から
「EC2ダッシュボード」を選択する。
インスタンスの作成」という青いボタンをクリック。

f:id:atsukofu:20200927164753p:plain


するとステップ1 AMIの選択画面が表示されますので、
Amazon Linux AMI 2018.03.0 (HVM), SSD Volume Type」  を選択します。

f:id:atsukofu:20200927165003p:plain

今度はステップ2 インスタンスのタイプを聞かれますので、t2.microを選択します。

f:id:atsukofu:20200927165254p:plain

次はいきなりステップ7に飛んで、確認画面が出ますので、「起動」をクリックします。

f:id:atsukofu:20200927165447p:plain

するとキーペアのダウンロードのためのポップアップが出ますので、
「新しいキーペアの作成」を選択し、キーペア名を入力し、ダウンロードします。
このキーペア名と、ダウンロードファイルは、あとでインスタンスに接続するときの鍵として使用しますので、
キーペア名は控えておき、ファイルはわかりやすい場所に保存しておきます。

f:id:atsukofu:20200927170131p:plain

キーペアのダウンロード後、「インスタンスの作成」ボタンが押せるようになりますので
それを押せばインスタンスの作成自体は完了です!

f:id:atsukofu:20200927170240p:plain


まとめ

インスタンスの作成自体は、2回目なので簡単に行うことができました!
また、インスタンス、AMI、キーペアの役割に関しても整理しながら学べたと思います。

次回はElasticIPをインスタンスに紐付けて、インスタンスに接続・操作してみたいと思います!

herokuでデプロイしてみた!

TECH::CAMPの最終課題発表会が終わり、卒業しました。

早いもんでもう半年か・・・という感じです。

 

TECH::CAMPは卒業しましたが、私のプログラミング人生は始まったばかり。

これからもっと頑張ります💪!!

 

現在ポートフォリオとして、PHPを使ったWebサイトを作成しています。

書籍を使用して学習しているのですが、

本番環境への移行を教えてくれる書籍がなく、

ネットで調べるぞーと思ったら、Railsに比べてPHPでのデプロイの記事が少ない。

laravelは結構あるんですが・・

まる1日ぐらいかかってしまいましたが、なんとかデプロイとDB紐付けまでできたので、

記録したいと思います。

開発環境

  • 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 login
herokuのホームページがブラウザで開きますので、ログインします。

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つ目を書きたいと思います。

 


 

今回の記事の内容

テストコード を描けるようになった後でも結構詰まってしまった、
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
$ 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
$ 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
email 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

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の中に記入します)

gem 'rspec-rails'
gem 'factory_bot_rails'

 

また、これは後からダウンロードしてみたのですが

gem 'faker', "~> 2.8"

一部のモデルにしかまだ使用していません。(理解不足なので練習中)

 

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

RSpec.configure do |config|
config.include FactoryBot::Syntax::Methods ←これを追加
~省略〜
end

上記を記載すると、

user = FactoryBot.build(:user)

 と記載していたのを

user = build(:user)

と省略記法で書けるようになります。

たくさんテストを書くことを想定すると、大文字小文字が混ざった記載を省けるのはありがたい!

 

ではFactoryBotで擬似的なユーザー、プロジェクト、コードを作成します。

 factories/user.rb

FactoryBot.define do

factory :user do
password = Faker::Internet.password(min_length:8)
username {Faker::Internet.username(15)}
email {Faker::Internet.email}
password {password}
password_confirmation {password}
end

end

とりあえずuserのみFakerを使用しています。

まず、passwordを最初に定義しているのは、ユーザー登録時に2回同じものを登録する必要があるからです。

usernameは15文字を超えるとバリデーションが作動して、テスト中にエラーになることがあるため、15文字指定にしています。(max_length: 15にするとエラーが出たんです・・・undefined methodだって。なんでや)

 

Fakerを使うと何がいいかというと、ランダムな文字の組み合わせで必要な情報を作成してくれることです。

テスト用の名前とかメールアドレスって、単純なもの(aaa@gmail.comとか)を作りがちですが、Fakerを使うと毎回違うダミーを作成してくれるので、厳密なテストが可能になります。

 

テストコード を書いてみる

ようやく本題のrspecのコードを書いていきます。

specディレクトリ配下にmodelsディレクトリを作成し、

その中にuser_spec.rbファイルを作成します。

そしてまずは

require 'rails_helper'
describe Project do
end

のみを記載し、テストを実行してみます。

テストの実行コマンドは

$ bundle exec rspec

だけでも良いですが、たくさんファイルが増えてきた場合は

bundle exec rspec spec/models/user_spec.rb

という風にファイルを指定してやると良いです。

上記のコードだけだと、条件を指定していないので、

1 example, 0 failures

とでて、無条件にテストを通過します。

 

ではテストの条件を書いていきます。

require 'rails_helper'
describe Project do
context "userを保存できない場合" do
it "usernameがないと登録できないこと" do
user = build(:user, username:nil)
user.valid?
expect(user.errors[:username]).to include("can't be blank")
end
 
it "usernameが16文字以上だと登録できないこと" do
user = build(:user, username: "abcdefghijklmnop")
user.valid?
expect(user.errors[:username]).to include("is too long (maximum is 15 characters)")
end
 
it "emailが他ユーザーと重複していると登録できないこと" do
user = create(:user, email: "aaa@email.com")
another_user = build(:user, email: "aaa@email.com")
another_user.valid?
expect(another_user.errors[:email]).to include("has already been taken"
)
end
 
it "emailがないと登録できないこと" do
user = build(:user, email:nil)
user.valid?
expect(user.errors[:email]).to include("can't be blank", "is invalid")
end
 
it "emailに「-_.」以外の記号があると登録できないこと" do
user = build(:user, email:"aaa:@aaa.com")
user.valid?
expect(user.errors[:email]).to include("is invalid")
end
 
it "passwordがないと登録できないこと" do
user = build(:user, password:nil)
user.valid?
expect(user.errors[:password]).to include("can't be blank")
end
 
it "passwordが8文字以下であれば登録できないこと" do
user = build(:user, password: "abcdefg")
user.valid?
expect(user.errors[:password]).to include("is too short (minimum is 8 characters)")
end
 
it "passwordとpassword_confirmationが異なると登録できないこと" do
user = build(:user, password_confirmation: "00000001")
user.valid?
expect(user.errors[:password_confirmation]).to include("doesn't match Password")
end
end
 
context "userを保存できる場合" do
it "usernameが15文字以内であれば登録できること" do
expect(build(:user, username: "abcdefghijklmno")).to be_valid
end
 
it "passwordが8文字以上であれば登録できること" do
expect(build
(:user, password: "00000000", password_confirmation: "00000000"))
.to be_valid
end
 
it "username, email, passwordが存在すれば登録できること" do
expect(build(:user)).to be_valid
end
 
it "emailに「-_.」の記号が入っていても登録できること" do
expect(build(:user, email: "a-a_a.a@aaa.com")).to be_valid
end
end
end

 

 

userを保存するときの様々な条件を想定して書いていきます。

先にバリデーションをかけた内容について書くとわかりやすいかなと思います。

 

簡単に解説を。

it "usernameがないと登録できないこと" do
user = build(:user, username:nil)
user.valid?
expect(user.errors[:username]).to include("can't be blank")
end

例えばこれだと、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"が出ていますよ、という意味です。

この一連の流れをテストで仮定して実行し、確認しているわけです。

 

上記の流れを当てはめて、他の条件についても書いていきます。

 

 

「登録できる場合」に関しては、

コードの記載は簡単です。

it "usernameが15文字以内であれば登録できること" do
expect(build(:user, username: "abcdefghijklmno")).to be_valid
end

エラー文が出ないような条件を与えてやり、 be_validマッチャ(バリデーションをクリアする)を書いてやります。

 

 

まとめ 

正直最初にテストコード をみたときは、

テストコード ってなんか意味あるの?ぐらいの考えでした。

ですが自分でアプリを作っているうちに、

いろいろな条件でバリデーションを考えるようになり、

バリデーションの動作を確認できるテストコード の意味がようやくわかってきました。

 

 

特にFakerでランダムに文字列を与えてくれるのはとてもありがたい機能だと思います。

ただしここで問題が。

emailには特定の記号しか含んではいけないというバリデーションをかけています。

Fakerの方には「この記号を含んではいけない」という規制をかけていないため、

たまにuserを生成できないメールアドレスを作ってくれちゃう時があります。

なので何回かテストしてると、通ったり通らなかったり。

Faker に規制をかける方法があるらしいので、後日調べて実装しようと思います。

 

また、もう一つ課題が。

重複したemailが登録できないことを確認するテストで、

ターミナルで確認したエラー文でテストを実行すると通らないという事態です。

テスト実行時には、ターミナルには「エラー文は空白のはずやで」と出ます。

なので一旦空白にしているのですが、これは解決しないとなあと思っていますので、

解決できたらまた更新したいと思っています。

→解決できました!

最初にcreateで作ったユーザーがのemailが、既にローカルのデータベースにあるemailと重複してしまっていたので、そもそも最初にダミーで入れるはずだったuserがcreateできてなかったみたい? です。

メールアドレスを別のものにしたらテスト通りました。

 

 

サインイン機能にバリデーションを追加

TECH::CAMPを卒業するまで残すところ後1ヶ月。

平日は帰宅後、休日は1日中最終課題と個人アプリ作成を進める日々です。

 

今日は、前回実装した、deviseを使用しないサインイン・サインアップ機能に、

バリデーションを追加していこうと思います。

 

Usersテーブルは

・username

・email

・password_digest

で成り立っています。(created_at などは省略)

以下のようにバリデーションをかけます。

 

・username

 入力必須

 15文字以内であること

 

・email

 入力必須

 一意性あり

 メールアドレスの形になっていること

 

・password

 入力必須

 8文字以上であること

 

 

実際のバリデーションコード

 

入力必須は簡単です。モデル(user.rb)に

validates :username, presence: true

 

と書けば、username は入力必須になります。

 

上記の続きで文字数を制限するには

validates :username, presence: true, length: {maximum: 15}

という風に記入します。

 

次にメールアドレスの一意性

validates :email,uniqueness: {case_sensitive: false},

大文字小文字の区別なく一意でなくてはならないということです。

(今回のアプリでは保存時にメールアドレスを小文字に変換するため)

 

次にメールアドレスの形になっているかどうか。

こちらは正規表現を用います。(勉強中・・)

validates :email,
presence: true,
uniqueness: {case_sensitive: false},
format: {with: /\A[\w+\-_.]+@[a-z\d\-_.]+\.[a-z]+\z/i}

\w ・・・英数字

+   ・・・直前の文字が1回以上繰り返される

\-_.   ・・・「-」「_」「.」の記号は使用できる

i  ・・・大文字小文字を区別しないというオプション 

 

次はこのバリデーション 達についてのテストコード を書いていきます。

 

 

まとめ

バリデーションについては、正規表現が一番難しいと思いました。

正規表現理解できればフォーム作成の幅が広がりそう!

しかも、理系っぽくてかっこいい!(わたしは文系)

ので、しっかり勉強しようと思います。

 

ただ、サイト色々探しては見たもののサンプルコードがワンパターンでどうもなあ・・

お盆休みはステイホームなのでAMAZON正規表現の本があるかどうか探してみます。