DK’s diary

プログラミング初学者による発信

pメソッド、putsメソッド、printメソッドの違い

背景

Rubyアルゴリズムを勉強するのに様々な書籍やWeb上の記事を参考にしているが、
出力にp、puts、printと見る記事によって異なっていたのでそれぞれどう違うのか調べてみた。


改行の有無

pメソッド

p 'これは'
p 'pメソッド'

結果

"これは"
"pメソッド"

putsメソッド

puts 'これは'
puts 'putsメソッド'

結果

これは
putsメソッド

printメソッド

print 'これは'
print 'printメソッド'

結果

これはprintメソッド


まとめると、

  • 改行有り

 ・pメソッド
 ・putsメソッド

  • 改行無し

 ・printメソッド


になります。
ここで気になるところが、、、
pメソッドで出力した際にダブルクォーテーションが付いたままになっています。
次の章で説明します!


pメソッドにおける型情報の出力

上記のようにpメソッドで出力した際にダブルクォーテーションが付いているのは、
型情報も一緒に出力しているからです。
つまり、ダブルクォーテーションで囲むことで文字列であることを表しています。
数値だとこれはついてきません。

pメソッド

p 'これはpメソッド'
p 20200625

結果

"これはpメソッド"
20200625
変数の扱い

これについてはあまり差異は無いかと思いますが、
強いて挙げるなら、上記で説明したようにpメソッドでは型情報も出力されることでしょうか・・・。
今回は一気に見ていきましょう。

apple = 'りんご'
juice = 'ジュース'
 
print apple, juice
puts apple, juice
p apple, juice


結果

りんごジュースりんご
ジュース
"りんご"
"ジュース"


上記のように、
printメソッドは改行しないのでputsメソッドで最初に出力したappleは1段目に出力されています。
また、pメソッドは型情報も出力するのでダブルクォーテーションがついています。
変数を引数に渡すだけで出力することができることはどれも同じですね!


まとめ
  • pメソッド

 ・改行をする
 ・引数と引数の型情報(文字列or数値)も一緒に出力する

  • putsメソッド

 ・改行する
 ・引数を出力する

  • printメソッド

 ・改行しない
 ・引数を出力する

deviseにおけるパスワードの文字数変更方法

いきなり余談だが

railsによるアプリケーション開発でdeviseによる会員登録機能を搭載すると、パスワード数はデフォルトで6文字以上となっている。
どうですか?短くないですか?笑
私は、ほとんどのwebサイトのパスワードが8文字以上になっている印象を受けます。
なので自分で作ったアプリも8文字以上にしようと思い調べてみました!


と、その前に。。。
本当に8文字以上で良いのか気になったので、
まず現代で推奨されるパスワードの最低文字数について調べました。


結論から述べると、15文字以上らしい(長っ!!)
これはFBIが提言しているもので、大文字と小文字の使用や記号の使用は必須としないみたい。


ちなみに、
7文字のパスワードは、ハッキングソフトウェアを使って0.29ミリ秒で破られてしまい、これが12文字になると200年近くかかる計算。そして24文字になると1800万年以上かかるらしい。


引用元
https://www.itmedia.co.jp/news/articles/2003/02/news061.html


変更方法

本題に戻りパスワードの長さの変更方法です。
FBIは15文字以上を推奨していますが少し長すぎるので、現在の慣例である8文字以上に設定することにします。


config > initializers > devise.rb ファイル内の記述を変更することで反映されます。
コメントアウトで、パスワードの長さの範囲と書かれているところの下です。

config > initializers > devise.rb

# ==> Configuration for :validatable
# Range for password length.
config.password_length = 6..128    ← これ

「6..128」が6文字〜128文字ということなので、
最初の数字を8にしてみました。

config > initializers > devise.rb

config.password_length = 8..128


これで設定が変更されているはずですが、
念の為アプリで確かめてみました!


6文字で登録しようとしたところ、、、


f:id:dkdkdk3:20200616182051p:plain


ちゃんと弾いてくれました!

VSCode内でのDraw.ioの使用

Draw.ioとは

web上でフローチャートやER図を描けるアプリケーションです。
予め図形や矢印などのテンプレートが用意されているので、
あとは文字を打ち込んだり矢印で関係性を示したりするだけで済みます。


f:id:dkdkdk3:20200613140536p:plain




私はER図作成のために利用しました。
通っていたプログラミングスクールでチーム開発があったのですが、コロナ禍ということで完全リモートでの開発となりました。
最初は紙に描いていたのですが、チーム全員と共有する必要があった為このアプリを使用することにし、
シンプルで直感的に扱える為、簡単に作成することができました!

そのDraw.ioがVSCodeでも扱えるようになったと聞き、どんな感じなのか調べてみることにしました。



VSCodeに Draw.ioの導入

f:id:dkdkdk3:20200613150033p:plain


VSCode拡張機能窓を選択

②「Draw.io Integration」と検索

③インストール!!


以上で導入完了です!さあさあこれから描いていきましょう!



使用方法

拡張子を.drawioか.dioでファイルを作成すると自動的にDraw.ioの画面になります。
今回はtest.dioで作成しました。

f:id:dkdkdk3:20200613150729p:plain


あとはファイル左側のテンプレートからドラッグ&ドロップでどんどん作成していくだけです。


https://i.gyazo.com/715856294c496a615ae6649a5864c6ff.gif


用意されている図形が以前より少ないように感じますが、
下部の "その他の図形" を選択し、必要なものを追加することで利用できるようになりました。

f:id:dkdkdk3:20200613153321p:plain



f:id:dkdkdk3:20200613152915p:plain



f:id:dkdkdk3:20200613153434p:plain

また、図形を検索することでリストに追加しなくても使用することはできます。


使用感については、 web版に比べてぬるぬる動くのでより使いやすくなった印象です!


*devise使用:ログイン済みのユーザーのみをアクセス可能にする設定

遷移先へのボタンを隠すだけではダメ

大部分のアプリケーションでは会員登録機能がついており、登録してログイン済みでなければそのアプリのメイン機能を使用できないようになっているかと思います。

私が作成しているアプリでもログイン済みか否かによってナビバーの表示を変更し、ログイン済みでなければ先に進めないようにしてあります。

ログイン前↓
f:id:dkdkdk3:20200611215751p:plain



ログイン後↓
f:id:dkdkdk3:20200611220112p:plain



これはif文で分岐させることで実装できます。
deviseのヘルパーメソッドであるuser_signed_in?を用いてユーザーがログイン済みかどうかを判定します。

<% if user_signed_in? %>
  <li class="nav-item">
    <%= link_to "#{current_user.username}さんのマイページ", user_path(current_user.id) %>
  </li>
  <li class="nav-item">
    <%= link_to "助っ人を探す", maps_path %>
  </li>
  <li class="nav-item">
    <%= link_to "チャット", "#" %>
  </li>
  <li class="nav-item">
    <%= link_to "ログアウト", destroy_user_session_path, method: "delete" %>
  </li>
<% else %>
  <li class="nav-item">
    <%= link_to "会員登録", new_user_registration_path %>
  </li>
  <li class="nav-item">
    <%= link_to "ログイン", new_user_session_path %>
  </li>
<% end %>


未ログインではボタンそのものが無いのでアクセスできないと思いきや、
実はURLを直打ちすることで飛べてしまいます。

先程の私のアプリの例で、未ログインのまま "助っ人を探す" のページに飛んでみます!
f:id:dkdkdk3:20200611223235p:plain


直接URLを打ち込んでと。。。

f:id:dkdkdk3:20200611224014p:plain

飛べてしまいました。
添付画像の右上を見てもらえばわかりますが未ログインのままです。
対策をしましょう!!


before_actionで対策

当該のコントローラにおいてすべてのアクションで実行の前に共通の処理を行いたいときに、before_actionを使用すると全てのアクションが実行される前に指定したメソッドを呼び出すことができます。

つまり上記の例でいうと、
マップを表示させているmapsコントローラーはユーザーがログイン済みでないとアクションが実行されないように設定すれば良いのです。

app > controllers > maps_controller.rb

class MapsController < ApplicationController
  before_action :login_check

  # 省略

  private
  def login_check
    unless user_signed_in?
      redirect_to root_path
    end
  end
end

before_actionでlogin_checkというメソッドを定義します。

メソッドを書くのはprivate以下です。
上記のナビバーと同様にuser_signed_in?を使用します。
ユーザーがログインしてなければ "root_path"(TOPページ)にリダイレクトするようにしました。
これで未ログインのユーザーが直打ちで侵入することは防げました。

が、しかし・・・
もっと簡単な方法がありました。

deviseのヘルパーメソッドの一つに
before_action :authenticate_user!
があり、

app > controllers > maps_controller.rb

# authenticate_user!適用前
class MapsController < ApplicationController
  before_action :login_check

    # 省略

  private
  def login_check
    unless user_signed_in?
      redirect_to root_path
    end
  end
end


#authenticate_user!適用後
class MapsController < ApplicationController
  before_action :authenticate_user!

    # 省略

end

上記のように1行で済みます!
会員登録にdeviseを使用していたら断然こっちですね!

ちなみに
デフォルトの設定ではログインしていない場合、ログイン画面に遷移する仕様になっています。
変更したい場合は下記のように上書きをすれば指定ページにリダイレクトされました。

app > controllers > maps_controller.rb

class MapsController < ApplicationController
  before_action :authenticate_user!

    # 省略

  private
  def authenticate_user!
    redirect_to root_path
  end
end

Capistranoによる自動デプロイ設定後に環境変数が読み込まれない件

Caspitranoでデプロイの自動化をすると、~/.bash_profileが読み込まれない

Caspitranoは自動デプロイツールの一種であり、
これを利用することによって、デプロイ時に必要なコマンド操作が1回で済むようになります。

先日個人のアプリを本番環境にデプロイ後、
アップデートを容易に行えるようにCaspitranoを導入しました。
導入後、本番環境のページを確認するとGoogleマップを表示させるページでエラーが発生。(泣)
ログを確認したところAPIキーが読み込まれていなかった。
確かに ~/.bash_profileに書いたはずなのですが。。。

色々調査をしてみると、
Caspitaranoを使用する本番環境では~/.bash_profileは読み込まれないとのこと。


対処法

Capistranoは、~/.bash_profileを読み込まないようにプログラムされているのでetc/environmentを使用します。

# 環境変数書き込み
$ sudo vim /etc/environment

ここに環境変数を記述すればOKです!
この後、環境変数を反映させるために一度接続を切ってから確認します。

# 接続を切断
$ exit
# EC2インスタンスに接続
$ ssh -i ...
# 環境変数が設定できているかの確認
$ env | grep ...

Rspecによるテスト:外部キーが設定されている際のモデルテスト

扱うモデルについて

メッセージ投稿に関するMessageモデルを例に説明していきます!
私の作成しているアプリのmessagesテーブルは以下のようになっており、
user_idとroom_idの2つを外部キーとして設定しています。
また、空での投稿を防ぐためにcontentにバリデーションをかけています。

Column Type Options
------- ------- -----------------
user_id integer foreign_key: true
room_id integer foreign_key: true
content string null: false
image string
外部キーを考慮せずにテストを行うと・・・

今回は"contentとimageがあれば投稿できる"という項目でテストを行います。
factory_botを使ってテンプレートを作成しました。
factory_botの説明についてはこちら↓↓
https://dkdkdk3.hatenablog.com/entry/2020/06/07/005240

spec > factories > messages.rb

FactoryBot.define do
  factory :message do
    content  {"テストです。"}
    image    {"test.jpg"}
  end
end


テストについては以下のように記述しました。
spec > models > message_spec.rb

require 'rails_helper'
describe Message do
  describe '#create' do
    it "contentとimageがあれば投稿できる" do
      message = build(:message)
      expect(message).to be_valid
    end
  end
end


この内容でテストを実行すると、

 1) Message#create contentとimageがあれば投稿できる
     Failure/Error: expect(message).to be_valid
       expected #<Message id: nil, content: "テストです。", image: "test.jpg", user_id: nil, created_at: nil, updated_at: nil, room_id: nil> to be valid, but got errors: Userを入力してください, Roomを入力してください

とテストは通りません。
最下部に、"got errors: Userを入力してください, Roomを入力してください"とあります。
MessageモデルにはUserとRoomモデルを外部キーに設定しており、
これらが無いためにエラーを起こしています。


テストにおける外部キーの設定方法

Messageモデルのテスト用にUserとRoomの情報をfactory_botで作成しました。
Roomに関しては個人間でチャットをするための部屋を用意しただけなので、カラムは作成していません。
spec > factories > users.rb

FactoryBot.define do
  factory :user do
    username              {"test"}
    email                 {"test@gmail.com"}
    password              {"000000"}
    password_confirmation {"000000"}
  end
end

spec > factories > rooms.rb

FactoryBot.define do
  factory :room do
  end
end


外部キーを使用するための準備はできました!
テストを書いていきましょう!
spec > models > message_spec.rb

require 'rails_helper'
describe Message do
  describe '#create' do
    it "contentとimageがあれば投稿できる" do
      user = create(:user)
      room = create(:room)
      message = build(:message, user_id: user.id, room_id: room.id)
      expect(message).to be_valid
    end
  end
end

UserとRoomの情報をcreateメソッドであらかじめ登録します。
一時的にテスト用のDBに保存しておく必要があるためです。
*buildメソッドだとDBに登録されないのでテストは通りません。

そのあとにMessageの情報を呼び出して外部キーの設定も記述します。
*この記述でテストが終わるのでbuildメソッドで大丈夫です!

Message
  #create
    contentとimageがあれば投稿できる

Finished in 1.12 seconds (files took 1.59 seconds to load)
1 example, 0 failures

これでテストが通るようになりました!


ちなみに試行錯誤している途中で、
Messageのfactory_botに外部キーの情報を記述してもテストは通りました。
spec > factories > messages.rb

FactoryBot.define do
  factory :message do
    content  {"テストです。"}
    image    {"test.jpg"}
    user     {build(:user)}
    room     {build(:room)}
  end
end

テストの記述はこれです。
spec > models > message_spec.rb

require 'rails_helper'
describe Message do
  describe '#create' do
    it "contentとimageがあれば投稿できる" do
      message = build(:message)
      expect(message).to be_valid
    end
  end
end

こっちでもテストは通るのですが、
テストを記述するファイル(spec > models > message_spec.rb)に外部キーの情報を書いておらず、
全体像が把握できないのであまり良い書き方では無いのかなと思います。

Rspecによるテスト:factory_botによるインスタンスの生成

factory_botとは

Rspecでテストを書いていて、
一回一回対象のインスタンスを生成(User.newのところ)するのは面倒です。
こんな感じで↓↓(例はUserモデルの単体テスト

require 'rails_helper'
describe User do
  describe '#create' do
    it "usernameがないと登録できない" do
      user = User.new(username: "", email: "test@test.com", password: "111111", password_confirmation: "111111")
      user.valid?
      expect(user.errors[:username]).to include("が入力されていません。")
    end

    it "emailがないと登録できない" do
      user = User.new(username: "テストさん", email: "", password: "111111", password_confirmation: "111111")
      user.valid?
      expect(user.errors[:email]).to include("が入力されていません。")
    end
  end
end

そこで便利なのが今回の主役 "factory_bot"
予めインスタンスの情報を記述しておけば、
User.newでいちいちインスタンスを生成せずに、
buildやcreateといったメソッドで簡単に呼び出すことができます。
(buildとcreateについては後述します。)


factory_botの導入

まずGemfileに以下を記述します。

Gemfile

gem 'factory_bot_rails'


bundle install を忘れずに!

次にファイルの作成です。
specディレクトリ直下にfactoriesというフォルダを作成し、
その配下にそれぞれのファイルを置いていくというのが慣例みたいです。
今回の例であればusers.rbファイルを新規作成します。
中身を書いていきます。

spec > factories > users.rb

FactoryBot.define do
  factory :user do
    username              {"テストさん"}
    email                 {"test@test.com"}
    password              {"111111"}
    password_confirmation {"111111"}
  end
end

これであとは呼び出すだけです。


factory_botの呼び出し
user = FactoryBot.build(:user)

作成したインスタンスは上記の記述で生成されます。
今回はbuildメソッドですが、createメソッドでも生成されます。
createメソッドの場合は一時的にテスト用のDBにデータが保存されます。
例えば一意性の確認の時に、最初にcreateメソッドで1人目を登録しておくことで、
2人目が同じ内容を登録した時にバリデーションが働いているかを確認することができます。


そして、FactoryBotという記述を省略することができます。
そのためにはrails_helper.rbファイルに以下を記述しましょう。
このファイルはRspecrailsにインストール後、
ターミナルにて"rails g rspec:install" で生成されるファイルの一つです。

spec > rails_helper.rb

RSpec.configure do |config|
# 下記を追記
  config.include FactoryBot::Syntax::Methods

最終的にはこうなります。

# factory_bot導入前
user = User.new(username: "テストさん", email: "test@test.com", password: "111111", password_confirmation: "111111")

# factory_bot導入後
user = build(:user)

かなりスリムになりました!


ちなみに、特定の箇所を変更したい場合はこう記述します。

user = build(:user, username: "")

これでusernameが空のインスタンスが生成されることになります。