20200121のRubyに関する記事は25件です。

Rubyのプログラミングスキルを向上させるコツ

リファクタリングのクセづけ

コードを書き終わったら、必ず、どこかリファクタリング出来ないか研究することをクセづける。

コードが9行から1行になることもある。
恐らく、プロのエンジニアには必須スキル!

テストの開発サイクルについて

  1. 先にテストを書いて失敗させる。
  2. テストが成功するような簡単なテストをひとつ試してみる
  3. リファクタリングする。
  • 基礎はめちゃくちゃ大事。
  • なぜなら基礎的なことはどんなにプロエンジニアになっても、基礎だけは使い続けるものだから。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MVCとはなんなのか

Ruby on Rails初心者として、自分自身の理解を整理したいのでまとめたいと思います。
何か認識違いがあれば、ご指摘いただければ幸いです。

MVCとは

UI(ユーザインタフェース)を持つソフトウエアのアーキテクチャの一種。
画面に表示する部分(UI)は、アプリケーション固有のデータや処理の扱いの性質が異なるため、画面の表示とアプリケーションのデータに関する部分を混ぜて記述してしまうと、コードが複雑化してしまい、保守性が悪くなってしまいます。

そこで、アプリケーション固有のデータの部分、画面を表示させるUIの部分、この2つをつなぎ合わせる制御の部分の3つに分けて管理をしやすくしようというものです。

M(model)

アプリケーション固有のデータや処理の扱いの部分
・データベースへの保存、読み込み、永続化を担当する。

V(View)

UIに関わる部分
・HTTPレスポンスの中身を実際に組み立てて、画面を表示する。

C(Controller)

ModelとViewを統合的に制御する部分
・ブラウザからのリクエストを受けて適切なレスポンスを作成するための制御を行う。

MVCのメリット

・それぞれが専門的な分野になるため、一つ一つの仕様変更がしやすくなる。
・分野ごとに得意な人が開発を進められる。

まとめ

MVCアーキテクチャを使うことによって複雑化するコードをそれぞれの3つの分野に分けることで単純化し、管理しやすくすることで、それぞれが得意な分野に分かれて開発できるようになり、作業しやすい環境が生まれるということ(と感じました)。

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

アップロードした画像の有無の判定

実装したこと

ビューファイルでimageカラムがnilだった時の判定をして表示するビューの切替。
アップローダー:carrierwave
最初は

index.html.haml
- if @user.image.nil?
 %p 画像がまだ登録されていません。
- else
 = image_tag @user.image

としたところ画像が無くてもPタグの内容が表示されなかった。

binding.pryで中身を確認したとこと

[1] pry(#<MachinesController>)> @user
=> #<User:0x00007f85e25cca00 id: 27, name: "画像なし", image: nil, created_at: Tue, 21 Jan 2020 08:28:09 UTC +00:00, updated_at: Tue, 21 Jan 2020 08:28:09 UTC +00:00>

nilだよなぁ。じゃあimageの中身見るか

[1] pry(#<MachinesController>)> @user.image
=> #<ImageUploader:0x00007f85e836eaf0
 @cache_id=nil,
 @file=nil,
 @filename=nil,
 @identifier=nil,
 @model=#<User:0x00007f85e9c083b8 id: 27, name: "画像なし", image: nil, created_at: Tue, 21 Jan 2020 08:28:09 UTC +00:00, updated_at: Tue, 21 Jan 2020 08:28:09 UTC +00:00>,
 @mounted_as=:image,
 @staged=false,
 @versions=nil>

なんかnilのはずなのにいっぱい入ってるやん!?

で調べてみたところ
https://teratail.com/questions/101330
に回答している方がいらっしゃって
「carrierwaveでは、uploaderをマウントしたアトリビュートにアクセスすると、
uploaderのオブジェクトを返すため、決してnilにはならないらしいです。」
とのこと。

なので、このページの回答の通りに

index.html.haml
- if @user.image.file.nil?
 %p 画像がまだ登録されていません。
- else
 = image_tag @user.image

にしたら無事に解決しました。

ちょっとややこしかったのでシェア。

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

【ruby on rails】button_toを使って情報を送り、paramsで受け取る方法。【いいね機能】

button_toとは

railsでページ遷移する際、ボタン形式のリンクを作成してくれるものです。
似たようなものにlink_toがあります。

button_toの書き方(一番簡単な例)

html.erb
<%= button_to "ボタン表示文字",hogehoge_path%>

こんな感じで指定したパスに飛べます。

button_toで情報を送りparamsで受け取る

今回はいいね機能を作成したいときの一例を紹介します

html.erb
<%= button_to "いいね",favorites_path(@hoge.id),{params: {id:@nantoka.id}}%>
favorites_controller.rb
@favorite = Favorite.new
@favorite.item_id = params[:id]

この書き方で、params[:id]を取得できます。
ポイントはbutton_toの第三引数に{params:{}}として、情報を引き渡すことができるということです。
簡単に使えるので、是非活用してみてください!

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

テスト駆動開発から始めるRuby入門

エピソード1

初めに

この記事は一応、Ruby入門者向けの記事ですが同時にテスト駆動開発入門者向けともなっています。

対象レベルによって以下のように読み進められれば効率が良いかと思います。

  • Ruby入門者でプログラミング初心者・・・とりあえずコードの部分だけを写経しましょう。解説文は最初のうちは何言ってるかわからないと思うので5回ぐらい写経してRubyを書く感覚がつかめてきてから読み直すといいでしょう。もっと知りたくなったら参考図書にあたってください。と言っても結構お高いので「リーダブルコード」と「かんたんRuby(プログラミングの教科書)」といった初心者向け言語入門書から買い揃えるのがおすすめです。

  • Ruby経験者でテスト駆動開発初心者・・・コード部分を写経しながら解説文を読み進めていきましょう。短いステップでテスト駆動のリズムが感覚がイメージしていただければ幸いです。もっと知りたくなったら原著の「テスト駆動開発」にあたってくださいオリジナルはJavaですがRubyで実装してみると多くの学びがあると思います。あと、「プロを目指す人のためのRuby入門」が対象読者に当たると思います。

  • 他の言語経験者でテスト駆動開発初心者・・・コード部分を自分が使っている言語に置き換えながら解説文を読み進めていきましょう。もっと知りたくなったら原著の「テスト駆動開発」にあたってくださいオリジナルはJavaとPythonが使われています。あと、「リファクタリング」は初版がJavaで第2版がJavaScriptで解説されています。

  • 言語もテスト駆動開発もつよつよな人・・・レビューお待ちしております(笑)。オブジェクト指向に関する言及が無いというツッコミですが追加仕様編でそのあたりの解説をする予定です。あと、「リファクタリング」にはRubyエディションもあるのですが日本語訳が絶版となっているので参考からは外しています。

写経するのに環境構築ができない・面倒なひとはこちらでお手軽に始めることができます。

あと、初心者の方で黒い画面でちまちまやっててナウいアプリケーションなんて作れるの?と思う人もいると思いますが最終的にはこんなアプリケーションになります。流石にフロントエンドはRubyではありませんがバックエンドはRubyのサーバレスアプリケーションで構成されているので少しはナウいやつだと思います。

TODOリストから始めるテスト駆動開発

TODOリスト

プログラムを作成するにあたってまず何をすればよいだろうか?私は、まず仕様の確認をして TODOリスト を作るところから始めます。

TODOリスト

何をテストすべきだろうか----着手する前に、必要になりそうなテストをリストに書き出しておこう。

— テスト駆動開発

仕様

1 から 100 までの数をプリントするプログラムを書け。
ただし 3 の倍数のときは数の代わりに「Fizz」と、5 の倍数のときは「Buzz」とプリントし、
3 と 5 両方の倍数の場合には「FizzBuzz」とプリントすること。

仕様の内容をそのままプログラムに落とし込むには少しサイズが大きいようですね。なので最初の作業は仕様を TODOリスト に分解する作業から着手することにしましょう。仕様をどのようにTODOに分解していくかは 50分でわかるテスト駆動開発の26分あたりを参考にしてください。

TODOリスト

  • 数を文字列にして返す

  • 3 の倍数のときは数の代わりに「Fizz」と返す

  • 5 の倍数のときは「Buzz」と返す

  • 3 と 5 両方の倍数の場合には「FizzBuzz」と返す

  • 1 から 100 までの数

  • プリントする

まず 数を文字列にして返す作業に取り掛かりたいのですがまだプログラミング対象としてはサイズが大きいようですね。もう少し具体的に分割しましょう。

  • 数を文字列にして返す

    • 1を渡したら文字列"1"を返す

これならプログラムの対象として実装できそうですね。

テストファーストから始めるテスト駆動開発

テストファースト

最初にプログラムする対象を決めたので早速プロダクトコードを実装・・・ではなく テストファースト で作業を進めていきましょう。まずはプログラムを実行するための準備作業を進める必要がありますね。

テストファースト

いつテストを書くべきだろうか----それはテスト対象のコードを書く前だ。

— テスト駆動開発

では、どうやってテストすればいいでしょうか?テスティングフレームワークを使って自動テストを書きましょう。

テスト(名詞) どうやってソフトウェアをテストすればよいだろか----自動テストを書こう。

— テスト駆動開発

今回Rubyのテスティングフレームワークには Minitestを利用します。Minitestの詳しい使い方に関しては Minitestの基本 6を参照してください。では、まず以下の内容のテキストファイルを作成して main.rb で保存します。

require 'minitest/reporters'
Minitest::Reporters.use!
require 'minitest/autorun'

class HelloTest < Minitest::Test
  def test_greeting
    assert_equal 'hello world', greeting
  end
end

def greeting
  'hello world'
end

テストを実行します。

$ ruby main.rb
Started with run options --seed 9701

  1/1: [======================================================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00090s
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips

テストは成功しましたね。では続いてテストを失敗させてみましょう。hello worldhello world!!!
に書き換えてテストを実行してみるとどうなるでしょうか。

...
class HelloTest < Minitest::Test
  def test_greeting
    assert_equal 'hello world!!!', greeting
  end
end
...
$ ruby main.rb
Started with run options --seed 18217

 FAIL["test_greeting", #<Minitest::Reporters::Suite:0x00007f98a59194f8 @name="HelloTest">, 0.0007280000027094502]
 test_greeting#HelloTest (0.00s)
        Expected: "hello world!!!"
          Actual: "hello world"
        main.rb:11:in `test_greeting'

  1/1: [======================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00101s
1 tests, 1 assertions, 1 failures, 0 errors, 0 skips

オッケー、テスティングフレームワークが正常に読み込まれて動作することが確認できました。続いてバージョン管理システムのセットアップをしておきましょう。バージョン管理システム何それ?だって!?君はセーブしないでロールプレイングゲームをクリアできるのか?できないならまずここでGitを使ったバージョン管理の基本を学んでおきましょう。

$ git init
$ git add .
$ git commit -m 'セットアップ'

これでソフトウェア開発の三種の神器のうち バージョン管理テスティング の準備が整いましたので TODOリスト の最初の作業に取り掛かかるとしましょう。

仮実装

TODOリスト

  • 数を文字列にして返す

    • 1を渡したら文字列"1"を返す
  • 3 の倍数のときは数の代わりに「Fizz」と返す

  • 5 の倍数のときは「Buzz」と返す

  • 3 と 5 両方の倍数の場合には「FizzBuzz」と返す

  • 1 から 100 までの数

  • プリントする

1を渡したら文字列"1"を返す プログラムを main.rb に書きましょう。最初に何を書くのかって?
アサーションを最初に書きましょう。

アサートファースト

いつアサーションを書くべきだろうか----最初に書こう

  • システム構築はどこから始めるべきだろうか。システム構築が終わったらこうなる、というストーリーを語るところからだ。

  • 機能はどこから書き始めるべきだろうか。コードが書き終わったらこのように動く、というテストを書くところからだ。

  • ではテストはどこから書き始めるべきだろうか。それはテストの終わりにパスすべきアサーションを書くところからだ。

— テスト駆動開発

まず、セットアッププログラムは不要なので削除しておきましょう。

require 'minitest/reporters'
Minitest::Reporters.use!
require 'minitest/autorun'

テストコードを書きます。え?日本語でテストケースを書くの?ですかって。開発体制にもよりますが日本人が開発するのであれば無理に英語で書くよりドキュメントとしての可読性が上がるのでテストコードであれば問題は無いと思います。

テストコードを読みやすくするのは、テスト以外のコードを読みやすくするのと同じくらい大切なことだ。

— リーダブルコード

require 'minitest/reporters'
Minitest::Reporters.use!
require 'minitest/autorun'

class FizzBuzzTest < Minitest::Test
  def test_1を渡したら文字列1を返す
    assert_equal '1', FizzBuzz.generate(1)
  end
end

テストを実行します。

$ ruby main.rb
Started with run options --seed 678

ERROR["test_1を渡したら文字列1を返す", #<Minitest::Reporters::Suite:0x00007f956d8b6870 @name="FizzBuzzTest">, 0.0006979999998293351]
 test_1を渡したら文字列1を返す#FizzBuzzTest (0.00s)
NameError:         NameError: uninitialized constant FizzBuzzTest::FizzBuzz
        Did you mean?  FizzBuzzTest
            main.rb:10:in `test_1を渡したら文字列1を返す'

  1/1: [======================================================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00201s
1 tests, 0 assertions, 0 failures, 1 errors, 0 skips

NameError: NameError: uninitialized constant FizzBuzzTest::FizzBuzz…​FizzBuzzが定義されていない。そうですねまだ作ってないのだから当然ですよね。ではFizzBuzz::generate メソッドを作りましょう。どんな振る舞いを書けばいいのでしょうか?とりあえず最初のテストを通すために 仮実装 から始めるとしましょう。

仮実装を経て本実装へ

失敗するテストを書いてから、最初に行う実装はどのようなものだろうか----ベタ書きの値を返そう。

— テスト駆動開発

FizzBuzz クラス を定義して 文字列リテラル を返す FizzBuzz::generate クラスメソッド を作成しましょう。ちょっと何言ってるかわからないかもしれませんがとりあえずそんなものだと思って書いてみてください。

...
class FizzBuzz
  def self.generate(n)
    '1'
  end
end

テストが通ることを確認します。

$ ruby main.rb
Started with run options --seed 60122

  1/1: [======================================================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00094s
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips

オッケー、これでTODOリストを片付けることができました。え?こんなベタ書きのプログラムでいいの?他に考えないといけないことたくさんあるんじゃない?ばかじゃないの?と思われるかもしませんが、この細かいステップに今しばらくお付き合いいただきたい。

TODOリスト

  • 数を文字列にして返す

    • 1を渡したら文字列"1"を返す
  • 3 の倍数のときは数の代わりに「Fizz」と返す

  • 5 の倍数のときは「Buzz」と返す

  • 3 と 5 両方の倍数の場合には「FizzBuzz」と返す

  • 1 から 100 までの数

  • プリントする

三角測量

1を渡したら文字列1を返すようにできました。では、2を渡したらどうなるでしょうか?

TODOリスト

  • 数を文字列にして返す

    • 1を渡したら文字列"1"を返す
    • 2を渡したら文字列"2"を返す
  • 3 の倍数のときは数の代わりに「Fizz」と返す

  • 5 の倍数のときは「Buzz」と返す

  • 3 と 5 両方の倍数の場合には「FizzBuzz」と返す

  • 1 から 100 までの数

  • プリントする

...
class FizzBuzzTest < Minitest::Test
  def test_1を渡したら文字列1を返す
    assert_equal '1', FizzBuzz.generate(1)
  end

  def test_2を渡したら文字列2を返す
    assert_equal '2', FizzBuzz.generate(2)
  end
end
$ ruby main.rb
Started with run options --seed 62350

 FAIL["test_2を渡したら文字列2を返す", #<Minitest::Reporters::Suite:0x00007fa4968938d8 @name="FizzBuzzTest">, 0.0009390000013809185]
 test_2を渡したら文字列2を返す#FizzBuzzTest (0.00s)
        Expected: "2"
          Actual: "1"
        main.rb:17:in `test_2を渡したら文字列2を返す'

  2/2: [======================================================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00179s
2 tests, 2 assertions, 1 failures, 0 errors, 0 skips

テストが失敗しました。それは文字列1しか返さないプログラムなのだから当然ですよね。では1が渡されたら文字列1を返し、2を渡したら文字列2を返すようにプログラムを修正しましょう。数値リテラル文字列リテラル に変換する必要があります。公式リファレンスで調べてみましょう。

Rubyの公式リファレンスは https://docs.ruby-lang.org/ です。日本語リファレンス からるりまサーチを選択してキーワード検索してみましょう。文字列 変換キーワードで検索すると to_s というキーワードが出てきました。今度はto_sで検索すると色々出てきました、どうやら to_s を使えばいいみたいですね。

ちなみに検索エンジンから Ruby 文字列 変換で検索してもいろいろ出てくるのですがすべてのサイトが必ずしも正確な説明をしているまたは最新のバージョンに対応しているとは限らないので始めは公式リファレンスや市販の書籍から調べる癖をつけておきましょう。

...
class FizzBuzz
  def self.generate(n)
    n.to_s
  end
end

テストを実行します。

$ ruby main.rb
Started with run options --seed 42479

  2/2: [======================================================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00098s
2 tests, 2 assertions, 0 failures, 0 errors, 0 skips

テストが無事通りました。このように2つ目のテストによって FizzBuzz::generate メソッドの一般化を実現することができました。このようなアプローチを 三角測量 と言います。

三角測量

テストから最も慎重に一般化を引き出すやり方はどのようなものだろうか----2つ以上の例があるときだけ、一般化を行うようにしよう。

— テスト駆動開発

TODOリスト

  • 数を文字列にして返す

    • 1を渡したら文字列"1"を返す
    • 2を渡したら文字列"2"を返す
  • 3 の倍数のときは数の代わりに「Fizz」と返す

  • 5 の倍数のときは「Buzz」と返す

  • 3 と 5 両方の倍数の場合には「FizzBuzz」と返す

  • 1 から 100 までの数

  • プリントする

たかが 数を文字列にして返す プログラムを書くのにこんなに細かいステップを踏んでいくの?と思ったかもしれません。プログラムを書くということは細かいステップを踏んで行くことなのです。そして、細かいステップを踏み続けることが大切なことなのです。

TDDで大事なのは、細かいステップを踏むことではなく、細かいステップを踏み続けられるようになることだ。

— テスト駆動開発

あと、テストケースの内容がアサーション一行ですがもっと検証するべきことがあるんじゃない?と思うでしょう。検証したいことがあれば独立したテストケースを追加しましょう。このような書き方はよろしくありません。

...
  def test_数字を渡したら文字列を返す
    assert_equal '1', FizzBuzz.generate(1)
    assert_equal '2', FizzBuzz.generate(2)
    assert_equal '3', FizzBuzz.generate(3)
    assert_equal '4', FizzBuzz.generate(4)
    assert_equal '5', FizzBuzz.generate(5)
  end
...

テストの本質というのは、「こういう状況と入力から、こういう振る舞いと出力を期待する」のレベルまで要約できる。

— リーダブルコード

ここで一段落ついたので、これまでの作業内容をバージョン管理システムにコミットしておきましょう。

git commit -m 'test: 数を文字列にして返す'

リファクタリングから始めるテスト駆動開発

リファクタリング

ここでテスト駆動開発の流れを確認しておきましょう。

  1. レッド:動作しない、おそらく最初のうちはコンパイルも通らないテストを1つ書く。

  2. グリーン:そのテストを迅速に動作させる。このステップでは罪を犯してもよい。

  3. リファクタリング:テストを通すために発生した重複をすべて除去する。

レッド・グリーン・リファクタリング。それがTDDのマントラだ。

— テスト駆動開発

コードはグリーンの状態ですが リファクタリング を実施していませんね。重複を除去しましょう。

リファクタリング(名詞) 外部から見たときの振る舞いを保ちつつ、理解や修正が簡単になるように、ソフトウェアの内部構造を変化させること。

— リファクタリング(第2版)

リファクタリングする(動詞) 一連のリファクタリングを適用して、外部から見た振る舞いの変更なしに、ソフトウェアを再構築すること。

— リファクタリング(第2版

メソッドの抽出

テストコードを見てください。テストを実行するにあたって毎回前準備を実行する必要があります。こうした処理は往々にして同じ処理を実行するものなので
メソッドの抽出 を適用して重複を除去しましょう。

メソッドの抽出

ひとまとめにできるコードの断片がある。

コードの断片をメソッドにして、それを目的を表すような名前をつける。

— 新装版 リファクタリング

class FizzBuzzTest < Minitest::Test
  def test_1を渡したら文字列1を返す
    assert_equal '1', FizzBuzz.generate(1)
  end

  def test_2を渡したら文字列2を返す
    assert_equal '2', FizzBuzz.generate(2)
  end
end

テストフレームワークでは前処理にあたる部分を実行する機能がサポートされています。Minitestでは setup メソッドがそれに当たるので FizzBuzz オブジェクトを共有して共通利用できるようにしてみましょう。ここでは インスタンス変数FizzBuzz クラス の参照を 代入 して各テストメソッドで共有できるようにしました。ちょっと何言ってるかわからないかもしれませんがここではそんなことをやってるぐらいのイメージで大丈夫です。

class FizzBuzzTest < Minitest::Test
  def setup
    @fizzbuzz = FizzBuzz
  end

  def test_1を渡したら文字列1を返す
    assert_equal '1', @fizzbuzz.generate(1)
  end

  def test_2を渡したら文字列2を返す
    assert_equal '2', @fizzbuzz.generate(2)
  end
end

テストプログラムを変更してしまいましたが壊れていないでしょうか?確認するにはどうすればいいでしょう? テストを実行して確認すればいいですよね。

$ ruby main.rb
Started with run options --seed 33356

  2/2: [======================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00083s
2 tests, 2 assertions, 0 failures, 0 errors, 0 skips

オッケー、前回コミットした時と同じグリーンの状態のままですよね。区切りが良いのでここでコミットしておきましょう。

git commit -m 'refactor: メソッドの抽出'

変数名の変更

もう一つ気になるところがあります。

...
class FizzBuzz
  def self.generate(n)
    n.to_s
  end
end

引数の名前が n ですね。コンピュータにはわかるかもしれませんが人間が読むコードとして少し不親切です。特にRubyのような動的言語では型が明確に定義されないのでなおさらです。ここは 変数名の変更 を適用して人間にとって読みやすいコードにリファクタリングしましょう。

コンパイラがわかるコードは誰にでも書ける。すぐれたプログラマは人間にとってわかりやすいコードを書く。

— リファクタリング(第2版)

名前は短いコメントだと思えばいい。短くてもいい名前をつければ、それだけ多くの情報を伝えることができる。

— リーダブルコード

...
class FizzBuzz
  def self.generate(number)
    number.to_s
  end
end

続いて、変更で壊れていないかを確認します。

$ ruby main.rb
Started with run options --seed 33356

  2/2: [======================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00083s
2 tests, 2 assertions, 0 failures, 0 errors, 0 skips

オッケー、この時点でテストコードとプロダクトコードを変更しましたがその変更はすでに作成した自動テストによって壊れていないことを簡単に確認することができました。え、こんな簡単な変更でプログラムが壊れるわけないじゃん、ドジっ子なの?ですって。残念ながら私は絶対ミスしない完璧な人間ではないし、どちらかといえば注意力の足りないプログラマなのでこんな間違いも普通にやらかします。

...
class FizzBuzz
  def self.generate(number)
    numbr.to_s
  end
end
$ ruby main.rb
Started with run options --seed 59453

ERROR["test_1を渡したら文字列1を返す", #<Minitest::Reporters::Suite:0x0000564f6b1dfc70 @name="FizzBuzzTest">, 0.001019135997921694]
 test_1を渡したら文字列1を返す#FizzBuzzTest (0.00s)
NameError:         NameError: undefined local variable or method `numbr' for FizzBuzz:Class
        Did you mean?  number
            main.rb:21:in `generate'
            main.rb:11:in `test_1を渡したら文字列1を返す'

ERROR["test_2を渡したら文字列2を返す", #<Minitest::Reporters::Suite:0x0000564f6b1985f0 @name="FizzBuzzTest">, 0.003952859999117209]
 test_2を渡したら文字列2を返す#FizzBuzzTest (0.00s)
NameError:         NameError: undefined local variable or method `numbr' for FizzBuzz:Class
        Did you mean?  number
            main.rb:21:in `generate'
            main.rb:15:in `test_2を渡したら文字列2を返す'

  2/2: [====================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00746s
2 tests, 0 assertions, 0 failures, 2 errors, 0 skips

最初にプロダクトコードを書いて一通りの機能を作ってから動作を確認する進め方だとこの手の間違いはいつどこで作り込んだのかわからなくなるため原因の調査に時間がかかり残念な経験をしたドジっ子プログラマは変更なんてするもんじゃないと思いコードを変更することに不安を持つようになるでしょう。でも、テスト駆動開発ならそんなドジっ子プログラマでも自動テストと小さなステップのおかげで上記のようなしょうもない間違いもすぐに見つけてすぐに対応することができるのでコードを変更する勇気を持つことができるのです。

テスト駆動開発は、プログラミング中の不安をコントロールする手法だ。

— テスト駆動開発

リファクタリングでは小さなステップでプログラムを変更していく。そのため間違ってもバグを見つけるのは簡単である。

— リファクタリング(第2版)

このグリーンの状態にいつでも戻れるようにコミットして次の TODOリスト の内容に取り掛かるとしましょう。

git commit -m 'refactor: 変数名の変更'

リファクタリングが成功するたびにコミットしておけば、たとえ壊してしまったとしても、動いていた状態に戻すことができます。変更をコミットしておき、意味のある単位としてまとまってから、共有のリポジトリに変更をプッシュすればよいのです。

— リファクタリング(第2版)

明白な実装

次は 3を渡したら文字列"Fizz" を返すプログラムに取り組むとしましょう。

TODOリスト

  • 数を文字列にして返す

    • 1を渡したら文字列"1"を返す
    • 2を渡したら文字列"2"を返す
  • 3 の倍数のときは数の代わりに「Fizz」と返す

    • 3を渡したら文字列"Fizz"を返す
  • 5 の倍数のときは「Buzz」と返す

  • 3 と 5 両方の倍数の場合には「FizzBuzz」と返す

  • 1 から 100 までの数

  • プリントする

まずは、テストファースト アサートファースト で小さなステップで進めていくんでしたよね。

...
  def test_3を渡したら文字列Fizzを返す
    assert_equal 'Fizz', @fizzbuzz.generate(3)
  end
...
$ ruby main.rb
Started with run options --seed 7095

 FAIL["test_3を渡したら文字列Fizzを返す", #<Minitest::Reporters::Suite:0x00007fbadf865f50 @name="FizzBuzzTest">, 0.017029999995429534]
 test_3を渡したら文字列Fizzを返す#FizzBuzzTest (0.02s)
        --- expected
        +++ actual
        @@ -1 +1,3 @@
        -"Fizz"
        +# encoding: US-ASCII
        +#    valid: true
        +"3"
        main.rb:19:in `test_3を渡したら文字列Fizzを返す'

  3/3: [======================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.05129s
3 tests, 3 assertions, 1 failures, 0 errors, 0 skips

さて、失敗するテストを書いたので次はテストを通すためのプロダクトコードを書くわけですがどうしましょうか? 仮実装 でベタなコードを書きますか?実現したい振る舞いはもし3を渡したらならば文字列Fizzを返す です。英語なら If number is 3, result is Fizzといったところでしょうか。ここは 明白な実装 で片付けた方が早いでしょう。

明白な実装

シンプルな操作を実現するにはどうすればいいだろうか----そのまま実装しよう。

仮実装や三角測量は、細かく細かく刻んだ小さなステップだ。だが、ときには実装をどうすべきか既に見えていることが。
そのまま進もう。例えば先ほどのplusメソッドくらいシンプルなものを仮実装する必要が本当にあるだろうか。
普通は、その必要はない。頭に浮かんだ明白な実装をただ単にコードに落とすだけだ。もしもレッドバーが出て驚いたら、あらためてもう少し歩幅を小さくしよう。

— テスト駆動開発

class FizzBuzz
  def self.generate(number)
    number.to_s
  end
end

ここでは if式演算子 を使ってみましょう。なんかプログラムっぽくなってきましたね。
3で割で割り切れる場合はFizzを返すということは 数値リテラル 3で割った余りが0の場合は 文字列リテラル Fizzを返すということなので余りを求める 演算子 を調べる必要がありますね。公式リファレンスで 算術演算子 をキーワードで検索したところ いろいろ出てきました。 %を使えばいいみたいですね。

class FizzBuzz
  def self.generate(number)
    result = number.to_s
    if number % 3 == 0
       result = 'Fizz'
    end
    result
  end
end

テストがグリーンになったのでコミットしておきます。

$ ruby main.rb
$ git commit -m 'test: 3を渡したら文字列Fizzを返す'

アルゴリズムの置き換え

TODOリスト

  • 数を文字列にして返す

    • 1を渡したら文字列"1"を返す
    • 2を渡したら文字列"2"を返す
  • 3 の倍数のときは数の代わりに「Fizz」と返す

    • 3を渡したら文字列"Fizz"を返す
  • 5 の倍数のときは「Buzz」と返す

    • 5を渡したら文字列"Buzz"を返す
  • 3 と 5 両方の倍数の場合には「FizzBuzz」と返す

  • 1 から 100 までの数

  • プリントする

class FizzBuzz
  def self.generate(number)
    result = number.to_s
    if number % 3 == 0
       result = 'Fizz'
    end
    result
  end
end

レッド・グリーンときたので次はリファクタリングですね。

class FizzBuzz
  def self.generate(number)
    result = number.to_s
    if number.modulo(3).zero?
       result = 'Fizz'
    end
    result
  end
end

ここでは アルゴリズムの置き換え を適用します。 メソッドチェーンと述語メソッド を使ってRubyらしい書き方にリファクタリングしてみました。

アルゴリズムの取り替え

アルゴリズムをよりわかりやすいものに置き換えたい。

メソッドの本体を新たなアルゴリズムで置き換える。

— 新装版 リファクタリング

メソッドチェーンは言葉の通り、メソッドを繋げて呼び出す方法です。

— かんたんRuby

述語メソッドとはメソッド名の末尾に「?」をつけたメソッドのことを指します。

— かんたんRuby

$ ruby main.rb
$ git commit -m 'refactor: アルゴリズムの置き換え'

だんだんとリズムに乗ってきました。ここはギアを上げて 明白な実装 で引き続き TODOリスト の内容を片付けていきましょう。

TODOリスト

  • 数を文字列にして返す

    • 1を渡したら文字列"1"を返す
    • 2を渡したら文字列"2"を返す
  • 3の倍数のときは数の代わりに「Fizz」と返す

    • 3を渡したら文字列"Fizz"を返す
  • 5 の倍数のときは「Buzz」と返す

    • 5を渡したら文字列"Buzz"を返す
  • 3 と 5 両方の倍数の場合には「FizzBuzz」と返す

  • 1 から 100 までの数

  • プリントする

...
  def test_5を渡したら文字列Buzzを返す
    assert_equal 'Buzz', @fizzbuzz.generate(5)
  end
end
class FizzBuzz
  def self.generate(number)
    result = number.to_s
    if number.modulo(3).zero?
       result = 'Fizz'
    end
    result
  end
end
class FizzBuzz
  def self.generate(number)
    result = number.to_s
    if number.modulo(3).zero?
      result = 'Fizz'
    elsif number.modulo(5).zero?
      result = 'Buzz'
    end
    result
  end
end
$ ruby main.rb
$ git commit -m 'test: 5を渡したら文字列Buzzを返す'

メソッドのインライン化

TODOリスト

  • 数を文字列にして返す

    • 1を渡したら文字列"1"を返す
    • 2を渡したら文字列"2"を返す
  • 3の倍数のときは数の代わりに「Fizz」と返す

    • 3を渡したら文字列"Fizz"を返す
  • 5 の倍数のときは「Buzz」と返す

    • 5を渡したら文字列"Buzz"を返す
  • 3 と 5 両方の倍数の場合には「FizzBuzz」と返す

  • 1 から 100 までの数

  • プリントする

class FizzBuzzTest < Minitest::Test
  def setup
    @fizzbuzz = FizzBuzz
  end

  def test_1を渡したら文字列1を返す
    assert_equal '1', @fizzbuzz.generate(1)
  end

  def test_2を渡したら文字列2を返す
    assert_equal '2', @fizzbuzz.generate(2)
  end

  def test_3を渡したら文字列Fizzを返す
    assert_equal 'Fizz', @fizzbuzz.generate(3)
  end

  def test_5を渡したら文字列Buzzを返す
    assert_equal 'Buzz', @fizzbuzz.generate(5)
  end
end
class FizzBuzzTest < Minitest::Test
  describe 'FizzBuzz' do
    def setup
      @fizzbuzz = FizzBuzz
    end

    describe '三の倍数の場合' do
      def test_3を渡したら文字列Fizzを返す
        assert_equal 'Fizz', @fizzbuzz.generate(3)
      end
    end

    describe '五の倍数の場合' do
      def test_5を渡したら文字列Buzzを返す
        assert_equal 'Buzz', @fizzbuzz.generate(5)
      end
    end

    describe 'その他の場合' do
      def test_1を渡したら文字列1を返す
        assert_equal '1', @fizzbuzz.generate(1)
      end

      def test_2を渡したら文字列2を返す
        assert_equal '2', @fizzbuzz.generate(2)
      end
    end
  end
end

ここでは、メソッドのインライン化 を適用してしてテストコードを読みやすくすることにしました。テストコードの 自己文書化 により動作する仕様書にすることができました。

メソッドのインライン化

メソッドの本体が、名前をつけて呼ぶまでもなく明らかである。

メソッド本体の呼び出し元にインライン化して、メソッドを除去する

— 新装版 リファクタリング

混乱せずに読めるテストコードを目指すなら(コンピュータではなく人のためにテストを書いていることを忘れてはならない)、テストメソッドの長さは3行を目指そう。

— テスト駆動開発

この関数名は「自己文書化」されている。関数名はいろんなところで使用されるのだから、優れたコメントよりも名前のほうが大切だ。

— リーダブルコード

$ ruby main.rb
$ git commit -m 'refactor: メソッドのインライン化'

さあ、TODOリスト もだいぶ消化されてきましたね。もうひと踏ん張りです。

TODOリスト

  • 数を文字列にして返す

    • 1を渡したら文字列"1"を返す
    • 2を渡したら文字列"2"を返す
  • 3の倍数のときは数の代わりに「Fizz」と返す

    • 3を渡したら文字列"Fizz"を返す
  • 5 の倍数のときは「Buzz」と返す

    • 5を渡したら文字列"Buzz"を返す
  • 3 と 5 両方の倍数の場合には「FizzBuzz」と返す

    • 15を渡したら文字列FizzBuzzを返す
  • 1 から 100 までの数

  • プリントする

...
    describe '三と五の倍数の場合' do
      def test_15を渡したら文字列FizzBuzzを返す
        assert_equal 'FizzBuzz', @fizzbuzz.generate(15)
      end
    end
...
class FizzBuzz
  def self.generate(number)
    result = number.to_s
    if number.modulo(3).zero?
      result = 'Fizz'
    elsif number.modulo(5).zero?
      result = 'Buzz'
    elsif number.modulo(15).zero?
      result = 'FizzBuzz'
    end
    result
  end
end
$ ruby main.rb
Started with run options --seed 45982

 FAIL["test_15を渡したら文字列FizzBuzzを返す", #<Minitest::Reporters::Suite:0x00007f822c00b2b0 @name="FizzBuzz::三と五の倍数の場合">, 0.00231200000
0529224]
 test_15を渡したら文字列FizzBuzzを返す#FizzBuzz::三と五の倍数の場合 (0.00s)
        Expected: "FizzBuzz"
          Actual: "Fizz"
        main.rb:25:in `test_15を渡したら文字列FizzBuzzを返す'

  4/4: [======================================================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00964s
4 tests, 4 assertions, 1 failures, 0 errors, 0 skips

おっと、調子に乗って 明白な実装 をしていたら怒られてしまいました。ここは一旦ギアを下げて小さなステップで何が問題かを調べることにしましょう。

明白な実装はセカンドギアだ。頭で考えていることがうまくコードに落とせないときは、ギアを下げる用意をしよう。

— テスト駆動開発

調べるにあたってコードを頭から読んでもいいのですが、問題が発生したのは 15を渡したら文字列FizzBuzzを返す テストを追加したあとですよね?ということは原因は追加したコードにあるはずですよね?よって、追加部分をデバッグすれば原因をすぐ発見できると思いませんか?

今回はRubyのデバッガとしてByebugをインストールして使うことにしましょう。

$ gem install byebug

インストールが完了したら早速Byebugからプログラムを起動して動作を確認してみましょう。

$ byebug main.rb

[1, 10] in /Users/k2works/Projects/hiroshima-arc/tdd_rb/docs/src/article/code/main.rb
=>  1: require 'minitest/reporters'
    2: Minitest::Reporters.use!
    3: require 'minitest/autorun'
    4:
    5: class FizzBuzzTest < Minitest::Test
    6:   describe 'FizzBuzz' do
    7:     def setup
    8:       @fizzbuzz = FizzBuzz
    9:     end
   10:
(byebug)

詳しい操作に関しては printデバッグにさようなら!Ruby初心者のためのByebugチュートリアルを参照してください。

では、問題の原因を調査するためbyebugメソッドでコード内にブレークポイントを埋め込んでデバッガを実行してみましょう。

...
    describe '三と五の倍数の場合' do
      def test_15を渡したら文字列FizzBuzzを返す
        require 'byebug'
        byebug
        assert_equal 'FizzBuzz', @fizzbuzz.generate(15)
      end
    end
...
$ byebug main.rb

[1, 10] in /Users/k2works/Projects/hiroshima-arc/tdd_rb/docs/src/article/code/main.rb
=>  1: require 'minitest/reporters'
    2: Minitest::Reporters.use!
    3: require 'minitest/autorun'
    4:
    5: class FizzBuzzTest < Minitest::Test
    6:   describe 'FizzBuzz' do
    7:     def setup
    8:       @fizzbuzz = FizzBuzz
    9:     end
   10:

ブレークポイントまで continue コマンドで処理を進めます。continue コマンドは c でもいけます。

(byebug) c
   22:
   23:     describe '三と五の倍数の場合' do
   24:       def test_15を渡したら文字列FizzBuzzを返す
   25:         require 'byebug'
   26:         byebug
=> 27:         assert_equal 'FizzBuzz', @fizzbuzz.generate(15)
   28:       end
   29:     end
   30:
   31:     describe 'その他の場合' do

続いて問題が発生した @fizzbuzz.generate(15) メソッド内にステップインします。

(byebug) s
   36:   end
   37: end
   38:
   39: class FizzBuzz
   40:   def self.generate(number)
=> 41:     result = number.to_s
   42:     if number.modulo(3).zero?
   43:       result = 'Fizz'
   44:     elsif number.modulo(5).zero?
   45:       result = 'Buzz'

引数の number15 だから elsif number.modulo(15).zero? の行で判定されるはず・・・

(byebug) s
   37: end
   38:
   39: class FizzBuzz
   40:   def self.generate(number)
   41:     result = number.to_s
=> 42:     if number.modulo(3).zero?
   43:       result = 'Fizz'
   44:     elsif number.modulo(5).zero?
   45:       result = 'Buzz'
   46:     elsif number.modulo(15).zero?
(byebug)
   38:
   39: class FizzBuzz
   40:   def self.generate(number)
   41:     result = number.to_s
   42:     if number.modulo(3).zero?
=> 43:       result = 'Fizz'

ファッ!?

   44:     elsif number.modulo(5).zero?
   45:       result = 'Buzz'
   46:     elsif number.modulo(15).zero?
   47:       result = 'FizzBuzz'
(byebug) result
"15"
(byebug) q!

15は3で割り切れるから最初の判定で処理されますよね。まあ、常にコードに注意を払って頭の中で処理しながらコードを書いていればこんなミスすることは無いのでしょうが私はドジっ子プログラマなので計算機ができることは計算機にやらせて間違いがあれば原因を調べて解決するようにしています。とりあえず、テストを通るようにしておきましょう。

class FizzBuzz
  def self.generate(number)
    result = number.to_s
    if number.modulo(3).zero?
      result = 'Fizz'
      if number.modulo(15).zero?
        result = 'FizzBuzz'
      end
    elsif number.modulo(5).zero?
      result = 'Buzz'
    end
    result
  end
end

テストが通ったのでコミットしておきます。コミットログにバグは残らないのですが作業の合間ではバグを作り込んでいましたよね。でも、テストがすぐに教えてくれるのですぐに修正することができました。結果として私のようなドジっ子プログラマでもバグの無いコードを書いているかのように見えるんですよ。

$ ruby main.rb
$ git commit -m 'test: 15を渡したら文字列FizzBuzzを返す'

私はテスト駆動開発を長年行っているので、他人にミスを気づかれる前に、自分の誤りを修正できるだけなのだ。

— テスト駆動開発

先程のコードですが・・・

class FizzBuzz
  def self.generate(number)
    result = number.to_s
    if number.modulo(3).zero?
      result = 'Fizz'
      if number.modulo(15).zero?
        result = 'FizzBuzz'
      end
    elsif number.modulo(5).zero?
      result = 'Buzz'
    end
    result
  end
end

if式 の中でさらに if式 をネストしています。いわゆる コードの不吉な臭い がしますね。ここは仕様の文言にある 3と 5 両方の倍数の場合には「FizzBuzz」とプリントすること。 に沿った記述にするとともにネストした部分をわかりやすくするために アルゴリズムの置き換え を適用してリファクタリングをしましょう。

ネストの深いコードは理解しにくい。

— リーダブルコード

class FizzBuzz
  def self.generate(number)
    result = number.to_s
    if number.modulo(3).zero? && number.modulo(5).zero?
      result = 'FizzBuzz'
    elsif number.modulo(3).zero?
      result = 'Fizz'
    elsif number.modulo(5).zero?
      result = 'Buzz'
    end
    result
  end
end

テストして、コミットです。

$ ruby main.rb
$ git commit -m 'refactor: アルゴリズムの置き換え'

休憩

TODOリスト

  • 数を文字列にして返す

    • 1を渡したら文字列"1"を返す
    • 2を渡したら文字列"2"を返す
  • 3の倍数のときは数の代わりに「Fizz」と返す

    • 3を渡したら文字列"Fizz"を返す
  • 5 の倍数のときは「Buzz」と返す

    • 5を渡したら文字列"Buzz"を返す
  • 3 と 5 両方の倍数の場合には「FizzBuzz」と返す

    • 15を渡したら文字列FizzBuzzを返す
  • 1 から 100 までの数

  • プリントする

数を引数にして文字列を返す FizzBuzz::generate メソッドはできたみたいですね。次のやることは・・・新しいメソッドを追加する必要がありそうです。気分を切り替えるため少し休憩を取りましょう。

疲れたり手詰まりになったりしたときはどうすればいいだろうか----休憩を取ろう。

— テスト駆動開発

引き続き TODOリスト を片付けたいのですが 1から100までの数 を返すプログラムを書かないといけません。3を渡したらFizzのような リテラル を返すプログラムではなく 1から100までの 配列オブジェクト を返すようなプログラムにする必要がありそうです。TODOリスト にするとこんな感じでしょうか。

TODOリスト

  • 1 から 100 までの数の配列を返す

    • 配列の初めは文字列の1を返す
    • 配列の最後は文字列の100を返す
  • プリントする

どうやら 配列オブジェクト を返すプログラムを書かないといけないようですね。え? 明白な実装 の実装イメージがわかない。そんな時はステップを小さくして 仮実装 から始めるとしましょう。

何を書くべきかわかっているときは、明白な実装を行う。わからないときには仮実装を行う。まだ正しい実装が見えてこないなら、三角測量を行う。それでもまだわからないなら、シャワーを浴びに行こう。

— テスト駆動開発

学習用テスト

配列

テストファースト でまずRubyの 配列 の振る舞いを確認していきましょう。公式リファレンスによるとRubyではArrayクラスとして定義されているようですね。空の配列を作るには [] (配列リテラル)を使えばいいみたいですね。こんな感じかな?

...
    describe '1から100までの数の配列を返す' do
      def test_配列の初めは文字列の1を返す
        result = []
        assert_equal '1', result
      end
    end
  end
end
$ ruby main.rb
Started with run options --seed 54004

 FAIL["test_配列の初めは文字列の1を返す", #<Minitest::Reporters::Suite:0x00007fd0fb93d540 @name="FizzBuzz::1から
100までの数の配列を返す">, 0.0016740000028221402]
 test_配列の初めは文字列の1を返す#FizzBuzz::1から100までの数の配列を返す (0.00s)
        Expected: "1"
          Actual: []
        main.rb:37:in `test_配列の初めは文字列の1を返す'

  5/5: [===================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00602s
5 tests, 5 assertions, 1 failures, 0 errors, 0 skips

これは同値ではないのはわかりますね。ではこうしたらどうなるでしょうか?

...
    describe '1から100までの数の配列を返す' do
      def test_配列の初めは文字列の1を返す
        result = ['1']
        assert_equal '1', result
      end
    end
  end
end
$ ruby main.rb
Started with run options --seed 32701

 FAIL["test_配列の初めは文字列の1を返す", #<Minitest::Reporters::Suite:0x00007fb36f096030 @name="FizzBuzz::1から100までの数の配列を返す">, 0.0018850000014936086]
 test_配列の初めは文字列の1を返す#FizzBuzz::1から100までの数の配列を返す (0.00s)
        Expected: "1"
          Actual: ["1"]
        main.rb:38:in `test_配列の初めは文字列の1を返す'

  5/5: [===================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.04383s
5 tests, 5 assertions, 1 failures, 0 errors, 0 skips

配列 には要素を操作するメソッドが用意されており内容を色々操作できそうですね。でも、いちいちテストコードを編集してテストを実行させるのも面倒なのでここはデバッガを使ってみましょう。まずブレークポイントを設定して・・・

...
    describe '1から100までの数の配列を返す' do
      def test_配列の初めは文字列の1を返す
        require 'byebug'
        byebug
        result = ['1']
        assert_equal '1', result
      end
    end
  end
end

デバッガを起動します。

$ byebug main.rb

[1, 10] in /Users/k2works/Projects/hiroshima-arc/tdd_rb/docs/src/article/code/main.rb
=>  1: require 'minitest/reporters'
    2: Minitest::Reporters.use!
    3: require 'minitest/autorun'
    4:
    5: class FizzBuzzTest < Minitest::Test
    6:   describe 'FizzBuzz' do
    7:     def setup
    8:       @fizzbuzz = FizzBuzz
    9:     end
   10:
(byebug)

continueでブレークポイントまで進めます。

(byebug) c
Started with run options --seed 15764

  /0: [=---=---=---=---=---=---=---=---=---=---=---=---=---=---=---=---=---=-] 0% Time: 00:00:00,  ETA: ??:??:??
[34, 43] in /Users/k2works/Projects/hiroshima-arc/tdd_rb/docs/src/article/code/main.rb
   34:
   35:     describe '1から100までの数の配列を返す' do
   36:       def test_配列の初めは文字列の1を返す
   37:         require 'byebug'
   38:         byebug
=> 39:         result = ['1']
   40:         assert_equal '1', result
   41:       end
   42:     end
   43:   end

ステップインして result の中身を確認してみましょう。

(byebug) s

[35, 44] in /Users/k2works/Projects/hiroshima-arc/tdd_rb/docs/src/article/code/main.rb
   35:     describe '1から100までの数の配列を返す' do
   36:       def test_配列の初めは文字列の1を返す
   37:         require 'byebug'
   38:         byebug
   39:         result = ['1']
=> 40:         assert_equal '1', result
   41:       end
   42:     end
   43:   end
   44: end
(byebug) result
["1"]

添字を指定して 配列 の最初の文字列を確認してみましょう。

(byebug) result
["1"]
(byebug) result[1]
nil

おや?1番目は"1"では無いようですね。配列 は0から始まるので1番目を指定するにはこうします。

(byebug) result
["1"]
(byebug) result[1]
nil
(byebug) result[0]
"1"

続いて、複数の文字列から構成される 配列 を作ってみましょう。

(byebug) result = ['1','2','3']
["1", "2", "3"]
(byebug) result[0]
"1"
(byebug) result[2]
"3"

ちなみにRubyだとこのように表記することができます。直感的でわかりやすくないですか?

(byebug) result
["1", "2", "3"]
(byebug) result.first
"1"
(byebug) result.last
"3"

最後に追加、削除、変更をやってみましょう。

(byebug) result = ['1','2','3']
["1", "2", "3"]
(byebug) result << '4'
["1", "2", "3", "4"]
(byebug) result.push('4')
["1", "2", "3", "4", "4"]
(byebug) result.delete_at(3)
"4"
(byebug) result
["1", "2", "3", "4"]
(byebug) result[2] = '30'
"30"
(byebug) result
["1", "2", "30", "4"]

配列 の振る舞いもだいぶイメージできたのでデバッガを終了させてテストコードを少し変えてみましょう。

(byebug) q
Really quit? (y/n) y
...
    describe '1から100までの数の配列を返す' do
      def test_配列の初めは文字列の1を返す
        result = ['1', '2', '3']
        assert_equal '1', result.first
        assert_equal '2', result[1]
        assert_equal '3', result.last
      end
    end
  end
end
$ ruby main.rb
Started with run options --seed 39118

  5/5: [===================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00186s
5 tests, 7 assertions, 0 failures, 0 errors, 0 skips

変数 result に配列を返すメソッドを作れば良さそうですね。とりあえずメソッド名は今の時点ではあまり考えずに・・・

...
    describe '1から100までの数の配列を返す' do
      def test_配列の初めは文字列の1を返す
        result = FizzBuzz.print_1_to_100
        assert_equal '1', result.first
      end
    end
  end
end
$ ruby main.rb
Started with run options --seed 19247

ERROR["test_配列の初めは文字列の1を返す", #<Minitest::Reporters::Suite:0x00007faaea925058 @name="FizzBuzz::1から
100までの数の配列を返す">, 0.0017889999980980065]
 test_配列の初めは文字列の1を返す#FizzBuzz::1から100までの数の配列を返す (0.00s)
NoMethodError:         NoMethodError: undefined method `print_1_to_100' for FizzBuzz:Class
            main.rb:37:in `test_配列の初めは文字列の1を返す'

  5/5: [===================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00454s
5 tests, 4 assertions, 0 failures, 1 errors, 0 skips

ここまでくれば 仮実装 はできますね。

class FizzBuzz
  def self.generate(number)
    result = number.to_s
    if number.modulo(3).zero? && number.modulo(5).zero?
      result = 'FizzBuzz'
    elsif number.modulo(3).zero?
      result = 'Fizz'
    elsif number.modulo(5).zero?
      result = 'Buzz'
    end
    result
  end

  def self.print_1_to_100
    [1, 2, 3]
  end
end
$ ruby main.rb
Started with run options --seed 24564

 FAIL["test_配列の初めは文字列の1を返す", #<Minitest::Reporters::Suite:0x00007fefd8917060 @name="FizzBuzz::1から
100までの数の配列を返す">, 0.0011969999977736734]
 test_配列の初めは文字列の1を返す#FizzBuzz::1から100までの数の配列を返す (0.00s)
        Expected: "1"
          Actual: 1
        main.rb:38:in `test_配列の初めは文字列の1を返す'

  5/5: [===================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00209s
5 tests, 5 assertions, 1 failures, 0 errors, 0 skips

ファッ!?、ああ、数字ではなく文字列で返すのだからこうですね。

...
  def self.print_1_to_100
    ['1', '2', '3']
  end
end

%記法 を使うとよりRubyらしく書けます。

...
  def self.print_1_to_100
    %w[1 2 3]
  end
end

%記法とは、文字列や正規表現などを定義する際に、%を使った特別な書き方をすることでエスケープ文字を省略するなど、可読性を高めることができる記法です。

— かんたんRuby

$ ruby main.rb
Started with run options --seed 42995

  5/5: [===================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00195s
5 tests, 5 assertions, 0 failures, 0 errors, 0 skips

TODOリスト の1つ目を 仮実装 で片づけことができました。ちなみにテストコードを使ってソフトウェアの振る舞いを検証するテクニックを 学習用テスト と言います。

学習用テスト

チーム外の誰かが書いたソフトウェアのテストを書くのはどのようなときか----そのソフトウェアの新機能を初めて使う際に書いてみよう。

— テスト駆動開発

TODOリスト

  • 1 から 100 までの数の配列を返す

    • 配列の初めは文字列の1を返す
    • 配列の最後は文字列の100を返す
  • プリントする

繰り返し処理

FizzBuzz::print_1_to_100 メソッドはまだ最後の要素が検証されていませんね。三角測量 を使って小さなステップで進めていくことにしましょう。

...
    describe '1から100までの数の配列を返す' do
      def test_配列の初めは文字列の1を返す
        result = FizzBuzz.print_1_to_100
        assert_equal '1', result.first
      end

      def test_配列の最後は文字列の100を返す
        result = FizzBuzz.print_1_to_100
        assert_equal '100', result.last
      end
    end
  end
end
$ ruby main.rb
Started with run options --seed 12031

 FAIL["test_配列の最後は文字列の100を返す", #<Minitest::Reporters::Suite:0x00007fccc9828500 @name="FizzBuzz::1から100までの数の配列を返す">, 0.0018540000019129366]
 test_配列の最後は文字列の100を返す#FizzBuzz::1から100までの数の配列を返す (0.00s)
        Expected: "100"
          Actual: "3"
        main.rb:43:in `test_配列の最後は文字列の100を返す'

  6/6: [===================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.02936s

配列は3までなので想定通りテストは失敗します。さて、1から100までの文字列で構成される配列をどうやって作りましょうか? 先程は if式 を使って 条件分岐 をプログラムで実行しました。今回は 繰り返し処理 をプログラムで実行する必要がありそうですね。Rubyの繰り返し処理には for式 while/until/loop などがありますが実際のところ eachメソッド を使った繰り返し処理が主流です。とはいえ、実際に動かして振る舞いを確認しないとイメージは難しいですよね。 学習用テスト を書いてもいいのですが今回は irb上で簡単なコードを動かしてみる6ことで振る舞いを検証してみましょう。まずコマンドラインでirbを起動します。

Rubyにはfor文はあります。ですが、ほとんどのRubyプログラマはfor文を使いません。筆者も5〜6年Rubyを使っていますが、for文を書いたことは一度もありません。Rubyの場合はforのような構文で繰り返し処理をさせるのではなく、配列自身に対して「繰り返せ」という命令を送ります。ここで登場するのがeachメソッドです。

— プロを目指す人のためのRuby入門

$ irb
irb(main):001:0>

まず先程デバッガで検証した配列の作成をやってみましょう。

irb(main):001:0> result = %w[1 2 3]
=> ["1", "2", "3"]

配列のeachメソッドをつかって配列の中身を繰り返し処理で表示させてみましょう。p はプリントメソッドです。

irb(main):003:0> result.each do |n| p n end
"1"
"2"
"3"
=> ["1", "2", "3"]

配列の中身を繰り返し処理で取り出す方法はわかりました。あとは100までの配列をどうやって作ればよいのでしょうか?['1','2','3'…​'100']と手書きで作りますか?100件ぐらいならまあできなくもないでしょうが1000件,10000件ならどうでしょうか?無理ですね。計算機にやってもらいましょう、調べてみるとRubyには レンジオブジェクト(Range) というもの用意されいるそうです。説明を読んでもピンと来ないので実際に動作を確認してみましょう。

レンジオブジェクト(範囲オブジェクトとも呼ばれます)はRangeクラスのオブジェクトのことで、「..」や「…​」演算子を使って定義します。「1..3」のように定義し、主に整数値や文字列を使って範囲を表現します。

— かんたんRuby

irb(main):008:0> (1..5).each do |n| p n end
1
2
3
4
5
=> 1..5
irb(main):009:0> (1...5).each do |n| p n end
1
2
3
4

100まで表示したいのでこうですね。

irb(main):010:0> (1..100).each do |n| p n end
1
2
3
..
99
100
=> 1..100

FizzBuzz::print_1_to_100 メソッド明白な実装 イメージができましたか? irb を終了させてプロダクトコードを変更しましょう。

irb(main):011:0> exit
...
  def self.print_1_to_100
    result = []
    (1..100).each do |n|
      result << n
    end
    result
  end
end
$ ruby main.rb
Started with run options --seed 38412

 FAIL["test_配列の初めは文字列の1を返す", #<Minitest::Reporters::Suite:0x00007f858480edf8 @name="FizzBuzz::1から
100までの数の配列を返す">, 0.0012219999989611097]
 test_配列の初めは文字列の1を返す#FizzBuzz::1から100までの数の配列を返す (0.00s)
        Expected: "1"
          Actual: 1
        main.rb:38:in `test_配列の初めは文字列の1を返す'

 FAIL["test_配列の最後は文字列の100を返す", #<Minitest::Reporters::Suite:0x00007f858480c8f0 @name="FizzBuzz::1から100までの数の配列を返す">, 0.0014040000023669563]
 test_配列の最後は文字列の100を返す#FizzBuzz::1から100までの数の配列を返す (0.00s)
        Expected: "100"
          Actual: 100
        main.rb:43:in `test_配列の最後は文字列の100を返す'

  6/6: [===================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00218s
6 tests, 6 assertions, 2 failures, 0 errors, 0 skips

ファッ!?また、やらかしました。文字列に変換しなといけませんね。

...
  def self.print_1_to_100
    result = []
    (1..100).each do |n|
      result << n.to_s
    end
    result
  end
end
$ ruby main.rb
Started with run options --seed 40179

  6/6: [===================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00196s
6 tests, 6 assertions, 0 failures, 0 errors, 0 skips

ちなみに、do …​ endを使う代わりに、{}で囲んでもブロックを作れる6のでこのように書き換えることができます。

...
  def self.print_1_to_100
    result = []
    (1..100).each { |n| result << n.to_s }
    result
  end
end

ここで、一旦コミットしておきましょう。

$ git commit -m 'test: 1から100までの数を返す'

TODOリスト

  • 1 から 100 までの数の配列を返す

    • 配列の初めは文字列の1を返す
    • 配列の最後は文字列の100を返す
  • プリントする

メソッド呼び出し

1から100までの数の配列を返すメソッドはできました。しかし、このプログラムは1から100までの数を FizzBuzz::generate した結果を返すのが正しい振る舞いですよね。 TODOリスト を追加してテストも追加します。

TODOリスト

  • 1 から 100 までの数の配列を返す

    • 配列の初めは文字列の1を返す
    • 配列の最後は文字列の100を返す
    • 配列の2番めは文字列のFizzを返す
  • プリントする

...
      def test_配列の2番目は文字列のFizzを返す
        result = FizzBuzz.print_1_to_100
        assert_equal 'Fizz', result[2]
      end
    end
  end
end
$ ruby main.rb
Started with run options --seed 50411

 FAIL["test_配列の2番目は文字列のをFizz返す", #<Minitest::Reporters::Suite:0x00007fe8a1917dc8 @name="FizzBuzz::1から100までの数の配列を返す">, 0.01608900000428548]
 test_配列の2番目は文字列のFizzを返す#FizzBuzz::1から100までの数の配列を返す (0.02s)
        --- expected
        +++ actual
        @@ -1 +1,3 @@
        -"Fizz"
        +# encoding: US-ASCII
        +#    valid: true
        +"3"
        main.rb:48:in `test_配列の2番目は文字列のFizzを返す'

  7/7: [===================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.03112s
7 tests, 7 assertions, 1 failures, 0 errors, 0 skips

ですよね、ここは 繰り返し処理 の中で FizzBuzz::generate を呼び出すように変更しましょう。

...
  def self.print_1_to_100
    result = []
    (1..100).each { |n| result << generate(n) }
    result
  end
end
$ ruby main.rb
Started with run options --seed 15549

 FAIL["test_配列の最後は文字列の100を返す", #<Minitest::Reporters::Suite:0x00007ff80a907e28 @name="FizzBuzz::1から100までの数の配列を返す">, 0.001347000004898291]
 test_配列の最後は文字列の100を返す#FizzBuzz::1から100までの数の配列を返す (0.00s)
        Expected: "100"
          Actual: "Buzz"
        main.rb:43:in `test_配列の最後は文字列の100を返す'

  7/7: [===================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00218s
7 tests, 7 assertions, 1 failures, 0 errors, 0 skips

新規に追加したテストはパスしたのですが2つ目のテストが失敗しています。これはテストケースが間違っていますね。

...
      def test_配列の最後は文字列のBuzzを返す
        result = FizzBuzz.print_1_to_100
        assert_equal 'Buzz', result.last
      end

      def test_配列の2番目は文字列のFizzを返す
        result = FizzBuzz.print_1_to_100
        assert_equal 'Fizz', result[2]
      end
    end
  end
end
$ ruby main.rb
Started with run options --seed 21247

  7/7: [===================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00217s
7 tests, 7 assertions, 0 failures, 0 errors, 0 skips

他のパターンも明記しておきましょう。

...
    describe '1から100までのFizzBuzzの配列を返す' do
      def test_配列の初めは文字列の1を返す
        result = FizzBuzz.print_1_to_100
        assert_equal '1', result.first
      end

      def test_配列の最後は文字列のBuzzを返す
        result = FizzBuzz.print_1_to_100
        assert_equal 'Buzz', result.last
      end

      def test_配列の2番目は文字列のFizzを返す
        result = FizzBuzz.print_1_to_100
        assert_equal 'Fizz', result[2]
      end

      def test_配列の4番目は文字列のBuzzを返す
        result = FizzBuzz.print_1_to_100
        assert_equal 'Buzz', result[4]
      end

      def test_配列の14番目は文字列のFizzBuzzを返す
        result = FizzBuzz.print_1_to_100
        assert_equal 'FizzBuzz', result[14]
      end
    end
  end
end

説明変数 への代入が重複しています。ついでに メソッドの抽出 をして重複をなくしておきましょう。

最初のステップ「準備(Arrange)」は、テスト間で重複しがちだ。それとは対象的に「実行(Act)」「アサート(Assert)」は重複しないことが多い。

— テスト駆動開発

...
    describe '1から100までのFizzBuzzの配列を返す' do
      def setup
        @result = FizzBuzz.print_1_to_100
      end

      def test_配列の初めは文字列の1を返す
        assert_equal '1', @result.first
      end

      def test_配列の最後は文字列のBuzzを返す
        assert_equal 'Buzz', @result.last
      end

      def test_配列の2番目は文字列のFizzを返す
        assert_equal 'Fizz', @result[2]
      end

      def test_配列の4番目は文字列のBuzzを返す
        assert_equal 'Buzz', @result[4]
      end

      def test_配列の14番目は文字列のFizzBuzzを返す
        assert_equal 'FizzBuzz', @result[14]
      end
    end
  end
$ ruby main.rb
Started with run options --seed 17460

  9/9: [===================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00207s
9 tests, 9 assertions, 0 failures, 0 errors, 0 skips

とりあえず、現時点で仕様を満たすプログラムにはなったみたいですね。

$ git commit -m 'test: 1から100までのFizzBuzzの配列を返す'

TODOリスト

  • 1 から 100 までのFizzBuzzの配列を返す

    • 配列の初めは文字列の1を返す
    • 配列の最後は文字列の100を返す
    • 配列の2番めは文字列のFizzを返す
    • 配列の4番目は文字列のBuzzを返す
    • 配列の14番目は文字列のFizzBuzzを返す
  • プリントする

配列や繰り返し処理の理解

まだリファクタリングが残っているのですがその前にRubyの配列メソッドの理解をもう少し深めたいので 学習用テスト を追加しましょう。

...
  describe '配列や繰り返し処理を理解する' do
    def test_繰り返し処理
      $stdout = StringIO.new
      [1, 2, 3].each { |i| p i * i }
      output = $stdout.string

      assert_equal "1\n" + "4\n" + "9\n", output
    end

    def test_特定の条件を満たす要素だけを配列に入れて返す
      result = [1.1, 2, 3.3, 4].select(&:integer?)
      assert_equal [2, 4], result
    end

    def test_特定の条件を満たす要素だけを配列に入れて返す
      result = [1.1, 2, 3.3, 4].find_all(&:integer?)
      assert_equal [2, 4], result
    end

    def test_特定の条件を満たさない要素だけを配列に入れて返す
      result = [1.1, 2, 3.3, 4].reject(&:integer?)
      assert_equal [1.1, 3.3], result
    end

    def test_新しい要素の配列を返す
      result = %w[apple orange pineapple strawberry].map(&:size)
      assert_equal [5, 6, 9, 10], result
    end

    def test_新しい要素の配列を返す
      result = %w[apple orange pineapple strawberry].collect(&:size)
      assert_equal [5, 6, 9, 10], result
    end

    def test_配列の中から条件に一致する要素を取得する
      result = %w[apple orange pineapple strawberry].find(&:size)
      assert_equal 'apple', result
    end

    def test_配列の中から条件に一致する要素を取得する
      result = %w[apple orange pineapple strawberry].detect(&:size)
      assert_equal 'apple', result
    end

    def test_指定した評価式で並び変えた配列を返す
      assert_equal %w[1 10 13 2 3 4], %w[2 4 13 3 1 10].sort
      assert_equal %w[1 2 3 4 10 13],
                   %w[2 4 13 3 1 10].sort { |a, b| a.to_i <=> b.to_i }
      assert_equal %w[13 10 4 3 2 1],
                   %w[2 4 13 3 1 10].sort { |b, a| a.to_i <=> b.to_i }
    end

    def test_配列の中から、条件に一致する要素を取得する
      result = %w[apple orange pineapple strawberry apricot].grep(/^a/)
      assert_equal %w[apple apricot], result
    end

    def test_ブロック内の条件式が真である間までの要素を返す
      result = [1, 2, 3, 4, 5, 6, 7, 8, 9].take_while { |item| item < 6 }
      assert_equal [1, 2, 3, 4, 5], result
    end

    def test_ブロック内の条件式が真である以降の要素を返す
      result = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].drop_while { |item| item < 6 }
      assert_equal [6, 7, 8, 9, 10], result
    end

    def test_畳み込み演算を行う
      result = [1, 2, 3, 4, 5].inject(0) { |total, n| total + n }
      assert_equal 15, result
    end

    def test_畳み込み演算を行う
      result = [1, 2, 3, 4, 5].reduce { |total, n| total + n }
      assert_equal 15, result
    end
  end
end
$ ruby main.rb
Started with run options --seed 18136

  19/19: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00307s
19 tests, 21 assertions, 0 failures, 0 errors, 0 skips
$ git commit -m 'test: 学習用テスト'

コードの不吉な臭い

終わりが見えてきましたがまだリファクタリングの必要がありそうです。

開発を終えるまでに考えつくまでに考えつく限りのテストを書き、テストに支えられたリファクタリングが、網羅性のあるテストに支えられてたリファクタリングになるようにしなければならない。

— テスト駆動開発

ここでプロダクトコードを眺めてみましょう。

class FizzBuzz
  def self.generate(number)
    result = number.to_s
    if number.modulo(3).zero? && number.modulo(5).zero?
      result = 'FizzBuzz'
    elsif number.modulo(3).zero?
      result = 'Fizz'
    elsif number.modulo(5).zero?
      result = 'Buzz'
    end
    result
  end

  def self.print_1_to_100
    result = []
    (1..100).each { |n| result << generate(n) }
    result
  end
end

コードの不吉な臭い が漂ってきませんか?私が感じた部分を解説していきますね。

不思議な名前

不思議な名前

明快なコードにするために最も重要なのは、適切な名前付けです。

— リファクタリング(第2版)

変数や関数などの構成要素の名前は、抽象的ではなく具体的なものにしよう。

— リーダブルコード

まず、気になったのが print_1_to_100 メソッドです。このメソッドはFizzBuzzの配列を返すメソッドであって1から100までを表示するメソッドではありませんよね。ここは メソッド名の変更 を適用して処理の内容に沿った名前に変更しましょう。え?動いている処理をわざわざ変更してプログラムを壊す危険を犯す必要があるのかですって。確かに自動テストのない状況でドジっ子プログラマがそんなことをすればいずれ残念なことになるでしょうね。でも、すでに自動テストが用意されている今なら自信をもって動いている処理でも変更できますよね。

リファクタリングに入る前に、しっかりとした一連のテスト群を用意しておくこと。これらのテストには自己診断機能が不可欠である。

— リファクタリング(第2版)

テストは不安を退屈に変える賢者の石だ。

— テスト駆動開発

...
  def self.print_1_to_100
    result = []
    (1..100).each { |n| result << generate(n) }
    result
  end
end
...
  def self.generate_list
    result = []
    (1..100).each { |n| result << generate(n) }
    result
  end
end

変更で壊れていないか確認します。

$ ruby main.rb
Started with run options --seed 47414

ERROR["test_配列の初めは文字列の1を返す", #<Minitest::Reporters::Suite:0x00007fe9e6858108 @name="FizzBuzz::1から
100までのFizzBuzzの配列を返す">, 0.0023099999998521525]
 test_配列の初めは文字列の1を返す#FizzBuzz::1から100までのFizzBuzzの配列を返す (0.00s)
NoMethodError:         NoMethodError: undefined method `print_1_to_100' for FizzBuzz:Class
            main.rb:37:in `setup'
...

ERROR["test_配列の最後は文字列のBuzzを返す", #<Minitest::Reporters::Suite:0x00007fe9f7097160 @name="FizzBuzz::1から100までのFizzBuzzの配列を返す">, 0.011574000000109663]
 test_配列の最後は文字列のBuzzを返す#FizzBuzz::1から100までのFizzBuzzの配列を返す (0.01s)
NoMethodError:         NoMethodError: undefined method `print_1_to_100' for FizzBuzz:Class
            main.rb:37:in `setup'

  19/19: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.01479s
19 tests, 16 assertions, 0 failures, 5 errors, 0 skips

いきなり失敗しちゃいました。でも、焦らずエラーメッセージを読みましょう。 NoMethodError: NoMethodError:undefined method `print_1_to_100' for FizzBuzz:Class メソッド名の変更したけどテストは以前のままでしたね。

...
    describe '1から100までのFizzBuzzの配列を返す' do
      def setup
        @result = FizzBuzz.print_1_to_100
      end
...
...
    describe '1から100までのFizzBuzzの配列を返す' do
      def setup
        @result = FizzBuzz.generate_list
      end
...
$ ruby main.rb
Started with run options --seed 54699

  19/19: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00351s
19 tests, 21 assertions, 0 failures, 0 errors, 0 skips

プロダクトコードは壊れていなことが確認できたので自信を持ってコミットしておきましょう。

$ git commit -m 'refactor: メソッド名の変更'

TDDにおけるテストの考え方は実用主義に貫かれている。TDDにおいてテストは目的を達成するための手段であり、その目的は、大きなる自信を伴うコードだ。

— テスト駆動開発

長い関数

長い関数

経験上、長く充実した人生を送るのは、短い関数を持ったプログラムです。

— リファクタリング(第2版)

次に気になったのが FizzBuzz::generate メソッド内のif分岐処理ですね。こうした条件分岐には仕様変更の際に追加ロジックが新たなif分岐として追加されてどんどん長くなって読みづらいコードに成長する危険性があります。そういうコードは早めに対策を打っておくのが賢明です。

class FizzBuzz
  def self.generate(number)
    result = number.to_s
    if number.modulo(3).zero? && number.modulo(5).zero?
      result = 'FizzBuzz'
    elsif number.modulo(3).zero?
      result = 'Fizz'
    elsif number.modulo(5).zero?
      result = 'Buzz'
    end
    result
  end

  def self.generate_list
    result = []
    (1..100).each { |n| result << generate(n) }
    result
  end
end

まずコードをもう少し読みやすくしましょう。

class FizzBuzz
  def self.generate(number)
    result = number.to_s

    if number.modulo(3).zero? && number.modulo(5).zero?
      result = 'FizzBuzz'
    elsif number.modulo(3).zero?
      result = 'Fizz'
    elsif number.modulo(5).zero?
      result = 'Buzz'
    end

    result
  end

  def self.generate_list
    result = []

    (1..100).each { |n| result << generate(n) }

    result
  end
end

FizzBuzzメソッド は大きく分けて 変数 の初期化 条件分岐 繰り返し処理 による判断、計算そして結果の 代入 を行い最後に 代入 された 変数 を返す流れになっています。 そこで各単位ごとにスペースを挿入してコードの可読性を上げておきましょう。

人間の脳はグループや階層を1つの単位として考える。コードの概要をすばやく把握してもらうには、このような「単位」を作ればいい。

— リーダブルコード

処理の単位ごとに区切りをつけました。次はif分岐ですがこうします。

class FizzBuzz
  def self.generate(number)
    result = number.to_s

    if number.modulo(3).zero? && number.modulo(5).zero?
      result = 'FizzBuzz'
    elsif number.modulo(3).zero?
      result = 'Fizz'
    elsif number.modulo(5).zero?
      result = 'Buzz'
    end

    result
  end
...
class FizzBuzz
  def self.generate(number)
    result = number.to_s

    return 'FizzBuzz' if number.modulo(3).zero? && number.modulo(5).zero?
    return 'Fizz' if number.modulo(3).zero?
    return 'Buzz' if number.modulo(5).zero?

    result
  end
...
$ ruby main.rb
Started with run options --seed 62095

  19/19: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00296s
19 tests, 21 assertions, 0 failures, 0 errors, 0 skips

条件に該当した場合は処理を最後まで進めずその場で終了させる書き方を ガード節 と言います。このように書くことで追加ロジックが発生しても既存のコードを編集することなく追加することができるので安全に簡単に変更できるコードにすることができます。

ガード節による入れ子条件記述の置き換え

メソッド内に正常ルートが不明確な条件つき振る舞いがある。

特殊ケースすべてに対してガード節を使う。

— 新装版 リファクタリング

関数で複数のreturn文を使ってはいけないと思っている人がいる。アホくさ。関数から早く返すのはいいことだ。むしろ望ましいときもある。

— リーダブルコード

$ git commit -m 'refactor: ガード節による入れ子条件の置き換え'

どの条件にも該当しない場合は数字を文字列してかえすのですが 一時変数result は最後でしか使われていませんね。このような場合は 変数のインライン化 を適用しましょう。

一時変数のインライン化

簡単な式によって一度だけ代入される一時変数があり、それが他のリファクタリングの障害となっている。

その一時変数への参照をすべて式で置き換える。

— 新装版 リファクタリング

class FizzBuzz
  def self.generate(number)
    result = number.to_s

    return 'FizzBuzz' if number.modulo(3).zero? && number.modulo(5).zero?
    return 'Fizz' if number.modulo(3).zero?
    return 'Buzz' if number.modulo(5).zero?

    result
  end
...
class FizzBuzz
  def self.generate(number)
    return 'FizzBuzz' if number.modulo(3).zero? && number.modulo(5).zero?
    return 'Fizz' if number.modulo(3).zero?
    return 'Buzz' if number.modulo(5).zero?
    number.to_s
  end
...
$ ruby main.rb
Started with run options --seed 2528

  19/19: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00255s
19 tests, 21 assertions, 0 failures, 0 errors, 0 skips

変更によって壊れていないことが確認できたのでコミットします。

$ git commit -m 'refactor: 変数のインライン化'

続いて、FizzBuzzを判定する部分ですがもう少しわかりやすくするため 説明用変数の導入 を適用します。

説明用変数の導入

複雑な式がある。

その式の結果または部分的な結果を、その目的を説明する名前をつけた一時変数に代入する。

— リファクタリング(第2版)

class FizzBuzz
  def self.generate(number)
    return 'FizzBuzz' if number.modulo(3).zero? && number.modulo(5).zero?
    return 'Fizz' if number.modulo(3).zero?
    return 'Buzz' if number.modulo(5).zero?
    number.to_s
  end
...
class FizzBuzz
  def self.generate(number)
    isFizz = number.modulo(3).zero?
    isBuzz = number.modulo(5).zero?

    return 'FizzBuzz' if number.modulo(3).zero? && number.modulo(5).zero?
    return 'Fizz' if isFizz
    return 'Buzz' if isBuzz
    number.to_s
  end
...

3で割り切れる場合の結果を isFizz 変数に 5で割り切れる場合の結果 isBuzz 変数に代入して使えるようにしました。このような変数を 説明変数 と呼びます。また似たようなパターンに 要約変数 というものがあります。FizzBuzzを返す判定部分にこの 説明変数 を適用しました。壊れていないか確認しておきましょう。

説明変数

式を簡単に分割するには、式を表す変数を使えばいい。この変数を「説明変数」と呼ぶこともある。

— リーダブルコード

要約変数

大きなコードをの塊を小さな名前に置き換えて、管理や把握を簡単にする変数のことを要約変数と呼ぶ。

— リーダブルコード

class FizzBuzz
  def self.generate(number)
    isFizz = number.modulo(3).zero?
    isBuzz = number.modulo(5).zero?

    return 'FizzBuzz' if isFizz && isBuzz
    return 'Fizz' if isFizz
    return 'Buzz' if isBuzz
    number.to_s
  end
...
$ ruby main.rb
Started with run options --seed 4314

  19/19: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00262s
19 tests, 21 assertions, 0 failures, 0 errors, 0 skips

壊れていませんね。ではコミットしておきましょう。

$ ruby main.rb
$ git commit -m 'refactor: 変数の抽出'

ループと変更可能なデータ

ループ

プログラミング言語の黎明期から、ループは中心的な存在でした。しかし今ではベルボトムのジーンズやペナントのお土産のように、あまり重要でなくなりつつあります。

— リファクタリング(第2版)

FizzBuzz::generate メソッドのリファクタリングはできたので続いて FizzBuzz::generate_list メソッドを見ていきましょう。

...
  def self.generate_list
    result = []

    (1..100).each { |n| result << generate(n) }

    result
  end
end

空の 配列 を変数に代入してその変数に FizzBuzz::generate メソッドの結果を追加して返す処理ですがもしこのような変更をしてしまったらどうなるでしょうか?

...
  def self.generate_list
    result = []

    (1..100).each { |n| result << generate(n) }

    result = []
    result
  end
end
$ ruby main.rb
Started with run options --seed 19180

 FAIL["test_配列の14番目は文字列のをFizzBuzz返す", #<Minitest::Reporters::Suite:0x00007fa72805c018 @name="FizzBuzz::1から100までのFizzBuzzの配列を返す">, 0.0021289999967848416]
 test_配列の14番目は文字列のをFizzBuzz返す#FizzBuzz::1から100までのFizzBuzzの配列を返す (0.00s)
        Expected: "FizzBuzz"
          Actual: nil
        main.rb:57:in `test_配列の14番目は文字列のをFizzBuzz返す'
...

Finished in 0.03063s
19 tests, 21 assertions, 5 failures, 0 errors, 0 sk

せっかく作った配列を初期化して返してしまいましたね。このようにミュータブルな変数はバグを作り込む原因となる傾向があります。まず一時変数を使わないように変更しましょう。

変更可能なデータ

データの変更はしばしば予期せぬ結果や、厄介なバグを引き起こします。

— リファクタリング(第2版)

「永続的に変更されない」変数は扱いやすい。

— リーダブルコード

...
  def self.generate_list
    return (1..100).each { |n| result << generate(n) }
  end
end
$ ruby main.rb
Started with run options --seed 56578

ERROR["test_配列の4番目は文字列のをBuzz返す", #<Minitest::Reporters::Suite:0x00007fe705854af0 @name="FizzBuzz::1から100までのFizzBuzzの配列を返す">, 0.001975000002857996]
 test_配列の4番目は文字列のをBuzz返す#FizzBuzz::1から100までのFizzBuzzの配列を返す (0.00s)
NameError:         NameError: undefined local variable or method `result' for FizzBuzz:Class
            main.rb:153:in `block in generate_list'
            main.rb:153:in `each'
            main.rb:153:in `generate_list'
            main.rb:37:in `setup'
...
  19/19: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.01032s
19 tests, 16 assertions, 0 failures, 5 errors, 0 skips

一時変数 result は使わないので

...
  def self.generate_list
    return (1..100).each { |n| generate(n) }
  end
end
$ ruby main.rb
Started with run options --seed 35137

ERROR["test_配列の4番目は文字列のをBuzz返す", #<Minitest::Reporters::Suite:0x00007f7f1384ff78 @name="FizzBuzz::1から100までのFizzBuzzの配列を返す">, 0.0014560000017809216]
 test_配列の4番目は文字列のをBuzz返す#FizzBuzz::1から100までのFizzBuzzの配列を返す (0.00s)
NoMethodError:         NoMethodError: undefined method `[]' for 1..100:Range
            main.rb:53:in `test_配列の4番目は文字列のをBuzz返す'
...
  19/19: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.03285s
19 tests, 18 assertions, 2 failures, 3 errors, 0 skips

結果を配列にして返したいのですが eachメソッド ではうまくできませんね。Rubyには新しい配列を作成する mapメソッド が用意されいるのでそちらを使いましょう。

mapは配列の要素を画する際によく利用されるメソッドで、ブロックの最後の要素(メモ)で新しい配列を作ります。

— かんたんRuby

...
  def self.generate_list
    return (1..100).map { |n| generate(n) }
  end
end
 $ ruby main.rb
Started with run options --seed 44043

  19/19: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00261s
19 tests, 21 assertions, 0 failures, 0 errors, 0 skips

うまくいきましたね。あと、Rubyではreturnを省略できるので

...
  def self.generate_list
    (1..100).map { |n| generate(n) }
  end
end
$ ruby main.rb
Started with run options --seed 7994

  19/19: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00238s

パイプラインによるループの置き換え の適用により eachメソッド による繰り返し処理を mapメソッド を使ったイミュータブルなコレクションパイプライン処理に変えることができました。

パイプラインによるループの置き換え

多くのプログラマと同様に、私もオブジェクトの集合の反復処理にはループを使うように教えられました。しかし言語環境は、よりすぐれた仕組みとしてコレクションのパイプラインを提供するようになりました。

— リファクタリング(第2版)

Rubyに限らず、プログラミングの世界ではしばしばミュータブル(mutable)とイミュータブル(imutable)と言う言葉が登場します。ミュータブルは「変更可能な」という意味で、反対にイミュータブルは「変更できない、不変の」という意味です。

— プロを目指す人のためのRuby入門

$ git commit -m 'refactor: パイプラインによるループの置き換え'

マジックナンバー

最大値は100にしていますが変更することもあるので マジックナンバーの置き換え を適用してわかりやすくしておきましょう。

シンボル定数によるマジックナンバーの置き換え

特別な意味を持った数字のリテラルがある。

定数を作り、それにふさわしい名前をつけて、そのリテラルを置き換える。

— 新装版 リファクタリング

Rubyでは定数は英字の大文字で始まる名前をつけると自動的に定数として扱われます。

class FizzBuzz
  MAX_NUMBER = 100

...

  def self.generate_list
    (1..MAX_NUMBER).map { |n| generate(n) }
  end
end

意味のわかる定数として宣言しました。コードに直接記述された 100 をといった 数値リテラル はマジックナンバーと呼ばれ往々にして後で何を意味するものかわからなくなり変更を難しくする原因となります。早めに意味を表す定数にしておきましょう。

名前付けされずにプログラム内に直接記述されている数値をマジックナンバーと呼び、一般的には極力避けるようにします。

— かんたんRuby

いい名前というのは、変数の目的や値を表すものだ。

— リーダブルコード

$ ruby main.rb
Started with run options --seed 32408

  19/19: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00241s
19 tests, 21 assertions, 0 failures, 0 errors, 0 skips

テストは通りました。でもこのコードは初見の人には分かりづらいのでコメントを入れておきましょう。Rubyの 単一行コメントアウト のやり方は行頭に # を使います。

...
  def self.generate_list
    # 1から最大値までのFizzBuzz配列を1発で作る
    (1..MAX_NUMBER).map { |n| generate(n) }
  end
end

ここではなぜこのような処理を選択したかをコメントしましたが何でもコメントすればよいというわけではありません。

コメント

ここでコメントについて言及しているのは、コメントが消臭剤として使われることがあるからです。コメントが非常に丁寧に書かれているのは、実はわかりにくいコードを補うためだったとうことがよくあるのです。

— リファクタリング(第2版)

コメントを書くのであれば、正確に書くべきだ(できるだけ明確で詳細に)。また、コメントには画面の領域を取られるし、読むのにも時間がかかるので、簡潔なものでなければいけない。

— リーダブルコード

$ git commit -m 'refactor: マジックナンバーの置き換え'

動作するきれいなコード

TODOリスト

  • 数を文字列にして返す

    • 1を渡したら文字列"1"を返す
    • 2を渡したら文字列"2"を返す
  • 3の倍数のときは数の代わりに「Fizz」と返す

    • 3を渡したら文字列"Fizz"を返す
  • 5 の倍数のときは「Buzz」と返す

    • 5を渡したら文字列"Buzz"を返す
  • 13 と 5 両方の倍数の場合には「FizzBuzz」と返す

    • 15を渡したら文字列FizzBuzzを返す
  • 1 から 100 までのFizzBuzzの配列を返す

    • 配列の初めは文字列の1を返す
    • 配列の最後は文字列の100を返す
    • 配列の2番めは文字列のFizzを返す
    • 配列の4番目は文字列のBuzzを返す
    • 配列の14番目は文字列のFizzBuzzを返す
  • プリントする

TODOリスト も残すところあと1つとなりました。これまで main.rb ファイル1つだけで開発を行ってきましたがリリースするにはもうひと手間かけたほうがいいでしょうね。libディレクトリを作成したあと main.rb ファイルを fizz_buzz.rb ファイルに名前を変更してlibディレクトリに移動します。

/
|--lib/
    |
     -- fizz_buzz.rb

続いてテストコードをテストディレクトリに保存してプログラム本体とテストコードを分離します

/
|--lib/
    |
     -- fizz_buzz.rb
|--test/
    |
     -- fizz_buzz_test.rb

分離したテストが動くか確認しておきましょう。

$ ruby test/fizz_buzz_test.rb
Started with run options --seed 17134

ERROR["test_1を渡したら文字列1を返す", #<Minitest::Reporters::Suite:0x00007fc07a085060 @name="FizzBuzz::その他の場合">, 0.001282999997783918]
 test_1を渡したら文字列1を返す#FizzBuzz::その他の場合 (0.00s)
NameError:         NameError: uninitialized constant FizzBuzzTest::FizzBuzz
        Did you mean?  FizzBuzzTest
            test/fizz_buzz_test.rb:8:in `setup'
...
  19/19: [===============================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.03717s
19 tests, 12 assertions, 0 failures, 9 errors, 0 skips

テストファイルからFizzBuzzクラスを読み込めるようにする必要があります。

require 'minitest/reporters'
Minitest::Reporters.use!
require 'minitest/autorun'
require './lib/fizz_buzz'

class FizzBuzzTest < Minitest::Test
...

Rubyで別のファイルを読み込むには require を使います。

requireを使う用途は主に三つあります。

  • 標準添付ライブラリを読み込む

  • 第三者が作成しているライブラリを読み込む

  • 別ファイルに定義した自分のファイルを読み込む

— かんたんRuby

また、require_relative

という方法も用意されています。どう違うのでしょうか?

require_relativeは$LOAD_PATHの参照は行わず「relative」という名称の通り相対的なパスでファイルの読み込みを行います。

— かんたんRuby

ちょっと何言ってるかわからないうちは require を上記のフォルダ構成で使っていてください。一応以下の使い分けがありますが今は頭の隅に留めるだけでいいと思います。

requireは標準添付ライブラリなどの自分が書いていないコードを読み込む時に使い、こちらのrequire_relativeは自分の書いたコードを読み込む時に使うように使い分けるのが良いでしょう。

— かんたんRuby

$ ruby test/fizz_buzz_test.rb
Started with run options --seed 44438

  19/19: [=================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00279s
19 tests, 21 assertions, 0 failures, 0 errors, 0 skips

では最後に main.rb ファイルを追加して FizzBuzz:generate_list を呼び出すようにします。

/main.rb
  |--lib/
      |
       -- fizz_buzz.rb
  |--test/
      |
       -- fizz_buzz_test.rb
require './lib/fizz_buzz.rb'

puts FizzBuzz.generate_list

puts は結果を画面に出力するメソッドです。 先程は p メソッドを使って画面に 配列 の中身を1件ずつ表示していましたが今回は 配列 自体を改行して画面に出力するため puts メソッドを使います。機能的にはほどんど変わらないのですが以下の様に使い分けるそうです。

まず、用途としてはputsメソッドとprintメソッドは一般ユーザ向け、pメソッドは開発者向け、というふうに別かれます。

— プロを目指す人のためのRuby入門

$ ruby main.rb
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
...
Buzz

ちなみに print メソッドを使った場合はこのように出力されます。

$ ruby main.rb
["1", "2", "Fizz", "4", "Buzz", "Fizz", "7", "8", "Fizz", "Buzz", "11", "Fizz", "13", "14", "FizzBuzz", "16", "17", "Fizz", "19", "Buzz", "Fizz", "22", "23", "Fizz", "Buzz", "26", "Fizz", "28", "29", "FizzBuzz", "31", "32", "Fizz", "34", "Buzz", "Fizz", "37", "38", "Fizz", "Buzz", "41", "Fizz", "43", "44", "FizzBuzz", "46", "47", "Fizz", "49", "Buzz", "Fizz", "52", "53", "Fizz", "Buzz", "56", "Fizz", "58", "59", "FizzBuzz", "61", "62", "Fizz", "64", "Buzz", "Fizz", "67", "68", "Fizz", "Buzz", "71", "Fizz", "73", "74", "FizzBuzz", "76", "77", "Fizz", "79", "Buzz", "Fizz", "82", "83", "Fizz", "Buzz", "86", "Fizz", "88", "89", "FizzBuzz", "91", "92", "Fizz", "94", "Buzz", "Fizz", "97", "98", "Fizz", "Buzz"] $

プログラムの完成です。コミットしておきましょう。

$ git commit -m 'feat: FizzBuzz'

TODOリスト

  • 数を文字列にして返す

    • 1を渡したら文字列"1"を返す
    • 2を渡したら文字列"2"を返す
  • 3の倍数のときは数の代わりに「Fizz」と返す

    • 3を渡したら文字列"Fizz"を返す
  • 5 の倍数のときは「Buzz」と返す

    • 5を渡したら文字列"Buzz"を返す
  • 13 と 5 両方の倍数の場合には「FizzBuzz」と返す

    • 15を渡したら文字列FizzBuzzを返す
  • 1 から 100 までのFizzBuzzの配列を返す

    • 配列の初めは文字列の1を返す
    • 配列の最後は文字列の100を返す
    • 配列の2番めは文字列のFizzを返す
    • 配列の4番目は文字列のBuzzを返す
    • 配列の14番目は文字列のFizzBuzzを返す
  • プリントする

ふりかえり

FizzBuzz プログラムの最初のバージョンをリリースすることができたのでこれまでのふりかえりをしておきましょう。

まず TODOリスト を作成して テストファースト で1つずつ小さなステップで開発を進めていきました。 仮実装を経て本実装へ の過程で Rubyの クラス を定義して 文字列リテラル を返す メソッド を作成しました。この時点でRubyの オブジェクトとメソッド という概念に触れています。

Rubyの世界では、ほぼどのような値もオブジェクトという概念で表されます。オブジェクトという表現はかなり範囲の広い表現方法で、クラスやインスタンスを含めてオブジェクトと称します。

— かんたんRuby

プログラミング言語においてメソッド、あるいは関数と呼ばれるものを簡単に説明すると処理をひとかたまりにまとめたものと言って良いでしょう。

— かんたんRuby

ちょっと何言ってるかわからないかもしれませんが、今はそういう概念があってこうやって書くのねという程度の理解で十分です。

その後 リファクタリング を通じて多くの概念に触れることになりました。 まず 変数名の変更 でRubyにおける 変数の概念と操作を通じて名前付けの重要性を学びました。

Rubyでは変数を扱うために特別な宣言やキーワードは必要ありません。「=」 の左辺に任意の変数名を記述するだけで変数宣言となります。

— かんたんRuby

続いて 明白な実装 を通して 制御構造 のうち 条件分岐 のための if式演算子 を使いプログラムを制御し判定・計算をする方法を学びました。また、アルゴリズムの置き換え を適用してコードをよりわかりやすくしました。

Rubyではプログラムを構成する最小の要素を式と呼びます。変数やリテラル、制御構文、演算子などが式として扱われます。

— かんたんRuby

そして、 学習用テスト を通して新しい問題を解決するために 配列オブジェクト レンジオブジェクト といった 文字列リテラル 数値リテラル 以外の データ構造 の使い方を学習して、配列 を操作するための 制御構造 として 繰り返し処理eachメソッド を使って実現しました。

これら「100」や「3.14」といった部分を数値リテラルと呼びます。

— かんたんRuby

このように文字列をシングルクオートやダブルクオートで括っている表記を文字列リテラルと呼びます。

— かんたんRuby

仕上げは、コードの不吉な臭い からさらなる改善を実施しました。 不思議な名前メソッド自動的テストを用意することで自信を持って リファクタリング を実施し、長い関数 に対して ガード節 を導入し 一時変数 説明変数 など 変数 バリエーションの取り扱いを学びました。そして、ループ変更可能なデータ から コレクションパイプライン の使い方と ミュータブル イミュータブル の概念を学び、コメント のやり方と 定数マジックナンバー の問題を学びました。

最後に、require の使い方を通してファイルの分割方法を学ぶことができました。

ちょっと何言ってるかわからない単語ばかり出てきたかもしれませんがこれでRubyの基本の半分は抑えています。自分でFizzBuzzコードが書けて用語の意味が説明できるようになれば技能・学科第一段階の半分ぐらいといったところでしょうか。仮免許取得にはまだ習得しなければならない技術と知識がありますので。

良いコード

以下のコードを作成しました。

/main.rb.

require './lib/fizz_buzz.rb'

puts FizzBuzz.generate_list

/lib/fizz_buzz.rb.

class FizzBuzz
  MAX_NUMBER = 100

  def self.generate(number)
    isFizz = number.modulo(3).zero?
    isBuzz = number.modulo(5).zero?

    return 'FizzBuzz' if isFizz && isBuzz
    return 'Fizz' if isFizz
    return 'Buzz' if isBuzz
    number.to_s
  end

  def self.generate_list
    # 1から最大値までのFizzBuzz配列を1発で作る
    (1..MAX_NUMBER).map { |n| generate(n) }
  end
end

/test/fizz_buzz_test.rb.

require 'minitest/reporters'
Minitest::Reporters.use!
require 'minitest/autorun'
require './lib/fizz_buzz'

class FizzBuzzTest < Minitest::Test
  describe 'FizzBuzz' do
    def setup
      @fizzbuzz = FizzBuzz
    end

    describe '三の倍数の場合' do
      def test_3を渡したら文字列Fizzを返す
        assert_equal 'Fizz', @fizzbuzz.generate(3)
      end
    end

    describe '五の倍数の場合' do
      def test_5を渡したら文字列Buzzを返す
        assert_equal 'Buzz', @fizzbuzz.generate(5)
      end
    end

    describe '三と五の倍数の場合' do
      def test_15を渡したら文字列FizzBuzzを返す
        assert_equal 'FizzBuzz', @fizzbuzz.generate(15)
      end
    end

    describe 'その他の場合' do
      def test_1を渡したら文字列1を返す
        assert_equal '1', @fizzbuzz.generate(1)
      end
    end

    describe '1から100までのFizzBuzzの配列を返す' do
      def setup
        @result = FizzBuzz.generate_list
      end

      def test_配列の初めは文字列の1を返す
        assert_equal '1', @result.first
      end

      def test_配列の最後は文字列のBuzzを返す
        assert_equal 'Buzz', @result.last
      end

      def test_配列の2番目は文字列のFizzを返す
        assert_equal 'Fizz', @result[2]
      end

      def test_配列の4番目は文字列のBuzzを返す
        assert_equal 'Buzz', @result[4]
      end

      def test_配列の14番目は文字列のFizzBuzzを返す
        assert_equal 'FizzBuzz', @result[14]
      end
    end
  end

  describe '配列や繰り返し処理を理解する' do
    def test_繰り返し処理
      $stdout = StringIO.new
      [1, 2, 3].each { |i| p i * i }
      output = $stdout.string

      assert_equal "1\n" + "4\n" + "9\n", output
    end

    def test_特定の条件を満たす要素だけを配列に入れて返す
      result = [1.1, 2, 3.3, 4].select(&:integer?)
      assert_equal [2, 4], result
    end

    def test_特定の条件を満たす要素だけを配列に入れて返す
      result = [1.1, 2, 3.3, 4].find_all(&:integer?)
      assert_equal [2, 4], result
    end

    def test_特定の条件を満たさない要素だけを配列に入れて返す
      result = [1.1, 2, 3.3, 4].reject(&:integer?)
      assert_equal [1.1, 3.3], result
    end

    def test_新しい要素の配列を返す
      result = %w[apple orange pineapple strawberry].map(&:size)
      assert_equal [5, 6, 9, 10], result
    end

    def test_新しい要素の配列を返す
      result = %w[apple orange pineapple strawberry].collect(&:size)
      assert_equal [5, 6, 9, 10], result
    end

    def test_配列の中から条件に一致する要素を取得する
      result = %w[apple orange pineapple strawberry].find(&:size)
      assert_equal 'apple', result
    end

    def test_配列の中から条件に一致する要素を取得する
      result = %w[apple orange pineapple strawberry].detect(&:size)
      assert_equal 'apple', result
    end

    def test_指定した評価式で並び変えた配列を返す
      assert_equal %w[1 10 13 2 3 4], %w[2 4 13 3 1 10].sort
      assert_equal %w[1 2 3 4 10 13],
                   %w[2 4 13 3 1 10].sort { |a, b| a.to_i <=> b.to_i }
      assert_equal %w[13 10 4 3 2 1],
                   %w[2 4 13 3 1 10].sort { |b, a| a.to_i <=> b.to_i }
    end

    def test_配列の中から、条件に一致する要素を取得する
      result = %w[apple orange pineapple strawberry apricot].grep(/^a/)
      assert_equal %w[apple apricot], result
    end

    def test_ブロック内の条件式が真である間までの要素を返す
      result = [1, 2, 3, 4, 5, 6, 7, 8, 9].take_while { |item| item < 6 }
      assert_equal [1, 2, 3, 4, 5], result
    end

    def test_ブロック内の条件式が真である以降の要素を返す
      result = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].drop_while { |item| item < 6 }
      assert_equal [6, 7, 8, 9, 10], result
    end

    def test_畳み込み演算を行う
      result = [1, 2, 3, 4, 5].inject(0) { |total, n| total + n }
      assert_equal 15, result
    end

    def test_畳み込み演算を行う
      result = [1, 2, 3, 4, 5].reduce { |total, n| total + n }
      assert_equal 15, result
    end
  end
end

どうでしょう、学習用テストは除くとしてプロダクトコードに対して倍以上のテストコードを作っていますよね。テストコードを作らず一発で fizz_buzz.rb のようなコードを書くことはできますか? たしかに fizz buzz ruby といったキーワードで検索すればサンプルコードは見つかるのでコピーして同じ振る舞いをするコードをすぐに書くことはできるでしょう。でも仕様が追加された場合はどうしましょう。

仕様

1 から 100 までの数をプリントするプログラムを書け。
ただし 3 の倍数のときは数の代わりに「Fizz」と、5 の倍数のときは「Buzz」とプリントし、
3 と 5 両方の倍数の場合には「FizzBuzz」とプリントすること。
タイプごとに出力を切り替えることができる。
タイプ1は通常、タイプ2は数字のみ、タイプ3は FizzBuzz の場合のみをプリントする。

また同じようなコードサンプルを探しますか?私ならば TODOリスト に以下の項目を追加することから始めます。

TODOリスト

  • タイプ1の場合

    • 数を文字列にして返す

      • 1を渡したら文字列"1"を返す

次に何をやるかはもうわかりますよね。テスト駆動開発とはただ失敗するテストを1つずつ書いて通していくことではありません。

TDDは分析技法であり、設計技法であり、実際には開発のすべてのアクティビティを構造化する技法なのだ。

— テスト駆動開発

ではテストファーストで書けば質の高い良いコードがかけるようになるのでしょうか?以下のコードを見てください。

require 'minitest/reporters'
Minitest::Reporters.use!
require 'minitest/autorun'

class FizzBuzz
  # fizz_buzzメソッドを実行する
  def self.fizz_buzz(n)
  a = n.to_s
    if n % 3 == 0
      a = 'Fizz'
    if n % 15 == 0
      a = 'FizzBuzz'
    end
        elsif n % 5 == 0
          a = 'Buzz'
        end
           a
  end

# 1から100までをプリントする
  def self.print_1_to_100
              n = []
    (1..100).each do |i|
  n << fizz_buzz(i)
                        end
  n
  end
end

class FizzBuzzTest < Minitest::Test
  describe 'FizzBuzz' do
    def setup
      @p = FizzBuzz
    end

      def test_15を渡したら文字列pを返す
        assert_equal 'FizzBuzz', FizzBuzz.fizz_buzz(15)
      end
      def test_3を渡したら文字列3を返す
        assert_equal 'Fizz', FizzBuzz.fizz_buzz(3)
      end
      def test_1を渡したら文字列1を返す
        assert_equal '1', @p.fizz_buzz(1)
      end
      def test_5を渡したら文字列Buzzを返す
        assert_equal 'Buzz', FizzBuzz.fizz_buzz(5)
      end

    describe '1から100までプリントする' do
  def setup
    @x = FizzBuzz.print_1_to_100
  end

  def test_配列の4番目は文字列のをBuzz返す
    assert_equal 'Buzz', @x[4]
  end

      def test_配列の初めは文字列の1を返す
        assert_equal '1', @x.first
      end

      def test_配列の最後は文字列のBuzzを返す
        assert_equal 'Buzz', FizzBuzz.print_1_to_100.last
      end

def test_配列の14番目は文字列のFizzBuzz返す
  assert_equal 'FizzBuzz', @x[14]
end
  def test_配列の2番目は文字列の2を返す
    assert_equal 'Fizz', @x[2]
  end

    end
  end
end
$ ruby test/fizz_buzz_tfd_test.rb
Started with run options --seed 43131

  9/9: [===================================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.00135s
9 tests, 9 assertions, 0 failures, 0 errors, 0 skips

プログラムは動くしテストも通ります。でもこれはテスト駆動開発で作られたと言えるでしょうか?質の高い良いコードでしょうか?何が足りないかはわかりますよね。

テスト駆動開発における質の向上の手段は、リファクタリングによる継続的でインクリメンタルな設計であり、「単なるテストファースト」と「テスト駆動開発」の違いはそこにあります。

— テスト駆動開発 付録C 訳者解説

そもそも良いコードは何なのでしょうか?いくつかの見解があるようです。

TDDは「より良いコードを書けば、よりうまくいく」という素朴で奇妙な仮設によって成り立っている

— テスト駆動開発

「動作するきれいなコード」。RonJeffriesのこの簡潔な言葉が、テスト駆動開発(TDD)のゴールだ。動作するきれいなコードはあらゆる意味で価値がある。

— テスト駆動開発

良いコードかどうかは、変更がどれだけ容易なのかで決まる。

— リファクタリング(第2版)

コードは理解しやすくなければいけない。

— リーダブルコード

コードは他の人が最短時間で理解できるように書かなければいけない。

— リーダブルコード

優れたソースコードは「目に優しい」ものでなければいけない。

— リーダブルコード

少なくともテスト駆動開発のゴールに良いコードがあるということはいえるでしょう。え?どうやったら良いコードを書けるようになるかって?私が教えてほしいのですがただ言えることは他の分野と同様に規律の習得と絶え間ない練習と実践の積み重ねのむこうにあるのだろうということだけです。

私がかつて発見した、そして多くの人に気づいてもらいたい効果とは、反復可能な振る舞いを規則にまで還元することで、規則の適用は機会的に反復可能になるということだ。

— テスト駆動開発

ここで、Kent Beckが自ら語ったセリフを思い出しました。「僕は、偉大なプログラマなんかじゃない。偉大な習慣を身につけた少しましなプログラマなんだ」。

— リファクタリング(第2版)

参照

参考サイト

参考図書

  • [1] テスト駆動開発 Kent Beck (著), 和田 卓人 (翻訳): オーム社; 新訳版 (2017/10/14)

  • [2] 新装版 リファクタリング―既存のコードを安全に改善する― (OBJECT TECHNOLOGY SERIES) Martin
    Fowler (著), 児玉 公信 (翻訳), 友野 晶夫 (翻訳), 平澤 章 (翻訳), その他: オーム社; 新装版
    (2014/7/26)

  • [3] リファクタリング(第2版): 既存のコードを安全に改善する (OBJECT TECHNOLOGY SERIES) Martin
    Fowler (著), 児玉 公信 (翻訳), 友野 晶夫 (翻訳), 平澤 章 (翻訳), その他: オーム社; 第2版
    (2019/12/1)

  • [4] リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)
    Dustin Boswell (著), Trevor Foucher (著), 須藤 功平 (解説), 角 征典 (翻訳):
    オライリージャパン; 初版八刷版 (2012/6/23)

  • [5] かんたん Ruby (プログラミングの教科書) すがわらまさのり (著) 技術評論社 (2018/6/21)

  • [6] プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで (Software Design
    plusシリーズ) 伊藤 淳一 (著): 技術評論社 (2017/11/25)

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

RubyからQualityForward APIを利用する

QualityForwardはクラウドベースのテストケース管理システムです。

Rubyは日本製、さらにRuby on Railsでの採用例も多いということもあって、多くの開発者に利用されているプログラミング言語です。サーバのみならず、ローカルコンピュータ上でCUIで使っている人も多いでしょう。

そんな中でテスト管理のデータを役立てるべく、QualityForwardのRubyライブラリを開発しはじめました。現在はプロジェクトの取得のみですが、今後バージョンアップを重ねていきます。

インストール

コードはgoofmint/qualityforward-rbにあります。Rubygemsからインストール可能です。

gem 'qualityforward'

使い方

以下のように使います。まず初期化します。

Qualityforward::Client.new 'YOUR_API_KEY'

プロジェクトを取得します。

p = Qualityforward::Project.get
puts p.id # 999

まとめ

Rubyスクリプトを使ってテスト結果のデータを取得したり、逆にプログラミングした内容でテストケースを自動作成するといった使い方も考えられます。今後のバージョンアップをお待ちください!

QualityForward

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

[rails] 投稿一覧にユーザー情報を表示

始めに

今回、プロゲートと違う方法で投稿一覧でユーザー情報を表示します。

プロゲートでrailsを終えてからよく、ツイッター風アプリを作成すると思います。
そこで、プロゲートのコードをコピペすることは誰でも出来ますが、
重要なのは理解して自分でコードを書くことだと思います。

他のやり方で表示することが出来たので、参考やヒントになれば嬉しいです。

完成イメージ

スクリーンショット 2020-01-21 17.00.44.png
※注:デザインは今回しません。また、分かりやすく理解してもらうためにユーザー情報は名前だけにします。

前提

・usersテーブルと投稿用のテーブル(今回はcommentsテーブル)があること。

MVC(model/controller/view)の設定

controller設定

app/controllers/comments_controller.rb
class CommentsController < ApplicationController
    def index
      @comment = Comment.all.order(created_at: :desc)
    end

これはプロゲートでもでてきた投稿された順に上から表示しています。

modelの設定(※ここからプロゲートと違う!)

app/models/comment.rb
class Comment < ApplicationRecord
    belongs_to :user#追記
end

belongs_toとはなんぞや?って思った方いるかも知れません。
簡単に説明すると、userテーブルと投稿用のテーブル(今回はcommentsテーブル)の2つを紐付けてくれる仕組みのものです。
詳細はこちら

view設定

app/views/comments/index.html.erb
<% @comment.each do |comment|%>
  <%= comment.user.name%>
  <%= link_to(comment.content,"/comments/#{comment.id}")%>
<% end %>

上記のコードの2行目の <%= comment.user.name%>は
先程belongs_to :userと紐付けているためこのように書くことが出来ます。
もし、紐付けが出来ていなかったらエラーが出ると思います。

最後に

今回、belongs_to を使ってuserテーブルとcommentテーブルをアソシエーションしました。

もし、何か修正点とかございましたらコメント等
恐縮ですが、宜しくおねがいします

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

【Rails】enumを使用したセレクトボックスの実装とDBへの保存

はじめに

都道府県などのプルダウンメニューを作成する際、enumを使いました
これを使うことで都道府県などのデータをわざわざテーブルとして用意する必要がなくなります
便利だったのですが、DBへの保存の際に型が合わないなどの問題も発生したのでまとめておきます

環境

Ruby: 2.5.1
Rails: 5.2.4

実際の操作

実際の操作は以下のようになります

  1. 該当カラムをinteger型で作成
  2. モデルにプルダウンメニューで表示させるもの一覧を記載
  3. ビューで表示させるプルダウンの記述
  4. 入力したフォームの受け取り型を編集

1. 該当カラムをinteger型で作成

下記の場合は都道府県です

schema.rb
t.integer :prefecture, null: false


2. モデルにプルダウンメニューで表示させるもの一覧を記載

models/address.rb
enum prefecture: {
    北海道:1,青森県:2,岩手県:3,宮城県:4,秋田県:5,山形県:6,福島県:7,
    茨城県:8,栃木県:9,群馬県:10,埼玉県:11,千葉県:12,東京都:13,神奈川県:14,
    新潟県:15,富山県:16,石川県:17,福井県:18,山梨県:19,長野県:20,
    岐阜県:21,静岡県:22,愛知県:23,三重県:24,
    滋賀県:25,京都府:26,大阪府:27,兵庫県:28,奈良県:29,和歌山県:30,
    鳥取県:31,島根県:32,岡山県:33,広島県:34,山口県:35,
    徳島県:36,香川県:37,愛媛県:38,高知県:39,
    福岡県:40,佐賀県:41,長崎県:42,熊本県:43,大分県:44,宮崎県:45,鹿児島県:46,沖縄県:47
  }



これで表示させる準備完了

3. ビューで表示させるプルダウンの記述

address.html.haml
= form.select :prefecture, Address.prefectures.keys, {prompt: "選択してください"}, class: "prefecture-select"



これでプルダウンメニューとして表示することはできるようになったんですが、binding.pryでparamsの中を見てみると"北海道"として値を取得してきてしまいました。
スクリーンショット 2020-01-20 21.34.13.png

prefectureカラムはinteger型なのでこのままだとDBに登録できなくて困ります。

4. 入力したフォームの受け取り型を編集

色々調べた結果、以下のようにすると対応するvalueの値を取得できることがわかりました。

address.html.haml
= form.select :prefecture, options_for_select(Address.prefectures), {prompt: "選択してください"}, class: "prefecture-select"

スクリーンショット 2020-01-20 21.32.58.png

しかし、これだと"1"という文字列なのでまだカラムに登録できません。

form.selectにおける入力値を数値として送れないかと思いましたが、やり方がわからなかったので、受け取る側で無理矢理数値型に変換することにしました。少々かっこ悪いですが、ストロングパラメータで数値型にした後にmergeしてます。

registrations_controller.rb
def address_params
  params.required(:address).permit(:postal_code, :city, :street, :building).merge(prefecture: params[:address][:prefecture].to_i)
end



これで晴れてDBへ保存できるようになりました

ただもっとスマートな方法がありそうな気がしてます

何か知ってる方いらっしゃいましたら教えていただけると嬉しいです

似たようなものにactive_hashというものがあります
gemをインストールする必要がありますが、こちらは数値型としてデータを取得できますし、ActiveRecordのメソッドも使えるみたいなので便利そうです

参考記事

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

Nokogiriでタグの切り出しがうまくいかないとき名前空間の存在を忘れていませんか?

HTMLやXMLのタグをスクレイビングするときに重宝するNokogiriライブラリは、かなり機能が充実していますが、精通するまで使用するのは難しいです。

ここでは、ライブラリ使用時にハマってしまったことを備忘録としてまとめました。
簡単な使い方が載っているサイトはすぐにたくさん見つかりましたが、後述するハマりポイントが記載されているサイトはなかなか見つかりませんでした。

Nokogiriの詳しい使い方は既に多くの人が分かりやすく説明してくれていますので、そちらを参照ください。

一応簡単におさらい

sample.rb
require 'nokogiri'

#①XMLファイルを読み込む
  file = Nokogiri::XML(File.read('sample.xml'))

#②XMLクラスを作成して、変数に格納する
  a = Nokogiri::XML::Builder.new(encoding: 'utf-8') do |xml|
    #要素の作成
  end

②の「要素の作成」については、以下のリンク先を参照すれば詳しく載っています。
https://www.rubydoc.info/gems/nokogiri/Nokogiri/XML/Builder

はまりポイント

以下のXMLをサンプルに説明します。

morning.xml
<head>
  <title>モーニング娘。</title>
  <team id='4期'>
     <status>石川</status>
     <status>吉澤</status>
     <status>加護</status>    
     <status></status>
  </team>
</head>
morning.rb
file = Nokogiri.XML(File.read(morning.xml))

タグの中身だけが欲しいんだ

morning.rb
file = Nokogiri.XML(File.read(morning.xml))
title = file.xpath('/head/title')
puts title
# > <title>モーニング娘。</title>

とすれば、titleタグが取得できますが、このままでは「

モーニング娘。」と出力されてしまいます。中身の「モーニング娘。」だけが欲しい時は「.text.strip」を追加してあげましょう。
morning.rb
file = Nokogiri.XML(File.read('morning.xml'))
title = file.xpath('/head/title').text.strip
puts title
# > モーニング娘。

名前空間の存在を忘れていたら要素を取り出せない

Nokogiriで要素を切り出すためにxpathを使えばいいのですが、ここで

a = file.xpath('/head/title')

とすれば、titleタグを切り出せますが、

a = file.xpath('/head/team')

とすると、うまくいきません。

もし名前空間を無視して使用したい場合は

file = Nokogiri::XML(File.read('morning.xml'))
file.remove_namespaces!

と、名前空間を削除しましょう。すると、

a = file.xpath('/head/team')

が動いて、teamタグが取得できます。

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

Rails 6+Grapeで作るAPIサーバーにDeviseトークン認証を付ける

はじめに

iOS・AndroidアプリやWebアプリをクライアントとしたAPIサーバーをRuby on Railsで実装するケースがあります。この記事では、Grape(REST APIフレームワーク)を利用して作るAPIサーバーにDevise(認証機能を提供するgem)を組み合わせ、アクセストークンを介した認証方式を実装する手順を紹介します。

同様の紹介事例について

Devise + Grape で認証付きAPIの実装 - Qiita

上記の記事では本記事と同様に、GrapeとDeviseに加えてトークン認証機能を付加するgemであるdevise_token_authを用いて実装する手法が紹介されています。ただし、

Token の更新に関してですが、今回の例では一度発行した Token は再度ログインするまで更新されません。

とあるようにdevise_token_authの本来の機能であるリクエストごとにアクセストークンを更新する仕様は有効になっていません。ここでは、上記記事のコードをベースに、Grapeとdevise_token_authの仲立ちをするgrape_devise_token_authを加えることでその点をクリアし、現行のRails 6で動作するサンプルを作ります。

前提条件

以降の手順解説では、Ruby 2.6.5とRails 6.0.2.1を使用することを想定しています。

Railsアプリを作成する

# rails new token-auth-sample

Railsアプリを新規作成して、以下のGemを追加します。

Gemfile.rb
gem 'devise'
gem 'devise_token_auth'
gem 'grape'
gem 'grape_devise_token_auth'
$ bundle install

Devise関連の環境設定を行う

# rails g devise:install
# rails g devise_token_auth:install User auth

新たに作成されたUserモデルのマイグレーションを行います。

# rails db:migrate

基本部分のコード修正

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  include DeviseTokenAuth::Concerns::SetUserByToken

  protect_from_forgery with: :null_session
end

protect_from_forgeryは一般的なRailsアプリにおけるCSRF対策の仕組みに関する設定で、指定しない場合はPOSTリクエストを受け付けた際にActionController::InvalidAuthenticityTokenエラーとなります。これを無効化するには protect_from_forgery with: :null_session とします。

app/models/user.rb
class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  include DeviseTokenAuth::Concerns::User
end

Userモデルでは、devise メソッドで有効化する機能が列挙されていますが、 :trackable が含まれていたら外しておきましょう。前述の rails g devise_token_auth:install User auth コマンドで作成されたマイグレーションでtrackable用のカラムが作成されておらず、サインインリクエストの際に NoMethodError (undefined method `current_sign_in_at' 〜 が発生するためです。(末尾の参考情報を参照)

config/routes.rb
Rails.application.routes.draw do
  devise_for :users

  namespace :api do
    mount_devise_token_auth_for 'User', at: '/v1/auth'

    mount Api::Root => '/'
  end
end

APIエンドポイント部分のコード追加

app/api/api.rb
module Api
  class Root < Grape::API
    GrapeDeviseTokenAuth.setup! do |config|
      config.authenticate_all = true
    end

    mount V1::Root
  end
end
api/v1/root.rb
module V1
  class Root < Grape::API
    version 'v1', using: :path
    format :json

    auth :grape_devise_token_auth, resource_class: :user

    helpers GrapeDeviseTokenAuth::AuthHelpers

    desc 'GET /api/v1/test'
    get 'test' do
      authenticate_user!

      {
        message: 'test',
        current_user_uid: current_user.uid,
        authenticated?: authenticated?,
      }
    end
  end
end

ここまでで、以下のエンドポイントが利用可能になっています。

  • ユーザーアカウント作成
    • POST http://localhost:3000/api/v1/auth
  • サインイン
    • POST http://localhost:3000/api/v1/auth/sign_in
  • サンプルGETリクエスト
    • GET http://localhost:3000/api/v1/test

動作確認

ローカルサーバーを起動しておきます。

# rails s
=> Booting Puma
=> Rails 6.0.2.1 application starting in development
=> Run `rails server --help` for more startup options
Puma starting in single mode...
* Version 4.3.1 (ruby 2.6.5-p114), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://127.0.0.1:3000
* Listening on tcp://[::1]:3000
Use Ctrl-C to stop

サインアップ(ユーザーアカウント作成)

まず、新しいユーザーを作成します。

0584E428-FCF5-4FE1-BBE2-AE3C2770069C.png

cURLでのリクエスト例
# curl --request POST \
  --url http://localhost:3000/api/v1/auth \
  --header 'content-type: application/x-www-form-urlencoded' \
  --data email=user01@example.com \
  --data password=user01 \
  --data password_confirmation=user01

サインアップリクエストのレスポンスヘッダーには、このユーザーの認証情報が含まれています。次のリクエストの際には認証情報として access-token, client, uid の値を指定します。

GETリクエスト

次に、作成されたユーザーでGETリクエストを行います。認証情報を収めた3つのリクエストヘッダーを付加します。

5B2BA75C-6C45-4C61-B669-364146A169CC.png

cURLでのリクエスト例
# curl --request GET \
  --url http://localhost:3000/api/v1/test \
  --header 'access-token: b638qOBLJ_sIEEJEiMC1ug' \
  --header 'client: 1XcTtaq2uA3DK0qvY6qM9Q' \
  --header 'uid: user01@example.com'

レスポンスヘッダーには access-token, client, uid が含まれており、 access-token の値が更新されています。 access-token はリクエストを行うたびに新しい値と置き換わることから、毎回異なるアクセストークンを用いてアクセスする仕様で動作していることが分かります。

おわりに

RailsとGrapeを使ったAPIプロジェクトで、Deviseの認証機能を用いてトークン認証を行う手順を紹介しました。devise_token_authのトークン更新機能が有効になっており、トークンの有効期間を絞れるという点で安全上望ましい仕様になっています。一方APIクライアントの方では、トークンの値が変更されるのに応じて書き換わったトークンを適切に更新管理することが大切になります。

参考情報

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

HerokuでRailsタスクを定期実行する方法(Google Apps Scriptのトリガーを使う方法)

HerokuにデプロイしたRailsアプリでタスクを定期実行する方法を紹介します。
ここで示す方法は、Google Apps Script (GAS)を使う方法です。

結論を先に述べると以下のようにします:
1. Railsタスクを作成しておく
2. GASでHerokuのAPI経由でコマンドをpostする関数を書く
3. GASでスケジューラーを作成する(トリガーを作る)

例として、Railsの場合を示していますが、GAS経由で「herokuのコマンドを定期実行する」という点がポイントなので、他の用途にも使えます。言語がRubyである必要性もありません。

この記事では、Railsの画像投稿アプリを例にとります。
スケジューラーを使って、24時間ごとにデータベースのテーブルのレコードを全て削除し、初期データの画像を投稿する方法を示します。

対象読者:プログラミング初心者向け

  • 私自身は転職活動中の未経験エンジニアです。内容には正確性を期しますが、間違いがあればご指摘くださると幸いです。
  • 初学者がHerokuを使っていて、「Herkouでデータベースを定期的にリセットしたいが調べても方法が分からない」と疑問を持った際に、当記事の内容を少しでも活用してもらえればと思って書きました。

経緯 (経緯に興味のない人は飛ばしてください)

HerokuにRailsアプリをデプロイしていて、一定時間ごとにデータベースを初期化するために今回の方法に行き着きました。

前提として、

このような理由で、画像投稿機能のあるwebアプリをHerokuにデプロイした場合、画像の保存場所をAWS S3などに指定することはよくあると思います。

しかし、私のようにポートフォリオとしてアプリを公開したいだけなら、24時間ごとに投稿画像が削除されても問題ないと考える人もいるでしょう。
一方、アプリの体裁を整えるために、幾つかの画像だけはアプリの再起動の度に投稿された状態にしたいこともあるでしょう。

今回の私がこのような状況でした。
これを解決するために以下のようにしました。

  • Railsのタスクを作成する。このタスクの実行でテーブルを空にしてから適当な画像を投稿する(このための数枚の画像は、git管理する)
  • Heroku APIを使い、httpリクエストでRailsタスクを実行する
  • GASでhttpリクエストを定期実行する

これで何とか解決したので、そのぞれの方法を説明します。

前提となるサンプリアプリの作成

画像投稿機能

例として、carrierwaveで画像を投稿するサンプルアプリを以下のように作ります。

rails new sample-app
rails db:create
rails generate scaffold picture image:string
rails db:migrate

画像投稿用のgemとしてcarrierwaveをインストールします。
gemfileに追記して

gem 'carrierwave'

としてからbundle installとします。
carrierwaveの設定のインストールのため、rails g uploader imageとし、carrierwaveのuploaderをモデルにマウントします。

app/models/picture.rb
class Picture < ApplicationRecord
  mount_uploader :image, ImageUploader
end

ファイルをアップロードできるようにerbファイルに以下を追記します。

app/views/pictures/_form.html.erb
<%= form.text_field :image %>

続いて、投稿した画像を表示できるように以下を追記します。

app/views/pictures/index.html.erb
<td><%= image_tag picture.image.to_s %></td>

最低限の内容ですが、これで画像投稿アプリができました。

画像投稿するためのRailsタスクを作成

rails g task initialize_dbとして、タスクのファイルを以下のように編集します。

lib/tasks/initialize_db.rake
namespace :initialize_db do
  desc "初期データとしてpicturesテーブルにデータを入れる"
  task add_initial_blog: :environment do
    Picture.destroy_all
    Picture.create!([
      {image: File.open("#{Rails.root}/public/samples/0apple.jpeg")},
      {image: File.open("#{Rails.root}/public/samples/0cake.jpeg")}
    ])
  end
end

初期データとして投稿するための画像データは、0apple.jpegと0cake.jpegです。
これを/public/samples/フォルダに配置します。
これらをgit管理することで、herokuの再起動時に削除されずに済みます。

これで、ターミナルでrails initialize_db:add_initial_blogとすれば、picturesテーブルのレコードを全て削除して、新規に2つの画像を投稿することができます。

ひとまず、ここまでの内容をHerokuにデプロイ済みだとします。
このタスクをスケジューラーで定期的に実行するのが今回の目的となります。

HerokuのコマンドをAPI経由で実行する

Herokuにアプリをデプロイする際や、ログを確認する際にはHeroku CLIを使って、コマンドを実行するのが一般的です。

しかし、コマンドの実行を自動化するにはAPIを使う方が(おそらく)便利です。
Heroku APIについては公式のリファレンスにまとまっています。

APIでone-off Dynoを作り、コマンドを実行

今回は、API経由でコマンドを実行する方法に着目します。
公式によれば以下のようにします: Platform API Reference | Heroku Dev Center

POST /apps/{app_id_or_name}/dynos

今回はherokuのCLIでheroku run rails initialize_db:add_initial_blogと打つのをAPIで代替したいので、curlで以下のようにします。

$ curl -n -X POST https://api.heroku.com/apps/$APP_ID_OR_NAME/dynos \
  -d '{
  "command": "heroku run rails initialize_db:add_initial_blog",
}' \
  -H "Content-Type: application/json" \
  -H "Accept: application/vnd.heroku+json; version=3"
  -H "Authorization: Bearer $HEROKU_API_KEY"

$APP_ID_OR_NAME$HEROKU_API_KEYについては個々のアプリに応じて読み替えてください。

Herokuの$APP_ID_OR_NAME$HEROKU_API_KEYの確認方法

$APP_ID_OR_NAMEを確認するにはCLIでheroku appsと打ちます。
もしくは、アプリの公開URLがhttps://<$APP_ID_OR_NAME>.herokuapp.com/のような形式になっているので、そこから読み取ることもできます。

$HEROKU_API_KEYを確認するには、heroku auth:tokenとCLIで打ちます。このトークンは1年しか有効ではないので、半永久的に使いたい場合には、heroku authorizations:createとしてトークンを生成してください:Getting Started with the Platform API | Heroku Dev Center

GASでスケジューラーを作成

他のスケジューラーではダメなのか

herokuで特定の処理を一定時間ごとに実行する代表的な方法はHeroku Schedulerというアドオンを使うものです。

しかし、Heroku Schedulerの使用にはクレジットカードの登録が必要であり、無料Dynoの枠を超えると課金される可能性もあるので、完全に無料の方法として、GASを使うことにしました。

他の方法としてClock ProcessesというHerokuの機能を活用する手もあります: Scheduled Jobs and Custom Clock Processes | Heroku Dev Center。Clock Processesはクレジットカード登録なしで使用できますが、追加のdynoを使うため、今回は見送りました。

GASなら無料でスクリプトの定期実行ができる

Googleドライブで使用できるGoogle Apps Scriptは、無料でスクリプトを定期実行できます。
実際には実行回数に制限がありますが、無料プランでも十分な回数です:Quotas for Google Services  |  Apps Script  |  Google Developers
例えば、今回利用するURL Fetch callsでは一日あたり20000回使用できます。

GASの環境設定

GASの実行環境を整えます。googleのアカウントを持っていれば、1分で完了します。
googleドライブで「Google Apps Script」というアプリを選択するだけです。

Google Apps Script

初回はメニューに登録されていないので、「アプリを追加」からGoogle Apps Scriptを検索して、アプリを追加してください。

GASで定期実行するスクリプト

GASは、一部独自機能があるものの、文法はJavaScriptに準拠しています。

heroku APIをcurlで使用するためのコマンドをGASのコードに読み替えます。
結果だけ示すと、以下のようにrunHerokuTask関数を定義し、この関数を定期実行すればうまくいきます。

function runHerokuTask() {
  runHerokuCommand("heroku run rails initialize_db:add_initial_blog");
}

function runHerokuCommand(command) {
  var urlTemplate = "https://api.heroku.com/apps/%s/dynos";
  var herokuToken = PropertiesService.getScriptProperties().getProperty("heroku_token");
  var herokuAppName = PropertiesService.getScriptProperties().getProperty("heroku_app_name"); 
  var herokuAuth = Utilities.formatString("Bearer %s", herokuToken);
  var url = Utilities.formatString(urlTemplate, herokuAppName);

  var headers = {
    "Content-type": "application/json",
    "Accept": "application/vnd.heroku+json; version=3",
    "Authorization": herokuAuth
  }

  var data = {
    "command": command
  }

  var options = {
    "method": "post",
    "payload": JSON.stringify(data),
    "headers": headers
  };

  var response = UrlFetchApp.fetch(url, options);
  var text = response.getContentText();
  Logger.log(text);
}

このコードでのポイントを幾つか説明します。

  • トークンやアプリ名などは、プロパティから取り出す
  • UrlFetchApp.fetchでhttpリクエストをする

GASのコードをgithubで公開する場合、パスワードや環境依存の定数はコードに含めたくはありません。
そのため、GASの機能のプロパティサービスを活用します。

プロジェクトのプロパティというところからスクリプトのプロパティで「キーと値のペア」を登録します。
プロジェクトのプロパティ

PropertiesService.getScriptProperties().getProperty("キーの名前")として、キーに紐付いた値を取り出します。

またhttpリクエストには、UrlFetchAppを使います。
今回は、POSTメソッドなので、オプションが必要ですが、getメソッドであれば、UrlFetchApp.fetch("http://www.google.com/")とするだけでwebサイトにアクセスできます。

GASのスクリプトの定期実行

GASのスクリプトの定期実行

GASのスクリプトの定期実行には、トリガーを登録します。

トリガーを登録

これで、毎日、0~1時ごろに登録した関数が自動実行されます。

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

dotenv のサンプルメモ

bkeepers/dotenv: A Ruby gem to load environment variables from .env. https://github.com/bkeepers/dotenv

サンプル

  • 存在しないキーを指定すると nil となる
  • 実際の環境変数があったらそちらが優先される
.env
TEST="hogehoge"
require "dotenv"
require "pp"

Dotenv.load

pp ENV["TEST"] # => "hogehoge"
pp ENV["TEST2"] # => nil
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

配列の値をハッシュのキーとバリューに変換する方法

配列の値をハッシュのキーとバリューに変換する方法

実務で書かれていたコードでなるほど!というRubyのコードがあったので備忘録としてメモします。

配列のこういう値があったとします。

 ["A", "B", "C", "D", "E", "F", "G"]

これをこんな感じでhashのkey_valueに変換したいときにどう実装すればよいでしょうか?

 {"A"=>"A", "B"=>"B", "C"=>"C", "D"=>"D", "E"=>"E", "F"=>"F", "G"=>"G"}

STEP1 配列作成

とりあえず、配列作ります。

hoge = ["A", "B", "C", "D", "E", "F", "G"]

STEP2 配列のzipメソッド利用してハッシュ化する前に整形する。

配列のzipメソッド利用すると自身と引数に渡した配列の各要素からなる配列の配列を生成して返します。
https://docs.ruby-lang.org/ja/latest/method/Array/i/zip.html

irb(main):024:0> hogehoge = hoge.zip(hoge)
=> [["A", "A"], ["B", "B"], ["C", "C"], ["D", "D"], ["E", "E"], ["F", "F"], ["G", "G"]]

STEP2 ハッシュ化する

to_hメソッドでハッシュに変更します。

irb(main):026:0> hogehoge.to_h
=> {"A"=>"A", "B"=>"B", "C"=>"C", "D"=>"D", "E"=>"E", "F"=>"F", "G"=>"G"}

完成!!

まとめ

Rubyは様々なメソッドがあって便利ですよね!

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

RailsのDB操作を行うコマンドまとめ

はじめに

DB操作を行うコマンドというのはrails db:migrateなどのことです。
コマンドは数が多く、名前から想像しにくい(自分だけ?)ものもあったりするため、備忘録としてまとめておこうかと思います。

最初はスカスカですが、少しずつ充実させていく予定です。
※見栄えも少しずつ整えていきます。。。

■参考サイト(公式GitHub)
https://github.com/rails/rails/blob/v5.2.0/activerecord/lib/active_record/railties/databases.rake

環境

  • Rails 5.2.0

コマンド

コマンド 説明 備考
rails db:create データベースを作成
rails db:drop データベースを削除
rails db:migrate マイグレートを実行
rails db:migrate:reset データベースを作成し直した後にマイグレートを実行
rails db:reset DB再作成後、マイグレートを実行し、seedファイルからデータ作成 db:drop後にdb:setupを行っている。
rails db:setup DB作成後、マイグレートを実行し、seedファイルからデータ作成 マイグレーションファイルは使用せず、schema.rbからマイグレートを行う
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails devise + google_oauth2 でログイン後のリダイレクト先を指定

applictaion_controller.rbではリダイレクト処理したくない

理由は特にない。気分の問題。

omniauth_callbacks_controller.rbに書く

app/controllers/chichinpuipui/omniauth_callbacks_controller.rb
  def google_oauth2
    ...
    ...
  end

  protected

  #ログイン後のリダイレクト先
  def after_sign_in_path_for(resource)
    itaino_itaino_tondeke_path
  end

google_oauth2メソッドより上にprotected書くとエラーになるので注意。

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

WebMockを使って外部API接続をスタブ化する

概要

APIはJavaで実装して、Javaから返ってきたjsonをRailsで受け取ってRails側でそのjsonデータを表示するという流れにしたく、Railsの実装をしている時のテストや、いちいちJavaを起動させなくていいようにスタブ化したのが目的です。

Rails開発でWebMockを使ってAPIアクセスをスタブ化するからのコードを引っ張ってきただけなので、リンク先の解説みた方が断然わかりやすいです。

実際のコード

api_stub.rb
module ApiStub
  require 'webmock'

  WebMock.enable!
  # アプリケーションコードに`call_get_api`という名称の外部API呼び出し実装を含むメソッドがあることして、ここで同名のメソッドを作成してstub登録します。
  def call_get_api
    WebMock.stub_request(:any, "http://www.example.com/").to_return(
        body: File.read("#{Rails.root}/test/fixtures/stub_api_response.json"),
        status: 200,
        headers: { 'Content-Type' =>  'application/json' })
    super # アプリケーションコードの実装を呼び出す
  end
end
api_module.rb
module ApiModule
  require_relative 'api_stub'
  prepend ApiStub if ENV["APIMODE"] == "mock" && Rails.env.development?

  def call_get_api
    # ここに本来の外部API呼び出しの実装が入る
  end
end
api_client.rb
class ApiClient
  require_relative 'api_module'
  include ApiModule
end
web_controller.rb
class WebController < ApplicationController

  require './lib/externals/api_client'
  require 'net/http'

  def fetch
    client = ApiClient.new
    client.call_get_api
    res = Net::HTTP.get("www.example.com", "/")
    @result = JSON.parse(res)
  end
end

脱線 スタブとモックの違い

スタブは受信メッセージのテストのために使うためのもの。
モックは送信メッセージのテストのために使うもの。

参照

Rails開発でWebMockを使ってAPIアクセスをスタブ化する
Ruby on RailsでWebMockを利用する

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

Rails 6ではsend_data/send_fileメソッド呼び出し時にERB::Util.url_encodeは不要です

Rails 6にアップグレードすると発生する問題

Railsでファイルダウンロード機能を実装するときはsend_dataメソッドやsend_fileメソッドがよく使われます。
このとき、ファイル名に日本語(正確には非ascii文字)が含まれていると、IEやEdgeで文字化けします。
そのため、Rails 5.2以前では以下のようにERB::Util.url_encodeメソッドを使って文字化けを防いでいました。

class FooController < ApplicationController
  # ...

  def download
    # ...

    # Rails 5.2以前ではurl_encodeをかけないと、IEやEdgeでダウンロードしたときに
    # `縺ォ縺サ繧薙#.txt`のようなファイル名になってしまっていた
    send_data(your_data, filename: ERB::Util.url_encode('にほんご.txt'), disposition: 'attachment')
  end
end

しかし、RailsアプリケーションをこのままRails 6にアップグレードすると、再びファイル名が文字化けします。
(IEやEdgeのみならず、Chrome等でも文字化けします)

# Rails 5.2でダウンロードしたときのファイル名
にほんご.txt

# Rails 6.0でダウンロードしたときのファイル名
%E3%81%AB%E3%81%BB%E3%82%93%E3%81%94.txt

原因

これはRails 6でファイル名がデフォルトでエンコードされるようになったのが原因です。

解決策

Rails 6ではデフォルトでエンコードされるので、ERB::Util.url_encodeを挟まずにsend_dataメソッドやsend_fileメソッドを呼び出してください。

-send_data(your_data, filename: ERB::Util.url_encode('にほんご.txt'), disposition: 'attachment')
+send_data(your_data, filename: 'にほんご.txt', disposition: 'attachment')

こうすれば、どのブラウザでも文字化けせずに日本語のファイル名でダウンロードすることができます。

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

カラムを追加してpgからschemaにdumpさせてみました

記録用

railsでgenerate migrationコマンドを使って、自動的に作成していましたが、その方法で作成しない方針だったため、migrationファイルからDBにカラムを追加したり、コメントを入れたり、schemaディレクトリにバックアップファイルを作成してその中にdumpさせました。

完全に理解していないですが、記録用と手順ややったことを忘れないためにも残しておきます。

migretionファイルでカラムの追加

カラムの追加

ALTER TABLE テーブル名 ADD COLUMN カラム名 データ型 型名  ;
COMMENT ON TABLE テーブル名 IS 'コメント';

SQLで確認

\d (テーブル名)
\d+ でコメントも確認することができる

列 | 型 | 修飾語

↑のような形式で追加したカラムを確認することができる

schemaファイルにdumpするとき

データベースを指定してダンプする場合は、pg_dumpを使用する

pg_dump DB名 --schema-only -U username -h host -p port -t  'table' > file(バックアップファイル).sql

dumpのオプション内容

-- schema-only
データ定義(スキーマ)のみをダンプし、データはダンプしません。

-U username
指定したユーザとして接続します。

-h
000.000.000.000

-p
ポート番号

-t table
tableに一致するテーブル(またはビューやシーケンス)のみをダンプします。

table > file.sql
dump先を指定

このようにオプションで絞っていきました。

schemaファイルにdumpできていることを確認することができました。
次回からこの手順で作成していきます。

参考文献

https://www.postgresql.jp/document/9.4/html/ddl-alter.html

https://www.postgresql.jp/document/8.2/html/app-pgdump.html

https://www.postgresql.jp/document/11/html/app-pgdump.html

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

【Rails】 DataTables 動的にカラムを変更する方法

はじめに

Railsアプリケーションで DataTables を使う方法を以前にまとめさせていただきました。
DataTables を使ったテーブルのカラムを動的に変更したい需要があると思いますが、まとめられている記事を見かけませんでしたので、こちらにてまとめさせていただきます。
この方法を理解していれば、開発時間を極端に減らして高機能なテーブルを提供することができます。

なお、今回の方法は少し強引にカラムを動的に変更しています。
もし、もっといい方法があるということをご存知の方はコメントをいただければ嬉しいです。

動的にカラムを変更する方法

前提条件

下記リンクにてDataTablesを実装していること。
このリンク先のコードを元にして、動的にカラムを変更する方法をこちらの記事にて特記したいと思います。

値を変更する方法

動的にカラムを変更するには、users.coffeeのコードを変更することによって可能です。

ここでは一例として、id が 3 のときに"あほ"を表示するコードを書いています。

app/assets/javascripts/users.coffee
$ ->
  # *** 省略 ***

  # user_table へカラムを追加する
  user_table.setColumns([

    # 以下に注目
    { 
      data: 'id',         title: 'ユーザID', width: '5%'
      # 以下を追記
      render: (data, type, row) ->
        # data / type / row にどんなデータが入っているか確認。
        console.log data
        console.log type
        console.log row

        if data == "3"
          "あほ"
    },
    { data: 'username',   title: 'ユーザ名', width: '25%' },
    { data: 'name',       title: '名前',    width: '30%' },
    { data: 'created_at', title: '登録日時', width: '20%' },
    { data: 'updated_at', title: '更新日時', width: '20%' },
  ])
  # *** 省略 ***

フォントを変更する方法

つづいて、フォントを変更する方法を紹介します。

一例として、id が 3 のときに赤色の文字で"あほ"と表示するコードを書いています。
htmlタグを使ってそこにcssを埋め込んでいるだけとなります。(少し強引かもしれません。。)

app/assets/javascripts/users.coffee
$ ->
  # *** 省略 ***
    # 以下に注目
    { 
      data: 'id',         title: 'ユーザID', width: '5%'
      # 以下を追記
      render: (data, type, row) ->
        if data == "3"
          "<span style='color: red;'>あほ</span>"
    },
  # *** 省略 ***

Bootstrap のレイアウトを導入する

最後に応用技として、 Bootstrap のレイアウトを導入する方法を紹介します。

一例として、id が 3 のときに赤色の文字で"あほ"と表示するコードを書いています。
応用と書きましたが、htmlタグを使ってbootstrapで使用できるclassを付与しているだけとなります。

app/assets/javascripts/users.coffee
$ ->
  # *** 省略 ***
    # 以下に注目
    { 
      data: 'id',         title: 'ユーザID', width: '5%'
      # 以下を追記
      render: (data, type, row) ->
        if data == "3"
          "<div><center><span class='label label-default'>あほ</span></center></div>"
    },
  # *** 省略 ***

まとめ

いかがでしょうか。Railsなので動的に値を変更したい需要はかなりあるかと思いますが、DataTables だとドキュメントが英語で読みづらいし、あまり柔軟性がないと考える方もいると思います。
少し強引ではありますが、このような感じで色々な応用をすることも可能ですので、ご自身で色々と試してみるのもいいかもしれません。

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

[bundler: command not found: unicorn_rails Install missing gem executables with `bundle install]AWSのEC2サーバ上でRailsに追加したgemfileが反映されない時の解決例

<エラー文>
スクリーンショット 2020-01-21 7.03.08.png

このエラーの原因で考えられること

local環境でしかgemfileが反映されていないことが考えられます。
具体的には下記のことにより起こっていると考えられます。

1.rbenvのrehashし忘れ
2.bundlerの入れ忘れ
3.githubへの反映し忘れ(commitとpushのし忘れ)

解決方法

1.rbenvのrehashし忘れ

rbenv(rubyの環境構築)について更新をすれば解決します
EC2サーバー上で下記のコマンドを入力してください

[ec2-user@ip-xxx-xx-xx-xxx chat-space$ gem install bundler -v 2.0.2]$rbenv rehash

2.bundlerの入れ忘れ

EC2サーバー上にbundlerをインストールし、再度bundle installすることで解決します
バンドラーのバージョンはご自分のプロジェクトファイルのものと合わせるためローカルサーバー上でバージョンを調べたのち、EC2サーバー上で下記のコマンドを入力してください

Neverland:chat-space kontatomoya$ bundler -v
#プロジェクトのローカルディレクトリで bundler -v でbundleのバージョンを調べられます(筆者の場合は"Bundler version 2.0.2"が帰ってきたのでver2.0.2で以下を進めています)

[ec2-user@ip-xxx-xx-xx-xxx chat-space]$ gem install bundler -v 2.0.2
[ec2-user@ip-xxx-xx-xx-xxx chat-space$ gem install bundler -v 2.0.2]$ bundle install
#bundlerをインストールし、bundle installしてます

3.GitHubへの反映し忘れ(commitとpushのし忘れ)

EC2サーバーにプロジェクトを保存するには、GitHub上のデータを受け取りコピーという流れとなるため、GitHub上のデータに反映した後のデータをgit cloneしないとEC2サーバー上でも反映されません。そのため下記の手順に従って最新ファイルを反映させましょう。

①GitHub Desktopでcommitとpushし、GitHub上のデータに反映させましょう。

やり方はここでは説明しませんが簡単です。わからない人は下記などでやり方を確認してみましょう。(公式の使用方法解説ページにリンクしてます。)

https://help.github.com/ja/desktop/getting-started-with-github-desktop/creating-your-first-repository-using-github-desktop

②EC2サーバーのファイルに反映させましょう
今はEC2のプロジェクトが更新されていない状態なので、pullコマンドで更新を行いましょう

[ec2-user@ip-xxx-xx-xx-xxx chat-space$ gem install bundler -v 2.0.2]$ git pull origin master
#EC2は表示だけが必要となるため反映させるのはmasterとしましょう

③bundle installを行う
このプロジェクトのダウンロードができただけで、インストールはできていないので最後に忘れずbundle installしましょう

[ec2-user@ip-xxx-xx-xx-xxx chat-space$ gem install bundler -v 2.0.2]$ bundle install

これでこのエラーは解消されます。

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

rails+froalaエディタ使用の注意

表示させる部分にはクラスに'fr-view fr-element'を付ける

froalaエディタは、とても高機能のエディタである。

ブログアプリで使用するとき、form画面ではとくに問題ないが、その内容を表示させるときに思うように表示されないときがある。
それは、form画面ではデフォルトで

<div class="fr-view fr-element">

で囲まれているが、表示させるときはない。
画像の左寄せ、中央寄せ、右寄せの機能がうまく表示されないのはこのせいである。
なので表示させたいページで同じようにdivで囲めばよい。

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

Webサービスをインターネットに公開する(cloud9からHerokuにデプロイ)

Herokuについて

  • https://jp.heroku.com
  • Paas(Platform as s Service)と呼ばれるクラウドサービス
  • 元々、Rubyのアプリケーションをホスティングするサービス
  • PHP等、他の言語にも対応

デプロイの準備

設定箇所
1. Gemfile
2. config/database.yml
3. config/environments/production.rb
4. config/routes.rb

1.Gemfile

Gemfileにあるgem'sqlite3'を切り取って group :development, :test doに貼り付ける
gem 'pg', '~> 0.18.4'を追加する

Gemfile
group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  gem 'sqlite3'
end
Gemfile
group :production do
  gem 'pg', '~> 0.18.4'
end

bundle install --without productionを実行する(production以外のgemをインストールする)

bundle install --without production

2.config/database.yml

sqliteはproduction環境では使わないので、database: db/production.sqlite3を削除し、下記のように記述する

config/database.yml
production:
  <<: *default
  adapter: postgresql
  encoding: unicode

3.config/environments/production.rb

config.assets.compile = falseをtrueに書き換える

config/environments/production.rb
# Do not fallback to assets pipeline if a precompiled asset is missed.
  config.assets.compile = true

4.config/routes.rb
rootページ(例:root 'questions#index')が設定されているか確認する(設定されていないとエラーになってしまう)

global configの設定

個人の識別情報を登録する

$ git config --global user.name "名前"
$ git config --global user.email メールアドレス

gitの設定

ソースコードのバージョンを管理するツール

gitリポジトリの新規作成

$ git init

管理するファイルを選択する
※git ignoreファイルで書かれたものは登録しない
cloud9の設定アイコンからShow Hidden Filesにチェックが入っているか確認する

$ git add -A

commitという登録処理をする

$ git commit -m "Initial commit"

Heroku Cliのインストール

Heroku Cli:cloud9などからHerokuを操作するためのツール
cli(Command Line Interface)

ファイルをダウンロードする
https://devcenter.heroku.com/articles/heroku-cliのOther installation methodsを使う
(TarballsのLinux(x64)のリンクをコピー)

$ curl -OL https://cli-assets.heroku.com/heroku-linux-x64.tar.gz

圧縮ファイルなので、解凍する。

tar zxf heroku-linux-x64.tar.gz

herokuフォルダを/user/localフォルダに移動する
/usr/local:一般的にシステム管理者が自分でコンパイルしたアプリケーションをインストールする場所

sudo mv heroku /usr/local/

パスの設定を行う

echo 'PATH=/usr/local/heroku/bin:$PATH' >> $HOME/.bash_profile

PATH=/usr/local/heroku/bin:$PATHという文字列をユーザーのホームディレクトリにある、.bach_profileというファイルの内容に追記、という意味。
.bach_profile:シェルの設定ファイルのこと。
シェルというのは、人間からコンピュータに命令を伝えるための仕組み。

設定したパスを反映する

source $HOME/.bash_profile

インストール出来たか確認するために、herokuのバージョンを確認する

heroku -v

インストール用にダウンロードしたheroku-linux-x64.tar.gzを削除する

rm -f heroku-linux-x64.tar.gz

RailsアプリとHerokuの関連付け

アプリのフォルダまで移動し、heroku cliを使ってherokuにログインする
EmailとPasswordを入力したらログイン完了!

$ heroku login

もしheroku: Press any key to open up the browser to login or q to exit:と表示されてログインできない場合は、

$ heroku login --interactive

を実行する

Railsアプリとherokuの関連付けを行う
アプリを表示するためのURLは https://アプリ名.herokuapp.com/

$ heroku create アプリ名

デプロイ

ソースコードをサーバーにデプロイする

$ git push heroku master

実行した時に、"You must use bundle 2 or greater with this lockfile."
とエラーが表示されたら、https://programmingnavi.com/1685/サポートサイトの記事を参照する

データベースのマイグレーションを実行する

$ heroku run rails db:migrate

インターネットに公開されたwebサイトの情報を確認する

$ heroku apps:info

※アプリを削除する場合

heroku apps:destroy --app アプリ名

本当に削除して良いか確認を求められるので、再度アプリ名を入力する
→削除される

こちらの内容はhttps://www.udemy.com/course/the-ultimate-ruby-on-rails-bootcamp/
を元に作成しました。

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

Rubyでゲームを作ってみた #1

Rubyで簡単なゲーム作成

プログラミング超初心者ですが、学んだことを最大限に活かして、トランプのブラックジャックを作成していきたいと思います。

準備するもの

・トランプ(52枚)
・マーク(4種類)
・数字(1→A 11,12,13→J,Q,Kに変換)
・数字(合計するため)

ブラックジャックのルール

・カードを2枚引く
・J,Q,Kは全て10として扱う
・Aは引いた際に1or11が選択可能

1.初期設定

deck = [*(1..52)]
hand_mark = []
hand_number = []
hand_figure = []

draw(deck,hand_mark,hand_number,hand_figure)
draw(deck,hand_mark,hand_number,hand_figure)

まずは配列を定義します。
トランプ52枚の配列、マーク、数字、合計の3つの配列は空で定義します。
そして、2回引く処理です。引数として、各配列を引数とします。

2.カードを引くメソッドの定義

def draw(deck,hand_mark,hand_number,hand_figure)
  draw = deck.sample
  deck.delete(draw)
  if draw <= 4
    sum = hand_figure.inject(:+)
    puts "あなたの合計は「#{sum}」です。"
    else
  end

  hand_mark <<  mark(draw)
  hand_number << number(draw)
  hand_figure << figure(draw)
end

drawの定義です。
sampleメソッドで52枚の中からランダムに1つ選択します。
そしてdeleteメソッドでそのカードを配列から削除します。
今回deckで1から順にスペード1,ハート1,ダイヤ1,クローバー1という順番で定義します。
ので、今回は1から4がエースということになります。
エースは1or11の選択が可能なので、drawが4以下の時は現在の手札の合計を表示します。
今回はinjectを使用しています。
そして、mark,number,figureメソッドでカードのマークと数字を決定して、それぞれの配列に挿入します。

3.マーク・数字の決定

def mark(draw)
  mark_no = draw % 4
  if mark_no == 0
    mark = "♣︎"
  elsif mark_no == 1
    mark = "♠︎"
  elsif mark_no == 2
    mark = "❤︎"
  elsif mark_no == 3
    mark = "♦︎"
  else 
    puts "error"
  end
end

def number(draw)
  if draw % 4 == 0
    number = draw.div(4) - 1
  else
    number = draw.div(4)
  end
  display_list = ["A","2","3","4","5","6","7","8","9","10","J","Q","K"]
  display_number = display_list[(number)]
end

def figure(draw_card)
    if draw % 4 == 0
    sum_figure = draw.div(4)
  else
    sum_figure = draw.div(4) + 1
  end

  if sum_figure > 10
    sum_figure = 10
  elsif sum_figure = 1
    puts "Aを引きました。「1」か「11」のどちらかを入力してください。"
    judge(sum_figure)
  else  
    sum_figure
  end
end

markではdrawを4で割った際のあまりによって、マークを判定しています。

numberでは数字の表示をします。
div(4)は4で割った時の商の整数部分のみを返します。
1から4までを0としたいので、4で割れる時だけ処理を変えます。
そして、display_listに対応する数字を用意します。
これにより、AとJ,Q,Kの表示が可能になりました。

figureでは数字の合計を計算します。
まず、numberと同様に4で割れる時だけifで処理を変えます。
そして、絵柄、エース、それ以外でifを使います。
10より大きい(=絵柄)の場合は10とします。
エースは文字を出力して、judgeで判定します。
それ以外の場合はそのままの数字を返します。

こんな簡単な内容ですが続きます。

keyword:
Array,配列,引数,%,.div,sample,delete,inject

※2020/1/21 追記
figureメソッドのAかどうかの判定が間違っていましたので、修正しました。

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

2020年 ITカンファレンスまとめ

2020年に開催されるITカンファレンス

1月20日時点のまとめです。
随時、更新していくので更新されていたら、コメントで教えてください。

言語

PyCon JP

Pythonに関するカンファレンス
日程: 8月28日、29日の予定
場所: 大田区産業プラザPiO
公式サイト

PHPカンファレンス

PHPに関するカンファレンス
日程: 10月11日
場所: 大田区産業プラザPiO
2019公式サイト
2020公式サイト

PHPkaigi

PHPに関するカンファレンス
日程: 2月9日 16:30〜
2月10日 10:00〜
2月11日 10:00〜
場所: 練馬区立区民・産業プラザ Coconeriホール
2020公式サイト

JSConf

JavaScriptに関するカンファレンス
開催不明
去年
日程:11月30、12月1日
場所:アーツ千代田 3331
2019公式サイト

RubyKaigi

Rubyに関するカンファレンス
日程:4月9日、11日
場所:長野県 まつもと市民芸術館
公式サイト

GoConference

Goに関するカンファレンス 春、秋開催
開催不明
去年
日程:秋 10月28日
場所:みどりコミュニティセンター
2019公式サイト

YAPC

Perlに関するカンファレンス
日程:3月27日、28日
場所:京都
公式サイト

JJUG

Javaに関するカンファレンス
開催不明
去年
日程:春 5月18日、秋 11月23日
場所:ベルサール新宿グランドコンファレンスセンター
春 公式サイト
秋 公式サイト

TSConf JP

TypeScriptに関するカンファレンス
日程:2月22日
場所:NAVITIME JAPAN
公式サイト

try! Swift

Swiftに関するカンファレンス
日程:3月18日,19日
場所:ベルサール渋谷ファースト
公式サイト

FW

DjangoCongress

Djangoに関するカンファレンス
日程: 6月20日
場所: 長野市生涯学習センター
公式サイト

Laravel JP Conference

Larabelに関するカンファレンス
日程:3月21日
場所:グランパークカンファレンス
公式サイト

CakeFest

CakePHPに関するカンファレンス
開催日不明
公式サイト

VueFes Japan

Vueに関するカンファレンス
開催不明
去年
日程: 10月12日
場所: TOC五反田メッセ
公式サイト

React Conf Japan

Reactに関するカンファレンス
日程:5月21日
場所:株式会社ナビタイムジャパン
公式サイト

ng-japan

Angularに関するカンファレンス
開催不明
去年
日程:7月13日
場所:Google Tokyo Office
公式サイト

NodeTokyo

Node.jsに関するカンファレンス
開催不明
去年
日程: 10月5日
場所: 丸の内 vacans
公式サイト

スマホ

PWA Night

PWAに関するカンファレンス
日程:2月1日
場所:Abema Towers 10F セミナールーム
公式サイト

iOSDC

iOSに関するカンファレンス
開催不明
去年
日程:9月5日、6日、7日
場所:早稲田大学 理工学部西早稲田キャンパス63号館
2019公式サイト

DroidKaigi

Androidに関するカンファレンス
日程:2月20日、21日
場所:五反田TOCビル 13F
公式サイト

Android Bazaar and Conference

開催不明
去年
日程:5月26日
場所:東海大学 高輪キャンパス
2019公式サイト

その他

AWS SUMMIT TOKYO/OSAKA

日程:5月13日、14日、15日
場所:パシフィコ横浜
日程:6月30日
場所:ホテルニューオータニ大阪
公式サイト

Google Cloud Next

開催不明
去年
日程:7月30日、8月1日
場所:東京プリンスホテル
ザ プリンス パークタワー東京
2019公式サイト

Microsoft Ignite The Tour Tokyo

Microsoftに関するカンファレンス
開催不明
去年
日程:12月5日、6日
場所:ザ・プリンス パークタワー東京
公式サイト

PostgreSQL Conference Japan

開催不明
去年
日程: 11月15日
場所: AP品川9階
2019公式サイト

Adobe MAX Japan

Adobeに関するカンファレンス
日程:11月24日
開催:パシフィコ横浜
公式サイト

Unite Tokyo

Unityに関するカンファレンス
開催不明
去年
日程:9月25日、26日
場所:グランドニッコー東京 台場
2019公式サイト

CEDEC

ゲームに関するカンファレンス
日程:9月2日、3日、4日
場所:パシフィコ横浜 ノース
公式サイト

Object-Oriented Conference

Object指向に関するカンファレンス
日程:2月16日
場所:お茶の水女子大学
公式サイト

VimConf

Vimに関するカンファレンス
開催不明
去年
日程:11月3日
場所:アキバホール
2019公式サイト

Digital Thinkers Conference

デザインに関するカンファレンス
日程:1月23日、24日
場所:イイノホール&カンファレンスセンター
公式サイト

MeetUp

Docker Meetup Tokyo

Dockerに関するミートアップ
公式サイト

Kubernetes Meetup Tokyo

Kubernetesに関するミートアップ
公式サイト

Twitter

最近、Twitter始めました。
ぜひ友達募集中です!
https://twitter.com/apasn1

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

初学者によるプログラミングMemo #17 scanメソッド

はじめに

今回はscanメソッドを使用するお話です
なお、本記述はMacにおいて、Railsでの開発を前提としています
また、まだまだひよっこですので、不備等ございましたらご指摘いただけると幸いです

目次

  • "scan"メソッドについて
  • 正規表現での特徴

"scan"メソッドについて

scanメソッドは「ある文字列に対して、マッチさせたい文字を指定し、マッチした部分文字列を配列として返す」メソッドです
使い方を具体的に見てみましょう

基本

"調べる元となるの文字列".scan"調べたい文字列(or正規表現)"

以下のような記述があるとします

sample1.rb
word = "foobar"

p word.scan(/ba/)
# p word.scan("ba") これも同じ結果

=> ["ba"]

結果として得られるのは["ba"]です
文字列を変えてみましょう

sample2.rb
key = "foobarbarbazz"

p key.scan(/ba/)

=> ["ba", "ba", "ba"]

この場合、"ba"は3回出てくるため、sample1.rbから結果は変わりました
このように、ある文字列に対してその文字が入っているか検索をかけてくれると考えてもいいでしょう
ただし、結果は配列で返ってくるため、単純に回数だけ知りたい場合は"length"メソッドをつけましょう

sample3.rb
key = "foobarbarbazz"

p key.scan(/ba/).length

=> 3

ブロックに渡して実行する

配列として返さない方法もあります

sample4.rb
hoge = "foobarbazfoobarbaz"

hoge.scan(/ba/) {|s| p s }

=> "ba"
   "ba"
   "ba"
   "ba"

正規表現での特徴

正規表現を使用するとき、"()"でくくってやると、その部分にマッチした「部分文字列の配列」の配列を返します
ややこしいので実際に挙動を見ましょう

sample5.rb
hoge = "foobarbazfoobarbaz"

p hoge.scan(/(ba)(.)/)

=> [["ba", "r"], ["ba", "z"], ["ba", "r"], ["ba", "z"]]

こんな感じです
マッチした「部分文字列の配列」の配列を返してくれています
もちろんブロックパターンも使えます

sample6.rb
hoge = "foobarbazfoobarbaz"

hoge.scan(/(ba)(.)/){|s| p s }

=> ["ba", "r"]
   ["ba", "z"]
   ["ba", "r"]
   ["ba", "z"]

さいごに

問題をやろうかと思いましたが、なんか簡単なものしか思いつかなかったのでやめました

よくテレビなんかで「歌詞に"BABY"が最も入っている曲第一位は」とかやってますよね
小さい頃は「数えるの大変やったんやろうなー」なんて思っていましたが、これを使えばそんな心配は全くないですね

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