Ruby学習メモ ブロック
<ブロック>
・ブロックはメソッドの引数として渡すことができる処理のかたまりである。ブロック内で記述した処理は必要に応じてメソッドから呼び出される。
numbers = [1, 2, 3 4]
sum = 0
numbers.each do |n|
sum += n
end
sum #=> 10
eachメソッドの役割は配列の要素を最初から最後まで順番に取り出すこと。しかし、取り出した要素をどう扱うのかは、その時の要件で変化する。
配列の要素を順番に取り出す作業はeachメソッドで行い、その要素をどう扱うかはブロックに記述する。上のコードでいうとdoからendとなる。
|n|のnはブロック引数と呼ばれ、eachメソッドから渡された配列の要素が入る。上のコードでは、nには1, 2, 3, 4が順番に渡される。
ブロックの内部では、sum += nように自由にRubyのコードを書くことができ、変数sumに配列の各要素nを加算するコードが書かれている。
<配列の要素を削除する条件を自由に指定する>
・delete_ifメソッドを使って、配列から奇数だけを削除するコードの例
a = [1, 2, 3, 1, 2, 3]
a.delete_if do |n|
n.odd?
end
a #=> [2, 2]
delete_ifメソッドもeachメソッドと同じように、配列の要素を順番に取り出し、そしてその要素をブロックに渡す。
delete_ifメソッドはブロックの戻り値をチェックする。その戻り値が真であれば、ブロックに渡した要素配列から削除し、偽であれば配列に残したままにする。
ブロックの戻り値はメソッドと同様、最後に評価された式となる。
delete_ifメソッドは、「配列の要素を順番に取り出すこと」と「ブロックの戻り値が真であれば要素を削除すること」という共通処理を提供している。
「要件を問わず共通する処理」はメソッド自身に、「要件によって異なる処理」はブロックにそれぞれ分担させる。
<ブロック引数とブロック内の変数>
ブロック引数のnはnでなくてもよく、引数の名前は自由に決めれる。
numbers = [1, 2, 3 4]
sum = 0
numbers.each do |n|
sum += n
end
# ブロック引数の名前は何でも良い。
numbers.each do |element|
sum += element
end
sum #=> 10
・偶数のみ、値を10倍にしてから加算するコードの例。
numbers = [1, 2, 3, 4]
sum = 0
numbers.each do |n|
sum_value = n.even? ? n * 10 : n
sum += sum_value
end
sum #=> 64
sum_valueはブロック内で初めて登場した変数であり、このような変数のスコープ(有効範囲)はブロック内部のみとなる。ブロックの外でsum_valueを参照するとエラーが発生する。
sumはブロックの外で作成されたので、ブロックの内部でも参照可能である。
・ブロック引数の名前をブロックの外にある変数の名前と同じにすると、ブロック内ではブロック引数の値が優先して参照される。(名前の重複により、他の変数やメソッドが参照できなくなることをシャドーイングという。)
numbers = [1, 2, 3, 4]
sum = 0
sum_value = 100
numbers.each do |sum_value|
sum += sum_value
end
sum #=> 10
<do…endと{ }>
Rubyの文法上、改行を入れなくてもブロックは作動する。
numbers = [1, 2, 3, 4]
sum = 0
numbers.each do |n| sum += n end
sum #=> 10
・do…endを使用する代わりに、{ }で囲んでもブロックを作成できる。
numbers = [1, 2, 3, 4]
sum = 0
numbers.each { |n| sum+= n }
sum #=> 10
・do…endと{ }はどちらも同じブロックなので、{ }を使い、ブロックの内部を開業させることも可能である。
numbers = [1, 2, 3, 4]
sum = 0
numbers.each { |n|
sum += n
}
sum #=> 10
・改行を含む長いブロックを書く場合はdo…end
・一行でコンパクトに描きたいときは{ }
上記のように使い分けられるケースが多い。
Ruby学習メモ 配列
<配列>
・配列とは複数のデータをまとめて格納できるオブジェクトのこと。配列内のデータ(要素)は順番に並んでいて、添え字(インデックス)を指定することでそのデータを取り出すことができる。配列は次のように[ ]と,を使って作成する。
# 空の配列を作る
[ ]
# 3つの要素が格納された配列を作る。
[要素1, 要素2, 要素3]
・配列はArrayクラスのオブジェクトになっている。
# 空の配列を作成し、そのクラス名を確認する
.class #=> Array
・次のように改行して書くこともできる。
a = [
1,
2,
3
]
#最後の要素に,がついても文法上エラーにならない。
a = [
1,
2,
3,
]
・配列は数値に限らず、どんなオブジェクトも格納でき、異なるデータ型を一つの配列に格納することもできる。また、配列の中に配列を含めることもできる。
# 配列の中に文字列を格納する例
a = [‘apple’, ‘orange’, ‘melon’]
# 異なるデータ型を格納
a = [1, ‘apple’, 2, ‘orange’, 3, ‘melon’]
# 配列の中に配列を含める
a = [[10, 20, 30], [40, 50, 60], [70, 80, 90]]
・配列の各要素を取得する場合は、[ ]と添え字(数値)を使う。最初の要素の添え字は0である。存在しない要素を指定してもエラーにならず、nilが返る。
a = [1, 2, 3]
# 1つ目の要素を取得
a[0] #=> 1
# 2つ目の要素を取得
a[1] #=> 2
# 3つ目の要素を取得
a[2] #> 3
・sizeメソッド(エイリアスメソッドはlength)を使うと配列の長さ(要素の個数)を取得できる。
a = [1, 2, 3]
a.size #=> 3
a.length #=> 3
・添え字を指定して値を代入すると、指定した要素を変更することができる。
# 配列[添え字] = 新しい値
a = [1, 2, 3]
a[1] = 20
a #=> [1, 20, 3]
<要素の変更、追加、削除>
・元の大きさ(配列の要素の数)よりも大きい添え字を指定して値を代入すると、間の要素の値がnilで埋められる。
a = [1, 2, 3]
a[4] = 50
a #=> [1, 2, 3, nil, 50]
a[7] = 80
a #=> [1, 2, 3, nil, 50, nil, nil, 80]
・<<を使うと配列の最後に要素を追加することができる。
a =
a << 1
a << 2
a << 3
a #=> [1, 2, 3]
・配列内の特定の位置にある要素を削除したい場合はdelete_atメソッドを使う。
a = [1, 2, 3]
# 2番目の要素を削除する(削除した値が戻り値となる)
a.delete_at(1) #=> 2
a #=> [1, 3]
#存在しない添え字を指定するとnilが返る。
a.delete_at(100) #=> nil
a #=> [1, 3]
<配列を使った多重代入>
・配列を使って多重代入することができる。
# 配列を使って多重代入する
a, b = [1, 2]
a #=> 1
b #=> 2
# 右辺の数が少ない場合はnilが返る。
c, d = [10]
c #=> 10
d #=> nil
# 右辺の数が多い場合は、はみ出した値が切り捨てられる。
e, f = [100, 200, 300]
e #=> 100
f #=> 200
RSpec学習メモ [RSpec編] 03_SystemSpec
<システムスペック> 参考記事:https://qiita.com/jnchito/items/c7e6e7abf83598a6516d
システムスペックを使用するには、以下のgemが必要である。
・capybara
・webdrivers
---------------------------------------------------------------------------------------
<Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }>
<spec/rails_helper.rb>
# 省略
# Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
上記のコメントアウトを外すことで、spec/support配下のRSpecファイルを読み込むように設定することができる。
---------------------------------------------------------------------------------------
<--format documentation>
<.rspec>
--format documentation
上記を記載することで、テスト実行の度に見やすいフォーマットでテスト結果を表示してくれる。
---------------------------------------------------------------------------------------
<config.filter_run_when_matching :focus>
<spec/spec_helper.rb>
# 省略
config.filter_run_when_matching :focus
この設定により、RSpec.describe SampleController, type: :request, focus: true doのように設定したspecファイルに対してのみ、テストを実行するようになる。
特定のテストだけを実行したい時に使用する。
---------------------------------------------------------------------------------------
<テストを書いて学んだこと>
<spec/system/user_sessions_spec.rb>
require 'rails_helper'
RSpec.describe "UserSessions", type: :system, focus: true do
let(:user) { create(:user) }
describe 'ログイン前' do
context 'フォームの入力値が正常' do
it 'ログインに成功する' do
visit login_path
fill_in 'Email', with: user.email
fill_in 'Password', with: 'password'
click_button 'Login'
expect(page).to have_content 'Login successful'
expect(current_path).to eq root_path
end
end
end
end
※fill_in ‘Password’, with: user.passwordにするとテストは通らない。
[原因] Sorceryを使用していて、Usersテーブルにはpasswordというカラムがなく、crypted_passwordカラムを取得するしかないが、内容はハッシュ化されているので画面でログインする際の情報としては利用できない。
---------------------------------------------------------------------------------------
<解答例から学んだこと>
<letとlet!の違い>
let は 最初にメソッドが呼ばれた時 に評価される。
let! は letが遅延評価であるのとは違い、各サンプルが実行される前に評価される。
<create_list>
FactoryBotから複数のインスタンスを作成することができる。
task_list = FactoryBot.create_list(:task, 3)
RSpec学習メモ [RSpec編] 02_ModulSpec
<テストエラーから学んだこと>
association :userを記載するときは、spec/factories/users.rbも作成する必要がある。
それを作成していなくて以下のようなエラー内容が出ていた。
<spec/factories/tasks.rb>
FactoryBot.define do
factory :task do
sequence(:title){ |n| "title#{n}" }
content { "test content" }
deadline { "1.week.from.now" }
status { :todo }
association :user
end
end
<spec/models/task_spec.rb>
require 'rails_helper'
RSpec.describe Task, type: :model do
describe 'validation' do
it 'is valid with all attributes' do
task = FactoryBot.build(:task)
expect(task).to be_valid
expect(task.errors).to be_empty
end
end
end
<エラー内容>
1) Task validation is valid with all attributes
Failure/Error: task = FactoryBot.build(:task)
KeyError:
Factory not registered: "user"
# ./spec/models/task_spec.rb:6:in `block (3 levels) in <top (required)>'
# ------------------
# --- Caused by: ---
# KeyError:
# key not found: "user"
# ./spec/models/task_spec.rb:6:in `block (3 levels) in <top (required)>'
Finished in 0.02851 seconds (files took 0.74684 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./spec/models/task_spec.rb:5 # Task validation is valid with all attributes
---------------------------------------------------------------------------------------
<解答例から学んだこと>
① sequence(:title, "title_1") ブロックを渡さす第二引数を渡すと、.nextが呼ばれるようになる。(※英字 + 記号 + 数字の時だけ)
sequence(:email) { |n| "user_#{n}@example.com" }
② spec/rails_helper.rbでconfig.include FactoryBot::Syntax::Methodsを追記することにより、以下のようにrspecのテストコード内でFactoryBotのメソッドを使用する際に、クラス名の指定を省略することができる。
# 通常FactoryBotをつけないとメソッドを呼ぶことができない。
task = FactoryBot.create(:task)
# 上記の設定を追記することで、FactoryBotの記述が省略できる。
task = create(:task)
③ it ‘is invalid with a duplicated email’ do
task = create(:task)
task_with_duplicated_title = build(:task, title: task.title)
expect(task_with_duplicated_title).to be_invalid
end
上記のようなテストでは、task_with_duplicated_titleのように、その変数が何の役割を持っているのか明示的にすること。
RSpec学習メモ [RSpec編] 01_環境構築
<リポジトリのフォーク>
Githubのアカウントを所有していると、自分のアカウント内に既存のリポジトリの複製を作成することができ、この操作をフォークと呼ぶ。
共有されていないリポジトリに対して書き込むことはできないが、フォークしたリポジトリは自分の所有物なので、自由に更新することができる。
つまり、自分のフォークを作成すれば、共有されていないリポジトリをベースとした作業を始めることができる。
---------------------------------------------------------------------------------------
<プルリクエスト>
開発者のローカルリポジトリでの変更を他の開発者に通知する機能のこと。次のような機能を提供する。
・機能追加や改修など、作業内容をレビュー・マージ担当者やその他関係者に通知する。
・ソースコードの変更箇所をわかりやすく表示する。
・ソースコードに関するコミュニケーションの場を提供する。
---------------------------------------------------------------------------------------
<課題を終えて学んだこと>
bundlerを使用するのではなく、vendor/bundle以下にgemをインストールしていく場合は、git ignoreしないとgit add.などのコマンドで取得したgemライブラリのソースコードがコミット対象に含まれてしまう。
今回作業ブランチへ切り替えをせず、コミットとgit pushを行ったが、必ず作業ブランチへ切り替えて作業をすることを忘れないようにする。
RSpec学習メモ(everyday Rails RSpecによるRailsテスト入門 第8章)
<スペックをDRYに保つ>
ここまで学んできた知識を使ってテストスイートを作成しても、コードはたくさん重複しており、DRY原則を破っている状態となる。
---------------------------------------------------------------------------------------
<サポートモジュール>
サポートモジュールを使用することで、重複するコードをspec/support ディレクトリ以下のモジュールファイルに切り分けることができる。
<spec/support/login_support.rb>
# ログイン処理をモジュールに切り分けて定義している。
module LoginSupport
Def sign_in_as(user)
Visit root_path
click_link "Sign in"
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_button "Log in"
end
end
# RSpecの設定。ここでは RSpec.configure を使って上記のモジュールをincludeしている。
# 必ずしも必要ではなく、テスト毎に明示的にサポートモジュールをincludeする方法もある。
RSpec.configure do |config|
config.include LoginSupport
end
<spec/features/projects_spec.rb>
require 'rails_helper'
RSpec.feature "Projects", type: :feature do
include LoginSupport
# 明示的にサポートモジュールをincludeしている。
# ユーザーは新しいプロジェクトを作成する
scenario "user creates a new project" do
# ...
end
end
モジュール内のメソッド名は、コードを読んだ時に目的パッとわかるような名前にすること。
---------------------------------------------------------------------------------------
<let で遅延読み込みする>
Beforeブロックを使用することでdescribeやcontext内部で、各テストの実行前に共通のインスタンス変数をセットアップできる。
しかし、それらはdescribeやcontextの内部に書いたテストを実行するたびに毎回実行される為、テストを遅くする可能性がある。
こうした問題に対処するために、RSpecはletというメソッドを提供している。letは呼ばれた時に初めてデータを読み込む、遅延読み込みを実現するメソッドである。
<spec/models/task_spec.rb>
require 'rails_helper'
RSpec.describe Task, type: :model do
let(:project) { FactoryBot.create(:project) }
# プロジェクトと名前があれば有効な状態であること
it "is valid with a project and name" do
task = Task.new(
project: project,
name: "Test task",
)
expect(task).to be_valid
end
# プロジェクトがなければ無効な状態であること
it "is invalid without a project" do
task = Task.new(project: nil)
task.valid?
expect(task.errors[:project]).to include("must exist")
end
# 名前がなければ無効な状態であること
it "is invalid without a name" do
task = Task.new(name: nil)
task.valid?
expect(task.errors[:name]).to include("can't be blank")
end
end
4行目にあるletを使って必要となるプロジェクトを作成している。最初のテストではprojectが必要なため、letによってprojectが作成される。
それ以降のテストではprojectが必要ないのでprojectが作成されることはない。一つ目のテストの実行が終わると、projectは取り除かれる。
ポイント
Beforeブロックではテストデータをセットアップする時、インスタンス変数に格納していたが、letは必要に応じてデータを作成する。
---------------------------------------------------------------------------------------
<shared_context (contextの共有)>
letを使用することで、複数のテストで必要な共通のデータを簡単にセットアップすることができる。
一方、shared_contextを使用すると複数のテストファイルで必要なセットアップを行うことができる。
ポイント
①Beforeブロックで記載しているものをletを使用してリファクタリングする。
②別のファイルにletの部分を切り分ける。(以下例)
③コントローラースペックのletの部分を、include_context "project setup" に置き換える。
<spec/support/contexts/project_setup.rb>
RSpec.shared_context "project setup" do
let(:user) { FactoryBot.create(:user) }
let(:project) { FactoryBot.create(:project, owner: user) }
let(:task) { project.tasks.create!(name: "Test task") }
end
RSpec学習メモ(everyday Rails RSpecによるRailsテスト入門 第6章)
<フィーチャースペックでUIをテストする>
モデルとコントローラが他のモデルやコントローラとうまく一緒に動作することを確認する。このようなテストをRSpecではフィーチャースペックと呼ぶ。
受入テストや、統合テストと呼ばれることもある。
---------------------------------------------------------------------------------------
<gem Capybara>
Capybaraを使用することで、リンクをクリックしたり、Webフォームを入力したり、画面の表示を検証したりすることができる。
Railsの開発環境で実行可能なジェネレータは用意されていない。
Capybaraをテスト環境にだけ追加することで、開発環境のメモリ消費を少し軽くすることができる。
Capybaraをインストールした後は、テストスイートに対してCapybaraを読み込むように伝える必要がある。
<spec/rails_helper.rb>
require ‘capybara/rspec’
これでCapybaraを使用する準備が完了する。
---------------------------------------------------------------------------------------
<フィーチャースペックの基本>
Capybaraではclick_linkやfill_in, visitといった理解しやすいメソッドが提供されていて、アプリケーションで必要な機能のシナリオを書くことができる。
フィーチャースペックでは一つのexample、もしくは一つのシナリオで複数のエクスペーションを書くことは問題ない。
---------------------------------------------------------------------------------------
<CapybaraのDSL>
CapybaraのDSLが提供しているメソッドによって、できることの幅が広がる。