- 投稿日:2020-01-21T23:29:42+09:00
Rubyのプログラミングスキルを向上させるコツ
- 投稿日:2020-01-21T22:28:38+09:00
MVCとはなんなのか
Ruby on Rails初心者として、自分自身の理解を整理したいのでまとめたいと思います。
何か認識違いがあれば、ご指摘いただければ幸いです。MVCとは
UI(ユーザインタフェース)を持つソフトウエアのアーキテクチャの一種。
画面に表示する部分(UI)は、アプリケーション固有のデータや処理の扱いの性質が異なるため、画面の表示とアプリケーションのデータに関する部分を混ぜて記述してしまうと、コードが複雑化してしまい、保守性が悪くなってしまいます。そこで、アプリケーション固有のデータの部分、画面を表示させるUIの部分、この2つをつなぎ合わせる制御の部分の3つに分けて管理をしやすくしようというものです。
M(model)
アプリケーション固有のデータや処理の扱いの部分
・データベースへの保存、読み込み、永続化を担当する。V(View)
UIに関わる部分
・HTTPレスポンスの中身を実際に組み立てて、画面を表示する。C(Controller)
ModelとViewを統合的に制御する部分
・ブラウザからのリクエストを受けて適切なレスポンスを作成するための制御を行う。MVCのメリット
・それぞれが専門的な分野になるため、一つ一つの仕様変更がしやすくなる。
・分野ごとに得意な人が開発を進められる。まとめ
MVCアーキテクチャを使うことによって複雑化するコードをそれぞれの3つの分野に分けることで単純化し、管理しやすくすることで、それぞれが得意な分野に分かれて開発できるようになり、作業しやすい環境が生まれるということ(と感じました)。
- 投稿日:2020-01-21T19:10:56+09:00
アップロードした画像の有無の判定
実装したこと
ビューファイルで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にしたら無事に解決しました。
ちょっとややこしかったのでシェア。
- 投稿日:2020-01-21T18:05:14+09:00
【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:{}}として、情報を引き渡すことができるということです。
簡単に使えるので、是非活用してみてください!
- 投稿日:2020-01-21T17:50:56+09:00
テスト駆動開発から始める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 world
をhello 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つ書く。
グリーン:そのテストを迅速に動作させる。このステップでは罪を犯してもよい。
リファクタリング:テストを通すために発生した重複をすべて除去する。
レッド・グリーン・リファクタリング。それが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 endclass FizzBuzz def self.generate(number) result = number.to_s if number.modulo(3).zero? result = 'Fizz' end result end endclass 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 endclass 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'引数の
number
は15
だから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 endif式 の中でさらに 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 skipsTODOリスト の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 4100まで表示したいのでこうですね。
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.rbrequire './lib/fizz_buzz.rb' puts FizzBuzz.generate_listputs は結果を画面に出力するメソッドです。 先程は 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)
- 投稿日:2020-01-21T17:43:04+09:00
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スクリプトを使ってテスト結果のデータを取得したり、逆にプログラミングした内容でテストケースを自動作成するといった使い方も考えられます。今後のバージョンアップをお待ちください!
- 投稿日:2020-01-21T17:36:43+09:00
[rails] 投稿一覧にユーザー情報を表示
始めに
今回、プロゲートと違う方法で投稿一覧でユーザー情報を表示します。
プロゲートでrailsを終えてからよく、ツイッター風アプリを作成すると思います。
そこで、プロゲートのコードをコピペすることは誰でも出来ますが、
重要なのは理解して自分でコードを書くことだと思います。他のやり方で表示することが出来たので、参考やヒントになれば嬉しいです。
完成イメージ
※注:デザインは今回しません。また、分かりやすく理解してもらうためにユーザー情報は名前だけにします。前提
・usersテーブルと投稿用のテーブル(今回はcommentsテーブル)があること。
MVC(model/controller/view)の設定
controller設定
app/controllers/comments_controller.rbclass CommentsController < ApplicationController def index @comment = Comment.all.order(created_at: :desc) endこれはプロゲートでもでてきた投稿された順に上から表示しています。
modelの設定(※ここからプロゲートと違う!)
app/models/comment.rbclass Comment < ApplicationRecord belongs_to :user#追記 endbelongs_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テーブルをアソシエーションしました。
もし、何か修正点とかございましたらコメント等
恐縮ですが、宜しくおねがいします
- 投稿日:2020-01-21T17:11:34+09:00
【Rails】enumを使用したセレクトボックスの実装とDBへの保存
はじめに
都道府県などのプルダウンメニューを作成する際、enumを使いました
これを使うことで都道府県などのデータをわざわざテーブルとして用意する必要がなくなります
便利だったのですが、DBへの保存の際に型が合わないなどの問題も発生したのでまとめておきます環境
Ruby: 2.5.1
Rails: 5.2.4実際の操作
実際の操作は以下のようになります
- 該当カラムをinteger型で作成
- モデルにプルダウンメニューで表示させるもの一覧を記載
- ビューで表示させるプルダウンの記述
- 入力したフォームの受け取り型を編集
1. 該当カラムをinteger型で作成
下記の場合は都道府県です
schema.rbt.integer :prefecture, null: false
2. モデルにプルダウンメニューで表示させるもの一覧を記載
models/address.rbenum 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の中を見てみると"北海道"として値を取得してきてしまいました。
prefectureカラムはinteger型なのでこのままだとDBに登録できなくて困ります。
4. 入力したフォームの受け取り型を編集
色々調べた結果、以下のようにすると対応するvalueの値を取得できることがわかりました。
address.html.haml= form.select :prefecture, options_for_select(Address.prefectures), {prompt: "選択してください"}, class: "prefecture-select"しかし、これだと"1"という文字列なのでまだカラムに登録できません。
form.selectにおける入力値を数値として送れないかと思いましたが、やり方がわからなかったので、受け取る側で無理矢理数値型に変換することにしました。少々かっこ悪いですが、ストロングパラメータで数値型にした後にmergeしてます。
registrations_controller.rbdef address_params params.required(:address).permit(:postal_code, :city, :street, :building).merge(prefecture: params[:address][:prefecture].to_i) end
これで晴れてDBへ保存できるようになりましたただもっとスマートな方法がありそうな気がしてます
何か知ってる方いらっしゃいましたら教えていただけると嬉しいです
似たようなものにactive_hashというものがあります
gemをインストールする必要がありますが、こちらは数値型としてデータを取得できますし、ActiveRecordのメソッドも使えるみたいなので便利そうです参考記事
- 投稿日:2020-01-21T17:09:21+09:00
Nokogiriでタグの切り出しがうまくいかないとき名前空間の存在を忘れていませんか?
HTMLやXMLのタグをスクレイビングするときに重宝するNokogiriライブラリは、かなり機能が充実していますが、精通するまで使用するのは難しいです。
ここでは、ライブラリ使用時にハマってしまったことを備忘録としてまとめました。
簡単な使い方が載っているサイトはすぐにたくさん見つかりましたが、後述するハマりポイントが記載されているサイトはなかなか見つかりませんでした。Nokogiriの詳しい使い方は既に多くの人が分かりやすく説明してくれていますので、そちらを参照ください。
一応簡単におさらい
sample.rbrequire '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.rbfile = Nokogiri.XML(File.read(morning.xml))タグの中身だけが欲しいんだ
morning.rbfile = Nokogiri.XML(File.read(morning.xml)) title = file.xpath('/head/title') puts title # > <title>モーニング娘。</title>とすれば、titleタグが取得できますが、このままでは「
モーニング娘。」と出力されてしまいます。中身の「モーニング娘。」だけが欲しい時は「.text.strip」を追加してあげましょう。morning.rbfile = 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タグが取得できます。
- 投稿日:2020-01-21T16:50:13+09:00
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-sampleRailsアプリを新規作成して、以下のGemを追加します。
Gemfile.rbgem 'devise' gem 'devise_token_auth' gem 'grape' gem 'grape_devise_token_auth'$ bundle installDevise関連の環境設定を行う
# rails g devise:install # rails g devise_token_auth:install User auth新たに作成されたUserモデルのマイグレーションを行います。
# rails db:migrate基本部分のコード修正
app/controllers/application_controller.rbclass ApplicationController < ActionController::Base include DeviseTokenAuth::Concerns::SetUserByToken protect_from_forgery with: :null_session endprotect_from_forgeryは一般的なRailsアプリにおけるCSRF対策の仕組みに関する設定で、指定しない場合はPOSTリクエストを受け付けた際にActionController::InvalidAuthenticityTokenエラーとなります。これを無効化するには
protect_from_forgery with: :null_session
とします。app/models/user.rbclass 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 endUserモデルでは、
devise
メソッドで有効化する機能が列挙されていますが、:trackable
が含まれていたら外しておきましょう。前述のrails g devise_token_auth:install User auth
コマンドで作成されたマイグレーションでtrackable用のカラムが作成されておらず、サインインリクエストの際にNoMethodError (undefined method `current_sign_in_at' 〜
が発生するためです。(末尾の参考情報を参照)config/routes.rbRails.application.routes.draw do devise_for :users namespace :api do mount_devise_token_auth_for 'User', at: '/v1/auth' mount Api::Root => '/' end endAPIエンドポイント部分のコード追加
app/api/api.rbmodule Api class Root < Grape::API GrapeDeviseTokenAuth.setup! do |config| config.authenticate_all = true end mount V1::Root end endapi/v1/root.rbmodule 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サインアップ(ユーザーアカウント作成)
まず、新しいユーザーを作成します。
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つのリクエストヘッダーを付加します。
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クライアントの方では、トークンの値が変更されるのに応じて書き換わったトークンを適切に更新管理することが大切になります。
参考情報
Devise + Grape で認証付きAPIの実装 - Qiita
https://qiita.com/travelist/items/ec0b08a9a19cbe9ec78bGrapeとdevise_token_authを併用する方法について
- Usage with Grape · Issue #73 · lynndylanhurley/devise_token_auth https://github.com/lynndylanhurley/devise_token_auth/issues/73
- How can I use this gem with Grape?・FAQ - devise-token-auth https://devise-token-auth.gitbook.io/devise-token-auth/faq#how-can-i-use-this-gem-with-grape
モデルでのTrackableオプションについて
- Remove Trackable option from generator by SugiKent · Pull Request #1362 · lynndylanhurley/devise_token_auth
https://github.com/lynndylanhurley/devise_token_auth/pull/1362- Why is Trackable option still active? · Issue #1356 · lynndylanhurley/devise_token_auth
https://github.com/lynndylanhurley/devise_token_auth/issues/1356
- 投稿日:2020-01-21T16:14:56+09:00
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アプリをデプロイしていて、一定時間ごとにデータベースを初期化するために今回の方法に行き着きました。
前提として、
- Heroku上にアップローダーしたファイルは再起動の際に削除されます:Why are my file uploads missing/deleted? - Heroku Help
- また、HerokuのDynoは24時間ごとに再起動がかかるので、ファイルをアップロードして長時間保存することはできません(gitリポジトリに含まれるものは除きます): Dynos and the Dyno Manager | Heroku Dev Center
このような理由で、画像投稿機能のある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.rbclass 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.rakenamespace :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 CenterPOST /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 CenterGASでスケジューラーを作成
他のスケジューラーではダメなのか
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を検索して、アプリを追加してください。
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のスクリプトの定期実行には、トリガーを登録します。
これで、毎日、0~1時ごろに登録した関数が自動実行されます。
- 投稿日:2020-01-21T15:24:45+09:00
dotenv のサンプルメモ
bkeepers/dotenv: A Ruby gem to load environment variables from
.env
. https://github.com/bkeepers/dotenvサンプル
- 存在しないキーを指定すると
nil
となる- 実際の環境変数があったらそちらが優先される
.envTEST="hogehoge"require "dotenv" require "pp" Dotenv.load pp ENV["TEST"] # => "hogehoge" pp ENV["TEST2"] # => nil
- 投稿日:2020-01-21T15:16:57+09:00
配列の値をハッシュのキーとバリューに変換する方法
配列の値をハッシュのキーとバリューに変換する方法
実務で書かれていたコードでなるほど!という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.htmlirb(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は様々なメソッドがあって便利ですよね!
- 投稿日:2020-01-21T13:19:56+09:00
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からマイグレートを行う
- 投稿日:2020-01-21T11:01:19+09:00
Rails devise + google_oauth2 でログイン後のリダイレクト先を指定
applictaion_controller.rbではリダイレクト処理したくない
理由は特にない。気分の問題。
omniauth_callbacks_controller.rbに書く
app/controllers/chichinpuipui/omniauth_callbacks_controller.rbdef google_oauth2 ... ... end protected #ログイン後のリダイレクト先 def after_sign_in_path_for(resource) itaino_itaino_tondeke_path endgoogle_oauth2メソッドより上にprotected書くとエラーになるので注意。
- 投稿日:2020-01-21T10:38:10+09:00
WebMockを使って外部API接続をスタブ化する
概要
APIはJavaで実装して、Javaから返ってきたjsonをRailsで受け取ってRails側でそのjsonデータを表示するという流れにしたく、Railsの実装をしている時のテストや、いちいちJavaを起動させなくていいようにスタブ化したのが目的です。
Rails開発でWebMockを使ってAPIアクセスをスタブ化するからのコードを引っ張ってきただけなので、リンク先の解説みた方が断然わかりやすいです。
実際のコード
api_stub.rbmodule 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 endapi_module.rbmodule ApiModule require_relative 'api_stub' prepend ApiStub if ENV["APIMODE"] == "mock" && Rails.env.development? def call_get_api # ここに本来の外部API呼び出しの実装が入る end endapi_client.rbclass ApiClient require_relative 'api_module' include ApiModule endweb_controller.rbclass 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を利用する
- 投稿日:2020-01-21T08:32:56+09:00
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')こうすれば、どのブラウザでも文字化けせずに日本語のファイル名でダウンロードすることができます。
- 投稿日:2020-01-21T08:19:38+09:00
カラムを追加して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(バックアップファイル).sqldumpのオプション内容
-- 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
- 投稿日:2020-01-21T07:20:46+09:00
【Rails】 DataTables 動的にカラムを変更する方法
はじめに
Railsアプリケーションで DataTables を使う方法を以前にまとめさせていただきました。
DataTables を使ったテーブルのカラムを動的に変更したい需要があると思いますが、まとめられている記事を見かけませんでしたので、こちらにてまとめさせていただきます。
この方法を理解していれば、開発時間を極端に減らして高機能なテーブルを提供することができます。なお、今回の方法は少し強引にカラムを動的に変更しています。
もし、もっといい方法があるということをご存知の方はコメントをいただければ嬉しいです。動的にカラムを変更する方法
前提条件
下記リンクにてDataTablesを実装していること。
このリンク先のコードを元にして、動的にカラムを変更する方法をこちらの記事にて特記したいと思います。
- 【Rails】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 だとドキュメントが英語で読みづらいし、あまり柔軟性がないと考える方もいると思います。
少し強引ではありますが、このような感じで色々な応用をすることも可能ですので、ご自身で色々と試してみるのもいいかもしれません。
- 投稿日:2020-01-21T07:17:25+09:00
[bundler: command not found: unicorn_rails Install missing gem executables with `bundle install]AWSのEC2サーバ上でRailsに追加したgemfileが反映されない時の解決例
このエラーの原因で考えられること
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 rehash2.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上のデータに反映させましょう。
やり方はここでは説明しませんが簡単です。わからない人は下記などでやり方を確認してみましょう。(公式の使用方法解説ページにリンクしてます。)
②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これでこのエラーは解消されます。
- 投稿日:2020-01-21T02:14:41+09:00
rails+froalaエディタ使用の注意
表示させる部分にはクラスに'fr-view fr-element'を付ける
froalaエディタは、とても高機能のエディタである。
ブログアプリで使用するとき、form画面ではとくに問題ないが、その内容を表示させるときに思うように表示されないときがある。
それは、form画面ではデフォルトで<div class="fr-view fr-element">で囲まれているが、表示させるときはない。
画像の左寄せ、中央寄せ、右寄せの機能がうまく表示されないのはこのせいである。
なので表示させたいページで同じようにdivで囲めばよい。
- 投稿日:2020-01-21T01:15:37+09:00
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.rb1.Gemfile
Gemfileにあるgem'sqlite3'を切り取って group :development, :test doに貼り付ける
gem 'pg', '~> 0.18.4'を追加するGemfilegroup :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' endGemfilegroup :production do gem 'pg', '~> 0.18.4' endbundle install --without productionを実行する(production以外のgemをインストールする)
bundle install --without production2.config/database.yml
sqliteはproduction環境では使わないので、database: db/production.sqlite3を削除し、下記のように記述する
config/database.ymlproduction: <<: *default adapter: postgresql encoding: unicode3.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 = true4.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 -Acommitという登録処理をする
$ 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.gzherokuフォルダを/user/localフォルダに移動する
/usr/local:一般的にシステム管理者が自分でコンパイルしたアプリケーションをインストールする場所sudo mv heroku /usr/local/パスの設定を行う
echo 'PATH=/usr/local/heroku/bin:$PATH' >> $HOME/.bash_profilePATH=/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.gzRailsアプリと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/
を元に作成しました。
- 投稿日:2020-01-21T00:38:16+09:00
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) enddrawの定義です。
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 endmarkでは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かどうかの判定が間違っていましたので、修正しました。
- 投稿日:2020-01-21T00:28:07+09:00
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始めました。
ぜひ友達募集中です!
https://twitter.com/apasn1
- 投稿日:2020-01-21T00:24:00+09:00
初学者によるプログラミングMemo #17 scanメソッド
はじめに
今回はscanメソッドを使用するお話です
なお、本記述はMacにおいて、Railsでの開発を前提としています
また、まだまだひよっこですので、不備等ございましたらご指摘いただけると幸いです目次
- "scan"メソッドについて
- 正規表現での特徴
"scan"メソッドについて
scanメソッドは「ある文字列に対して、マッチさせたい文字を指定し、マッチした部分文字列を配列として返す」メソッドです
使い方を具体的に見てみましょう基本
"調べる元となるの文字列".scan"調べたい文字列(or正規表現)"以下のような記述があるとします
sample1.rbword = "foobar" p word.scan(/ba/) # p word.scan("ba") これも同じ結果 => ["ba"]結果として得られるのは["ba"]です
文字列を変えてみましょうsample2.rbkey = "foobarbarbazz" p key.scan(/ba/) => ["ba", "ba", "ba"]この場合、"ba"は3回出てくるため、sample1.rbから結果は変わりました
このように、ある文字列に対してその文字が入っているか検索をかけてくれると考えてもいいでしょう
ただし、結果は配列で返ってくるため、単純に回数だけ知りたい場合は"length"メソッドをつけましょうsample3.rbkey = "foobarbarbazz" p key.scan(/ba/).length => 3ブロックに渡して実行する
配列として返さない方法もあります
sample4.rbhoge = "foobarbazfoobarbaz" hoge.scan(/ba/) {|s| p s } => "ba" "ba" "ba" "ba"正規表現での特徴
正規表現を使用するとき、"()"でくくってやると、その部分にマッチした「部分文字列の配列」の配列を返します
ややこしいので実際に挙動を見ましょうsample5.rbhoge = "foobarbazfoobarbaz" p hoge.scan(/(ba)(.)/) => [["ba", "r"], ["ba", "z"], ["ba", "r"], ["ba", "z"]]こんな感じです
マッチした「部分文字列の配列」の配列を返してくれています
もちろんブロックパターンも使えますsample6.rbhoge = "foobarbazfoobarbaz" hoge.scan(/(ba)(.)/){|s| p s } => ["ba", "r"] ["ba", "z"] ["ba", "r"] ["ba", "z"]さいごに
問題をやろうかと思いましたが、なんか簡単なものしか思いつかなかったのでやめました
よくテレビなんかで「歌詞に"BABY"が最も入っている曲第一位は」とかやってますよね
小さい頃は「数えるの大変やったんやろうなー」なんて思っていましたが、これを使えばそんな心配は全くないですね