20200125のRubyに関する記事は26件です。

【Ruby】Qiita 週間いいね数ランキング【タグ別】

他のタグ

集計期間

01月18日 ~ 01月25日

いいね数ランキング

1位: Rails 6ではsend_data/send_fileメソッド呼び出し時にERB::Util.url_encodeは不要です

Ruby Rails
9いいね
@jnchitoさん(01月20日 23時32分の投稿)

2位: テスト駆動開発から始めるRuby入門 ~2時間でTDDとリファクタリングのエッセンスを体験する~

Ruby TDD 初心者 入門 Refactoring
6いいね
@k2worksさん(01月21日 08時50分の投稿)

3位: HanamiのアプリケーションをREST APIとして実装する

Ruby api hanami
6いいね
@damonokoさん(01月19日 20時37分の投稿)

4位: devise ユーザーのプロフィール画面作成と編集(デフォルトをカスタマイズ)

Ruby Rails devise Gem 初心者
5いいね
@akr03xxxさん(01月20日 14時01分の投稿)

5位: DXOpalがマルチタッチに対応しました

Ruby Opal DXOpal
4いいね
@yharaさん(01月21日 15時25分の投稿)

6位: 2020年 ITカンファレンスまとめ

Ruby Python PHP JavaScript Conference
4いいね
@TaitoAjikiさん(01月20日 15時28分の投稿)

7位: 【Ruby】3文字の時だけ「遊☆戯☆王」みたいに出力されるアルゴリズム

Ruby アルゴリズム ライフハック
4いいね
@kaorioka09jmさん(01月20日 12時43分の投稿)

8位: Rubyのencodeとforce_encodingの違い

Ruby encode
4いいね
@kuracuxさん(01月20日 04時04分の投稿)

9位: 【翻訳】URI.escapeは非推奨メソッドです。あなたのクエリ文字列をパーセントエンコードするには

Ruby Rails
3いいね
@jnchitoさん(01月25日 03時32分の投稿)

10位: CSS,SCSSで画像を背景にする方法

Ruby HTML CSS scss haml
2いいね
@jiroubouさん(01月24日 11時23分の投稿)

11位: [Ruby] Array#find_index の複数の index を返すバージョンがほしい

Ruby
2いいね
@QUANONさん(01月24日 02時40分の投稿)

12位: 【Rails×Ajax】いいね機能の実装で上手く出来ないあなたへの2つの注意喚起 #学習者向け

Ruby JavaScript Rails エラー対処
2いいね
@shoji621さん(01月20日 03時03分の投稿)

13位: configure IIS with ruby on rails app on windows server

Ruby Rails Windows deploy Deployment
2いいね
@alokrawat050さん(01月19日 12時51分の投稿)

14位: ページ遷移先でリロードしないと非同期通信(ajax)できない

Ruby JavaScript Rails
2いいね
@avicii2314さん(01月19日 05時18分の投稿)

15位: https(SSL)通信の環境下でjavascriptが動かなくなる場合の原因と解決方法 ( 本番環境(AWS)でjavascriptを読み込む方法 )

Ruby JavaScript Rails
2いいね
@avicii2314さん(01月19日 04時17分の投稿)

16位: テスト駆動開発から始めるRuby入門 ~ソフトウェア開発の三種の神器を準備する~

Ruby TDD リファクタリング 入門 テスト駆動開発
1いいね
@k2worksさん(01月25日 10時42分の投稿)

17位: 【jQuery】RailsでValidation Pluginを使った動的なバリデーションチェックの実装 〜詳細実装/Bootstrap編〜

Ruby Rails jQuery Bootstrap Validation
1いいね
@tiphp452さん(01月25日 01時24分の投稿)

18位: 初学者によるプログラミングMemo #19 正規表現(基本編)

Ruby 正規表現
1いいね
@Ikuy_hさん(01月24日 10時21分の投稿)

19位: 小規模チームが簡単に管理できるテスト管理アプリを個人開発しました

Ruby Heroku 個人開発 テスト管理ツール
1いいね
@shanhongkunさん(01月23日 23時42分の投稿)

20位: Rspecと Factoryの使い方

Ruby Rails RSpec FactoryGirl
1いいね
@gototakumaさん(01月23日 14時47分の投稿)

21位: RubyでLeetCodeを解いてみた Palindrome Number

Ruby leetcode
1いいね
@yusuke-0505さん(01月23日 05時03分の投稿)

22位: rakeタスクに引数を渡したいとき

Ruby Rails rake
1いいね
@kaobabaさん(01月23日 02時10分の投稿)

23位: 初学者によるプログラミングMemo #18 フィボナッチ数列(メモ化)

Ruby フィボナッチ数列 メモ化
1いいね
@Ikuy_hさん(01月22日 16時16分の投稿)

24位: [Ruby on Rails]データベースの作成、カラムの追加

Ruby Rails
1いいね
@sansiroさん(01月22日 10時19分の投稿)

25位: トップレベルで定義したメソッドは全クラスのプライベートメソッドとなる

Ruby
1いいね
@tsuruoka91さん(01月22日 04時44分の投稿)

26位: Rails6 のちょい足しな新機能を試す 117(puma pidfile編)

Ruby puma Rails6
1いいね
@suketaさん(01月22日 03時20分の投稿)

27位: 自動デプロイ(Capistrano)でエラー mkdir: ディレクトリ `/var/www' を作成できません: 許可がありません

Ruby Rails Capistrano AWS Rails5
1いいね
@nousiさん(01月21日 15時21分の投稿)

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

Ruby Rails
1いいね
@y-sunaさん(01月21日 08時11分の投稿)

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

Ruby Rails devise grape devise_token_auth
1いいね
@shimizu-nowhereさん(01月21日 07時50分の投稿)

30位: Rubyでトランプゲームを作ってみた #1

Ruby
1いいね
@okiku-sttさん(01月20日 15時38分の投稿)

31位: 秀丸からRubyを呼び出して、選択範囲を加工する

Ruby 秀丸エディタ
1いいね
@code2545Lightさん(01月20日 12時23分の投稿)

32位: Erubi とは何か

Ruby erb Erubi
1いいね
@scivolaさん(01月20日 12時07分の投稿)

33位: 【これからプログラミング&クラウドを始める人向け】AWS Cloud9 を利用して Ruby の開発環境を作ってみる③ - Ruby のバージョン管理

Ruby AWS cloud9
1いいね
@KCbogardさん(01月19日 16時32分の投稿)

34位: high_voltageで利用規約等の静的ページを作る

Ruby Rails 静的ページ 初学者向け high_voltage
1いいね
@royroyさん(01月19日 08時46分の投稿)

35位: RailsにおけるAjaxの実装(JavaScriptとjQueryのコード比較)

Ruby JavaScript Rails jQuery Ajax
1いいね
@t-yama-3さん(01月19日 08時19分の投稿)

36位: 整数から任意の桁の数字を取得する方法

Ruby
1いいね
@kodaiiさん(01月19日 07時59分の投稿)

37位: 【Rails】ActionMailer + AWS SES

Ruby Rails AWS ses ActionMailer
1いいね
@syukan3さん(01月19日 05時50分の投稿)

38位: MacでのRuby環境構築2020

Ruby Mac 初心者
1いいね
@chihiroさん(01月19日 05時22分の投稿)

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

【Ruby】Qiita 週間いいね数ランキング【自動更新】

他のタグ

集計期間

01月19日 ~ 01月26日

いいね数ランキング

1位: 【翻訳】URI.escapeは非推奨メソッドです。あなたのクエリ文字列をパーセントエンコードするには

Ruby Rails
14いいね
@jnchitoさん(01月25日 12時32分の投稿)

2位: Rails 6ではsend_data/send_fileメソッド呼び出し時にERB::Util.url_encodeは不要です

Ruby Rails
9いいね
@jnchitoさん(01月21日 08時32分の投稿)

3位: テスト駆動開発から始めるRuby入門 ~2時間でTDDとリファクタリングのエッセンスを体験する~

Ruby TDD 初心者 入門 Refactoring
6いいね
@k2worksさん(01月21日 17時50分の投稿)

4位: devise ユーザーのプロフィール画面作成と編集(デフォルトをカスタマイズ)

Ruby Rails devise Gem 初心者
6いいね
@akr03xxxさん(01月20日 23時01分の投稿)

5位: DXOpalがマルチタッチに対応しました

Ruby Opal DXOpal
4いいね
@yharaさん(01月22日 00時25分の投稿)

6位: 2020年 ITカンファレンスまとめ

Ruby Python PHP JavaScript Conference
4いいね
@TaitoAjikiさん(01月21日 00時28分の投稿)

7位: 【Ruby】3文字の時だけ「遊☆戯☆王」みたいに出力されるアルゴリズム

Ruby アルゴリズム ライフハック
4いいね
@kaorioka09jmさん(01月20日 21時43分の投稿)

8位: Rubyのencodeとforce_encodingの違い

Ruby encode
4いいね
@kuracuxさん(01月20日 13時04分の投稿)

9位: 【Ruby】Qiita 週間いいね数ランキング【自動更新】

Ruby
2いいね
@kou_pg_0131さん(01月25日 22時53分の投稿)

10位: 【jQuery】RailsでValidation Pluginを使った動的なバリデーションチェックの実装 〜詳細実装/Bootstrap編〜

Ruby Rails jQuery Bootstrap Validation
2いいね
@tiphp452さん(01月25日 10時24分の投稿)

11位: CSS,SCSSで画像を背景にする方法

Ruby HTML CSS scss haml
2いいね
@jiroubouさん(01月24日 20時23分の投稿)

12位: [Ruby] Array#find_index の複数の index を返すバージョンがほしい

Ruby
2いいね
@QUANONさん(01月24日 11時40分の投稿)

13位: 【Rails×Ajax】いいね機能の実装で上手く出来ないあなたへの2つの注意喚起 #学習者向け

Ruby JavaScript Rails エラー対処
2いいね
@shoji621さん(01月20日 12時03分の投稿)

14位: テスト駆動開発から始めるRuby入門 ~ソフトウェア開発の三種の神器を準備する~

Ruby TDD リファクタリング 入門 テスト駆動開発
1いいね
@k2worksさん(01月25日 19時42分の投稿)

15位: 初学者によるプログラミングMemo #19 正規表現(基本編)

Ruby 正規表現
1いいね
@Ikuy_hさん(01月24日 19時21分の投稿)

16位: 小規模チームが簡単に管理できるテスト管理アプリを個人開発しました

Ruby Heroku 個人開発 テスト管理ツール
1いいね
@shanhongkunさん(01月24日 08時42分の投稿)

17位: Rspecと Factoryの使い方

Ruby Rails RSpec FactoryGirl
1いいね
@gototakumaさん(01月23日 23時47分の投稿)

18位: 【Ruby】mapメソッドのつかいかた(+別メソッドとの組み合わせの例など)

Ruby Rails
1いいね
@4EAE_Learnerさん(01月23日 20時11分の投稿)

19位: RubyでLeetCodeを解いてみた Palindrome Number

Ruby leetcode
1いいね
@yusuke-0505さん(01月23日 14時03分の投稿)

20位: rakeタスクに引数を渡したいとき

Ruby Rails rake
1いいね
@kaobabaさん(01月23日 11時10分の投稿)

21位: 初学者によるプログラミングMemo #18 フィボナッチ数列(メモ化)

Ruby フィボナッチ数列 メモ化
1いいね
@Ikuy_hさん(01月23日 01時16分の投稿)

22位: [Ruby on Rails]データベースの作成、カラムの追加

Ruby Rails
1いいね
@sansiroさん(01月22日 19時19分の投稿)

23位: トップレベルで定義したメソッドは全クラスのプライベートメソッドとなる

Ruby
1いいね
@tsuruoka91さん(01月22日 13時44分の投稿)

24位: Rails6 のちょい足しな新機能を試す 117(puma pidfile編)

Ruby puma Rails6
1いいね
@suketaさん(01月22日 12時20分の投稿)

25位: 自動デプロイ(Capistrano)でエラー mkdir: ディレクトリ `/var/www' を作成できません: 許可がありません

Ruby Rails Capistrano AWS Rails5
1いいね
@nousiさん(01月22日 00時21分の投稿)

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

Ruby Rails
1いいね
@y-sunaさん(01月21日 17時11分の投稿)

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

Ruby Rails devise grape devise_token_auth
1いいね
@shimizu-nowhereさん(01月21日 16時50分の投稿)

28位: Rubyでトランプゲームを作ってみた #1

Ruby
1いいね
@okiku-sttさん(01月21日 00時38分の投稿)

29位: 秀丸からRubyを呼び出して、選択範囲を加工する

Ruby 秀丸エディタ
1いいね
@code2545Lightさん(01月20日 21時23分の投稿)

30位: Erubi とは何か

Ruby erb Erubi
1いいね
@scivolaさん(01月20日 21時07分の投稿)

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

attr_accessorに関して

はじめに

Ruby初心者ですが、attr_accsessorの内容が最初イマイチ理解できず。
徐々に理解が出来たので纏めてみました!

※「この表現が変だよ」などあればコメントお願いします。

attr_accessorとは?

@nameなどのインスタンス変数をクラス外から呼び出し、書き込みを定義するメソッド。

attr_reader, attr_writerとの違い

attr_reader = 呼び出し専用
attr_writer = 書き込み専用

attr_accessor = 両方の機能を兼ね備えている

インスタンス変数とは

インスタンスの中で値を保持する為の変数。

例)
user1 = User.new
Userクラスをnewすることでuser1というインスタンスが作られる。
クラスの中で定義されている「@変数名」がインスタンス変数となる。

使い方

インスタンス変数の内容を、呼び出しと書き込みの両方を行いたい場合はattr_accessorを選択する。

使用例

class User
  attr_accessor :name, :age
# attr_accessor :ここにはインスタンス変数名を入れる。(シンボルか文字列で指定)

  def initialize(name,age)
# インスタンス変数に名前と年齢が代入される
    @name = name
    @age = age
  end
end

# user1とuser2のインスタンスを作成
user1 = User.new('ルフィー', 19)
user2 = User.new('ゾロ', 21)

# attr_readerで読み出し(以下attr_readerの内容)
puts user1.name
=> ルフィー
# def name
#   @name ← ('ルフィー')
# end


# attr_readerで読み出し(以下attr_readerの内容)
puts user1.age
=> 19
# def age
#   @age ← (19)
# end


# attr_readerで読み出し(以下attr_readerの内容)
puts user2.name
=> ゾロ
# def name
#   @name ← ('ゾロ')
# end


# attr_readerで読み出し(以下attr_readerの内容)
puts user2.age
=> 21
# def age
#   @age ← (21)
# end


# attr_writerで書き出し(以下attr_writerの内容)
user1.name = 'フランキー'
# フランキーがuser1.nameに代入される

# def name=(値) ← ('フランキー'が引数で渡される)
#   @name ← (インスタンス変数@nameに'フランキー'がセットされる)
# end


# attr_writerで書き出し(以下attr_writerの内容)
user1.age = 36
# 36がuser1.ageに代入される

# def age=(値) ← (36が引数)
#   @age ← (インスタンス変数@ageに36がセットされる)
# end


# user1の内容が変更されている
puts user1.name
=> フランキー
puts user1.age
=> 36

最後に

Rubyは凄く自由な書き方が出来て面白いと感じます!
(ただ、これが大規模開発には向かないと言われるところなんでしょうか)

今後投稿していく中で、もっと分かりやすく伝えられる方法を模索して
初学者でも理解しやすい様に心がけていきたいと思います!

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

テスト駆動開発から始めるRuby入門 ~ソフトウェア開発の三種の神器を準備する~

エピソード2

初めに

この記事は テスト駆動開発から始めるRuby入門 ~2時間でTDDとリファクタリングのエッセンスを体験する~ の続編です。

自動化から始めるテスト駆動開発

エピソード1ではテスト駆動開発のゴールが 動作するきれいなコード であることを学びました。では、良いコードを書き続けるためには何が必要になるでしょうか?それはソフトウェア開発の三種の神器と呼ばれるものです。

今日のソフトウェア開発の世界において絶対になければならない3つの技術的な柱があります。
三本柱と言ったり、三種の神器と言ったりしていますが、それらは

  • バージョン管理

  • テスティング

  • 自動化

の3つです。

https://t-wada.hatenablog.jp/entry/clean-code-that-works

バージョン管理テスティング に関してはエピソード1で触れました。本エピソードでは最後の 自動化 に関しての解説と次のエピソードに備えたセットアップ作業を実施しておきたいと思います。ですがその前に バージョン管理 で1つだけ解説しておきたいことがありますのでそちらから進めて行きたいと思います。

コミットメッセージ

これまで作業の区切りにごとにレポジトリにコミットしていましたがその際に以下のような書式でメッセージを書いていました。

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

この書式は
Angularルールに従っています。具体的には、それぞれのコミットメッセージはヘッダ、ボディ、フッタで構成されています。ヘッダはタイプ、スコープ、タイトルというフォーマットで構成されています。

<タイプ>(<スコープ>): <タイトル>
<空行>
<ボディ>
<空行>
<フッタ>

ヘッダは必須です。 ヘッダのスコープは任意です。 コミットメッセージの長さは50文字までにしてください。

(そうすることでその他のGitツールと同様にGitHub上で読みやすくなります。)

コミットのタイプは次を用いて下さい。

  • feat: A new feature (新しい機能)

  • fix: A bug fix (バグ修正)

  • docs: Documentation only changes (ドキュメント変更のみ)

  • style: Changes that do not affect the meaning of the code
    (white-space, formatting, missing semi-colons, etc) (コードに影響を与えない変更)

  • refactor: A code change that neither fixes a bug nor adds a feature
    (機能追加でもバグ修正でもないコード変更)

  • perf: A code change that improves performance (パフォーマンスを改善するコード変更)

  • test: Adding missing or correcting existing tests
    (存在しないテストの追加、または既存のテストの修正)

  • chore: Changes to the build process or auxiliary tools and libraries
    such as documentation generation
    (ドキュメント生成のような、補助ツールやライブラリやビルドプロセスの変更)

コミットメッセージにつけるプリフィックスに関しては 【今日からできる】コミットメッセージに 「プレフィックス」をつけるだけで、開発効率が上がった話を参照ください。

パッケージマネージャ

では 自動化 の準備に入りたいのですがそのためにはいくつかの外部プログラムを利用する必要があります。そのためのツールが RubyGems です。

RubyGemsとは、Rubyで記述されたサードパーティ製のライブラリを管理するためのツールで、RubyGemsで扱うライブラリをgemパッケージと呼びます。

— かんたんRuby

RubyGems はすでに何度か使っています。例えばエピソード1の初めの minitest-reporters
のインストールなどです。

$ gem install minitest-reporters

では、これからもこのようにして必要な外部プログラムを一つ一つインストールしていくのでしょうか?また、開発用マシンを変えた時にも同じことを繰り返さないといけないのでしょうか?面倒ですよね。そのような面倒なことをしないで済む仕組みがRubyには用意されています。それが Bundler です。

Bundlerとは、作成したアプリケーションがどのgemパッケージに依存しているか、そしてインストールしているバージョンはいくつかという情報を管理するためのgemパッケージです。

— かんたんRuby

Bundler をインストールしてgemパッケージを束ねましょう。

$ gem install bundler
$ bundle init

Gemfile が作成されます。

# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

# gem "rails"

# gem "rails" の部分を以下の様に書き換えます。

# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem 'rubocop', require: false

書き換えたら bundle install でgemパッケージをインストールします。

$ bundle install
Fetching gem metadata from https://rubygems.org/....................
Resolving dependencies...
Using ast 2.4.0
Using bundler 2.1.4
Using jaro_winkler 1.5.4
Using parallel 1.19.1
Fetching parser 2.7.0.2
Installing parser 2.7.0.2
Using rainbow 3.0.0
Using ruby-progressbar 1.10.1
Fetching unicode-display_width 1.6.1
Installing unicode-display_width 1.6.1
Fetching rubocop 0.79.0
Installing rubocop 0.79.0
Bundle complete! 1 Gemfile dependency, 9 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

これで次の準備ができました。

静的コード解析

良いコードを書き続けるためにはコードの品質を維持していく必要があります。エピソード1では テスト駆動開発 によりプログラムを動かしながら品質の改善していきました。出来上がったコードに対する品質チェックの方法として 静的コード解析 があります。Ruby用 静的コード解析 ツールRuboCop を使って確認してみましょう。プログラムは先程 Bundler を使ってインストールしたので以下のコマンドを実行します。

 $ rubocop
Inspecting 5 files
CCCWW

Offenses:

Gemfile:3:8: C: Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.
source "https://rubygems.org"
       ^^^^^^^^^^^^^^^^^^^^^^
Gemfile:5:21: C: Layout/SpaceInsideBlockBraces: Space between { and | missing.
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
                    ^^
...

なにかいろいろ出てきましたね。RuboCopの詳細に関しては RuboCop is 何?を参照ください。--lint オプションをつけて実施してみましょう。

$ rubocop --lint
Inspecting 5 files
...W.

Offenses:

test/fizz_buzz_test.rb:109:7: : Parenthesize the param %w[2 4 13 3 1 10].sort { |a, b| a.to_i <=> b.to_i } to make sure that the block will be associated with the %w[2 4 13 3 1 10].sort method call.
      assert_equal %w[1 2 3 4 10 13], ...
      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
test/fizz_buzz_test.rb:111:7: W: Lint/AmbiguousBlockAssociation: Parenthesize the param %w[2 4 13 3 1 10].sort { |b, a| a.to_i <=> b.to_i } to make sure that the block will be associated with the %w[2 4 13 3 1 10].sort method call.
      assert_equal %w[13 10 4 3 2 1], ...
      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

5 files inspected, 2 offenses detected

また何やら出てきましたね。 W:Lint/AmbiguousBlockAssociationのメッセージを調べたところ、fizz_buzz_test.rb の以下の学習用テストコードは書き方がよろしくないようですね。

...
      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_指定した評価式で並び変えた配列を返す
      result1 = %w[2 4 13 3 1 10].sort
      result2 = %w[2 4 13 3 1 10].sort { |a, b| a.to_i <=> b.to_i }
      result3 = %w[2 4 13 3 1 10].sort { |b, a| a.to_i <=> b.to_i }

      assert_equal %w[1 10 13 2 3 4], result1
      assert_equal %w[1 2 3 4 10 13], result2
      assert_equal %w[13 10 4 3 2 1], result3
    end
...

再度確認します。チェックは通りましたね。

$ rubocop --lint
Inspecting 5 files
.....

5 files inspected, no offenses detected

テストも実行して壊れていないかも確認しておきます。

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

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

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

いちいち調べるのも手間なので自動で修正できるところは修正してもらいましょう。

$ rubocop --auto-correct

再度確認します。

 $ rubocop
Inspecting 5 files
...CC

Offenses:

test/fizz_buzz_test.rb:15:11: C: Naming/MethodName: Use snake_case for method names.
      def test_3を渡したら文字列Fizzを返す
          ^^^^^^^^^^^^^^^^^^^^^
...

まだ、自動修正できなかった部分があるようですね。この部分はチェック対象から外すことにしましょう。

$ rubocop --auto-gen-config
Added inheritance from `.rubocop_todo.yml` in `.rubocop.yml`.
Phase 1 of 2: run Layout/LineLength cop
Inspecting 5 files
.....

5 files inspected, no offenses detected
Created .rubocop_todo.yml.
Phase 2 of 2: run all cops
Inspecting 5 files
.C.CW

5 files inspected, 110 offenses detected
Created .rubocop_todo.yml.

生成された .rubocop_todo.yml の以下の部分を変更します。

...
# Offense count: 32
# Configuration parameters: IgnoredPatterns.
# SupportedStyles: snake_case, camelCase
Naming/MethodName:
  EnforcedStyle: snake_case
  Exclude:
    - 'test/fizz_buzz_test.rb'
...

再度チェックを実行します。

$ rubocop
Inspecting 5 files
.....

5 files inspected, no offenses detected

セットアップができたのでここでコミットしておきましょう。

$ git add .
$ git commit -m 'chore: 静的コード解析セットアップ'

コードフォーマッタ

良いコードであるためにはフォーマットも大切な要素です。

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

— リーダブルコード

Rubyにはいくつかフォーマットアプリケーションはあるのですがここは RuboCop の機能を使って実現することにしましょう。以下のコードのフォーマットをわざと崩してみます。

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

スタイルオプションをつけてチェックしてみます。

$ rubocop --only Layout
Inspecting 5 files
.C...

Offenses:

lib/fizz_buzz.rb:7:3: C: Layout/IndentationWidth: Use 2 (not 8) spaces for indentation.
          isFizz = number.modulo(3).zero?
  ^^^^^^^^
lib/fizz_buzz.rb:8:5: C: Layout/IndentationConsistency: Inconsistent indentation detected.
    isBuzz = number.modulo(5).zero?
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
lib/fizz_buzz.rb:10:5: C: Layout/IndentationConsistency: Inconsistent indentation detected.
    return 'FizzBuzz' if isFizz && isBuzz
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
lib/fizz_buzz.rb:11:5: C: Layout/IndentationConsistency: Inconsistent indentation detected.
    return 'Fizz' if isFizz
    ^^^^^^^^^^^^^^^^^^^^^^^
lib/fizz_buzz.rb:12:5: C: Layout/IndentationConsistency: Inconsistent indentation detected.
    return 'Buzz' if isBuzz
    ^^^^^^^^^^^^^^^^^^^^^^^
lib/fizz_buzz.rb:14:5: C: Layout/IndentationConsistency: Inconsistent indentation detected.
    number.to_s
    ^^^^^^^^^^^

5 files inspected, 6 offenses detected

編集した部分が Use 2 (not 8) spaces for indentation. と指摘されています。--fix-layout オプションで自動保存しておきましょう。

$ rubocop --fix-layout
Inspecting 5 files
.C...

Offenses:

lib/fizz_buzz.rb:7:3: C: [Corrected] Layout/IndentationWidth: Use 2 (not 8) spaces for indentation.
          isFizz = number.modulo(3).zero?
  ^^^^^^^^
lib/fizz_buzz.rb:8:5: C: [Corrected] Layout/IndentationConsistency: Inconsistent indentation detected.
    isBuzz = number.modulo(5).zero?
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
lib/fizz_buzz.rb:8:11: C: [Corrected] Layout/IndentationConsistency: Inconsistent indentation detected.
          isBuzz = number.modulo(5).zero?
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
lib/fizz_buzz.rb:10:5: C: [Corrected] Layout/IndentationConsistency: Inconsistent indentation detected.
    return 'FizzBuzz' if isFizz && isBuzz
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
lib/fizz_buzz.rb:10:11: C: [Corrected] Layout/IndentationConsistency: Inconsistent indentation detected.
          return 'FizzBuzz' if isFizz && isBuzz
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
lib/fizz_buzz.rb:11:5: C: [Corrected] Layout/IndentationConsistency: Inconsistent indentation detected.
    return 'Fizz' if isFizz
    ^^^^^^^^^^^^^^^^^^^^^^^
lib/fizz_buzz.rb:11:11: C: [Corrected] Layout/IndentationConsistency: Inconsistent indentation detected.
          return 'Fizz' if isFizz
          ^^^^^^^^^^^^^^^^^^^^^^^
lib/fizz_buzz.rb:12:5: C: [Corrected] Layout/IndentationConsistency: Inconsistent indentation detected.
    return 'Buzz' if isBuzz
    ^^^^^^^^^^^^^^^^^^^^^^^
lib/fizz_buzz.rb:12:11: C: [Corrected] Layout/IndentationConsistency: Inconsistent indentation detected.
          return 'Buzz' if isBuzz
          ^^^^^^^^^^^^^^^^^^^^^^^
lib/fizz_buzz.rb:14:5: C: [Corrected] Layout/IndentationConsistency: Inconsistent indentation detected.
    number.to_s
    ^^^^^^^^^^^
lib/fizz_buzz.rb:14:11: C: [Corrected] Layout/IndentationConsistency: Inconsistent indentation detected.
          number.to_s
          ^^^^^^^^^^^

5 files inspected, 11 offenses detected, 11 offenses corrected
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
$ rubocop --only Layout
Inspecting 5 files
.....

5 files inspected, no offenses detected

フォーマットが修正されたことが確認できましたね。ちなみに --auto-correct オプションでもフォーマットをしてくれるので通常はこちらのオプションで問題ないと思います。

コードカバレッジ

静的コードコード解析による品質の確認はできました。では動的なテストに関してはどうでしょうか? コードカバレッジ を確認する必要あります。

コード網羅率(コードもうらりつ、英: Code coverage
)コードカバレッジは、ソフトウェアテストで用いられる尺度の1つである。プログラムのソースコードがテストされた割合を意味する。この場合のテストはコードを見ながら行うもので、ホワイトボックステストに分類される。

— ウィキペディア

Ruby用 コードカバレッジ 検出プログラムとして SimpleCovを使います。Gemfileに追加して Bundler でインストールをしましょう。

# frozen_string_literal: true

source 'https://rubygems.org'

git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }

gem 'minitest'
gem 'minitest-reporters'
gem 'rubocop', require: false
gem 'simplecov', require: false, group: :test
$ bundle install
Fetching gem metadata from https://rubygems.org/..................
Resolving dependencies...
Fetching ansi 1.5.0
Installing ansi 1.5.0
Using ast 2.4.0
Fetching builder 3.2.4
Installing builder 3.2.4
Using bundler 2.1.4
Using docile 1.3.2
Using jaro_winkler 1.5.4
Using json 2.3.0
Fetching minitest 5.14.0
Installing minitest 5.14.0
Using ruby-progressbar 1.10.1
Fetching minitest-reporters 1.4.2
Installing minitest-reporters 1.4.2
Using parallel 1.19.1
Using parser 2.7.0.2
Using rainbow 3.0.0
Using unicode-display_width 1.6.1
Using rubocop 0.79.0
Using simplecov-html 0.10.2
Using simplecov 0.17.1
Bundle complete! 4 Gemfile dependencies, 17 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

サイトの説明に従ってテストコードの先頭に以下のコードを追加します。

# frozen_string_literal: true
require 'simplecov'
SimpleCov.start
require 'minitest/reporters'
Minitest::Reporters.use!
require 'minitest/autorun'
require './lib/fizz_buzz'
...

テストを実施します。

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

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

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

テスト実行後に coverage というフォルダが作成されます。その中の index.html を開くとカバレッジ状況を確認できます。セットアップが完了したらコミットしておきましょう。

$ git add .
$ git commit -m 'chore: コードカバレッジセットアップ'

タスクランナー

ここまででテストの実行、静的コード解析、コードフォーマット、コードカバレッジを実施することができるようになりました。でもコマンドを実行するのにそれぞれコマンドを覚えておくのは面倒ですよね。例えばテストの実行は

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

  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

Rubyの タスクランナーRake です。

RakeはRubyにおけるタスクランナーです。rakeコマンドと起点となるRakefileというタスクを記述するファイルを用意することで、タスクの実行や登録されたタスクの一覧表示を行えます。

— かんたんRuby

早速、テストタスクから作成しましょう。まず Rakefile を作ります。Mac/Linuxでは touch
コマンドでファイルを作れます。Windowsの場合は手作業で追加してください。

$ touch Rakefile
require 'rake/testtask'

task default: [:test]

Rake::TestTask.new do |test|
  test.test_files = Dir['./test/fizz_buzz_test.rb']
  test.verbose = true
end

タスクが登録されたか確認してみましょう。

$ rake -T
rake test  # Run tests

タスクが登録されたことが確認できたのでタスクを実行します。

$ rake test
/Users/k2works/.rbenv/versions/2.5.5/bin/ruby -w -I"lib" -I"/Users/k2works/.rbenv/versions/2.5.5/lib/ruby/gems/2.5.0/gems/rake-13.0.1/lib" "/Users/k2works/.rbenv/versions/2.5.5/lib/ruby/gems/2.5.0/gems/rake-13.0.1/lib/rake/rake_test_loader.rb" "./test/fizz_buzz_test.rb"
/Users/k2works/Projects/hiroshima-arc/tdd_rb/docs/src/article/code/test/fizz_buzz_test.rb:79: warning: method redefined; discarding old test_特定の条件を満たす要素だけを配列に入れて返す
/Users/k2works/Projects/hiroshima-arc/tdd_rb/docs/src/article/code/test/fizz_buzz_test.rb:74: warning: previous definition of test_特定の条件を満たす要素だけを配列に入れて返す was here
/Users/k2works/Projects/hiroshima-arc/tdd_rb/docs/src/article/code/test/fizz_buzz_test.rb:94: warning: method redefined; discarding old test_新しい要素の配列を返す
/Users/k2works/Projects/hiroshima-arc/tdd_rb/docs/src/article/code/test/fizz_buzz_test.rb:89: warning: previous definition of test_新しい要素の配列を返す was here
/Users/k2works/Projects/hiroshima-arc/tdd_rb/docs/src/article/code/test/fizz_buzz_test.rb:104: warning: method redefined; discarding old test_配列の中から条件に一致する要素を取得する
/Users/k2works/Projects/hiroshima-arc/tdd_rb/docs/src/article/code/test/fizz_buzz_test.rb:99: warning: previous definition of test_配列の中から条件に一致する要素を取得する was here
/Users/k2works/Projects/hiroshima-arc/tdd_rb/docs/src/article/code/test/fizz_buzz_test.rb:138: warning: method redefined; discarding old test_畳み込み演算を行う
/Users/k2works/Projects/hiroshima-arc/tdd_rb/docs/src/article/code/test/fizz_buzz_test.rb:133: warning: previous definition of test_畳み込み演算を行う was here
Started with run options --seed 5886

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

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

テストは実施されたのですが警告メッセージが表示されるようになりました。メッセージの内容としては 学習用テスト のテストメソッド名が重複していることが理由のようです。せっかくなので修正しておきましょう。

class FizzBuzzTest < Minitest::Test
  describe 'FizzBuzz' do
  ...
  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_指定した評価式で並び変えた配列を返す
      result1 = %w[2 4 13 3 1 10].sort
      result2 = %w[2 4 13 3 1 10].sort { |a, b| a.to_i <=> b.to_i }
      result3 = %w[2 4 13 3 1 10].sort { |b, a| a.to_i <=> b.to_i }

      assert_equal %w[1 10 13 2 3 4], result1
      assert_equal %w[1 2 3 4 10 13], result2
      assert_equal %w[13 10 4 3 2 1], result3
    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

メソッド名の変更 を適用してリファクタリングしましょう。

class FizzBuzzTest < Minitest::Test
  describe 'FizzBuzz' do
  ...
  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_selectメソッドで特定の条件を満たす要素だけを配列に入れて返す
      result = [1.1, 2, 3.3, 4].select(&:integer?)
      assert_equal [2, 4], result
    end

    def test_find_allメソッドで特定の条件を満たす要素だけを配列に入れて返す
      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_mapメソッドで新しい要素の配列を返す
      result = %w[apple orange pineapple strawberry].map(&:size)
      assert_equal [5, 6, 9, 10], result
    end

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

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

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

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

      assert_equal %w[1 10 13 2 3 4], result1
      assert_equal %w[1 2 3 4 10 13], result2
      assert_equal %w[13 10 4 3 2 1], result3
    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_injectメソッドで畳み込み演算を行う
      result = [1, 2, 3, 4, 5].inject(0) { |total, n| total + n }
      assert_equal 15, result
    end

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

テストを再実行して警告メッセージが消えたこと確認します。

$ rake test
/home/gitpod/.rvm/rubies/ruby-2.6.3/bin/ruby -w -I"lib" -I"/home/gitpod/.rvm/rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/rake-12.3.2/lib" "/home/gitpod/.rvm/rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/rake-12.3.2/lib/rake/rake_test_loader.rb" "./test/fizz_buzz_test.rb"
Started with run options --seed 10674

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

Finished in 0.00396s
24 tests, 26 assertions, 0 failures, 0 errors, 0 skips

テストタスクが実行されたことが確認できたので引き続き静的コードの解析タスクを追加します。こちらも開発元がタスクを用意しているのでそちらを使うことにします。

require 'rake/testtask'
require 'rubocop/rake_task'
RuboCop::RakeTask.new

task default: [:test]

Rake::TestTask.new do |test|
  test.test_files = Dir['./test/fizz_buzz_test.rb']
  test.verbose = true
end

タスクが登録されたことを確認します。

$ rake -T
rake rubocop               # Run RuboCop
rake rubocop:auto_correct  # Auto-correct RuboCop offenses
rake test                  # Run tests

続いてタスクを実行してみましょう。

$ rake rubocop
Running RuboCop...
Inspecting 5 files
.C..C

Offenses:

Rakefile:1:1: C: Style/FrozenStringLiteralComment: Missing magic comment # frozen_string_literal: true.
require 'rake/testtask'
^
Rakefile:10:4: C: Layout/TrailingEmptyLines: Final newline missing.
end

test/fizz_buzz_test.rb:2:1: C: Layout/EmptyLineAfterMagicComment: Add an empty line after magic comments.
require 'simplecov'
^
test/fizz_buzz_test.rb:148:6: C: Layout/TrailingWhitespace: Trailing whitespace detected.
  end
     ^^

5 files inspected,

いろいろ出てきましたので自動修正しましょう。

$ rake rubocop:auto_correct
Running RuboCop...
Inspecting 5 files
.C..C

Offenses:

Rakefile:1:1: C: [Corrected] Style/FrozenStringLiteralComment: Missing magic comment # frozen_string_literal: true.
require 'rake/testtask'
^
Rakefile:2:1: C: [Corrected] Layout/EmptyLineAfterMagicComment: Add an empty line after magic comments.
require 'rake/testtask'
^
Rakefile:10:4: C: [Corrected] Layout/TrailingEmptyLines: Final newline missing.
end

test/fizz_buzz_test.rb:2:1: C: [Corrected] Layout/EmptyLineAfterMagicComment: Add an empty line after magic comments.
require 'simplecov'
^
test/fizz_buzz_test.rb:148:6: C: [Corrected] Layout/TrailingWhitespace: Trailing whitespace detected.
  end
     ^^

5 files inspected, 5 offenses detected, 5 offenses corrected
$ rake rubocop
Running RuboCop...
Inspecting 5 files
.....

5 files inspected, no offenses detected

うまく修正されたようですね。後、フォーマットコマンドもタスクとして追加しておきましょう。こちらは開発元が用意していないタスクなので以下のように追加します。

# frozen_string_literal: true

require 'rake/testtask'
require 'rubocop/rake_task'
RuboCop::RakeTask.new

task default: [:test]

Rake::TestTask.new do |test|
  test.test_files = Dir['./test/fizz_buzz_test.rb']
  test.verbose = true
end

desc "Run Format"
task :format do
  sh "rubocop --fix-layout"
end
$ rake -T
rake format                # Run Format
rake rubocop               # Run RuboCop
rake rubocop:auto_correct  # Auto-correct RuboCop offenses
rake test                  # Run tests
$ rake format
rubocop --fix-layout
Inspecting 5 files
.C...

Offenses:

Rakefile:17:4: C: [Corrected] Layout/TrailingEmptyLines: Final newline missing.
end


5 files inspected, 1 offense detected, 1 offense corrected

フォーマットは rake rubocop:auto_correct で一緒にやってくれるので特に必要は無いのですがプログラムの開発元が提供していないタスクを作りたい場合はこのように追加します。セットアップができたのでコミットしておきましょう。

$ git add .
$ git commit -m 'chore: タスクランナーセットアップ'

タスクの自動化

良いコードを書くためのタスクをまとめることができました。でも、どうせなら自動で実行できるようにしたいですよね。タスクを自動実行するためのgemを追加します。GuardとそのプラグインのGuard::Shell Guard::Minitest をインストールします。それぞれの詳細は以下を参照してください。

# frozen_string_literal: true

source 'https://rubygems.org'

git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }

gem 'guard'
gem 'guard-minitest'
gem 'guard-shell'
gem 'minitest'
gem 'minitest-reporters'
gem 'rake'
gem 'rubocop', require: false
gem 'simplecov', require: false, group: :test

bundle installbundle に省略できます。

$ bundle
$ guard init

Guardfile が生成されるので以下の内容に変更します。

# frozen_string_literal: true

# Add files and commands to this file, like the example:
#   watch(%r{file/path}) { `command(s)` }
#
guard :shell do
  watch(/(.*).rb/) { |_m| `rake rubocop:auto_correct` }
  watch(%r{lib/(.*).rb}) { |_m| `rake test` }
end

guard :minitest do
  # with Minitest::Unit
  watch(%r{test\/*.rb})
end

guard が起動するか確認して一旦終了します。

$ guard start
10:18:32 - INFO - Guard::Minitest 2.4.6 is running, with Minitest::Unit 5.14.0!
10:18:32 - INFO - Running: all tests
Coverage report generated for MiniTest to /workspace/tdd_rb/coverage. 4 / 11 LOC (36.36%
) covered.
Started with run options --guard --seed 53002

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

Finished in 0.00696s
24 tests, 26 assertions, 0 failures, 0 errors, 0 skips

10:18:33 - INFO - Guard is now watching at '/workspace/tdd_rb'
[1] guard(main)>

続いて Rakefile にguardタスクを追加します。あと、guardタスクをデフォルトにして rake を実行すると呼び出されるようにしておきます。

# frozen_string_literal: true

require 'rake/testtask'
require 'rubocop/rake_task'
RuboCop::RakeTask.new

task default: [:guard]

Rake::TestTask.new do |test|
  test.test_files = Dir['./test/fizz_buzz_test.rb']
  test.verbose = true
end

desc 'Run Format'
task :format do
  sh 'rubocop --fix-layout'
end

desc 'Run Guard'
task :guard do
  sh 'guard start'
end

自動実行タスクを起動しましょう。

$ rake
guard start
10:18:58 - INFO - Guard::Minitest 2.4.6 is running, with Minitest::Unit 5.14.0!
10:18:58 - INFO - Running: all tests
Coverage report generated for MiniTest to /workspace/tdd_rb/coverage. 4 / 11 LOC (36.36%
) covered.
Started with run options --guard --seed 2641

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

Finished in 0.00372s
24 tests, 26 assertions, 0 failures, 0 errors, 0 skips

10:18:59 - INFO - Guard is now watching at '/workspace/tdd_rb'
[1] guard(main)>

起動したら fizz_buzz.rb を編集してテストが自動実行されるか確認しましょう。ここでは3と5で割り切れる場合は FizBuzzBuzz を返すように変更しています。

class FizzBuzz
  MAX_NUMBER = 100

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

    return 'FizzBuzzBuzz' 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
$ rake
guard start
10:21:16 - INFO - Guard::Minitest 2.4.6 is running, with Minitest::Unit 5.14.0!
10:21:16 - INFO - Running: all tests
Coverage report generated for MiniTest, Unit Tests to /workspace/tdd_rb/coverage. 11 / 1
1 LOC (100.0%) covered.
Started with run options --guard --seed 8830

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

Finished in 0.00718s
24 tests, 26 assertions, 0 failures, 0 errors, 0 skips

10:21:17 - INFO - Guard is now watching at '/workspace/tdd_rb'
/home/gitpod/.rvm/rubies/ruby-2.6.3/bin/ruby -w -I"lib" -I"/workspace/.rvm/gems/rake-13.
0.1/lib" "/workspace/.rvm/gems/rake-13.0.1/lib/rake/rake_test_loader.rb" "./test/fizz_bu
zz_test.rb"
SimpleCov failed with exit 1rake aborted!
Command failed with status (1): [ruby -w -I"lib" -I"/workspace/.rvm/gems/rake-13.0.1/lib
" "/workspace/.rvm/gems/rake-13.0.1/lib/rake/rake_test_loader.rb" "./test/fizz_buzz_test
.rb" ]
/workspace/.rvm/gems/rake-13.0.1/exe/rake:27:in `<top (required)>'
/workspace/.rvm/bin/ruby_executable_hooks:24:in `eval'
/workspace/.rvm/bin/ruby_executable_hooks:24:in `<main>'
Tasks: TOP => test
(See full trace by running task with --trace)
Running RuboCop...
Inspecting 6 files
......

6 files inspected, no offenses detected
Started with run options --seed 20590


 FAIL["test_15を渡したら文字列FizzBuzzを返す", #<Minitest::Reporters::Suite:0x000055d0ed2b22a8 @name="FizzBuzz::三と五の倍数の場合">, 0.002640306978719309]
 test_15を渡したら文字列FizzBuzzを返す#FizzBuzz::三と五の倍数の場合 (0.00s)
        Expected: "FizzBuzz"
          Actual: "FizzBuzzBuzz"
        /workspace/tdd_rb/test/fizz_buzz_test.rb:30:in `test_15を渡したら文字列FizzBuzzを返す'

 FAIL["test_配列の14番目は文字列のFizzBuzzを返す", #<Minitest::Reporters::Suite:0x000055d0ed2c0f60 @name="FizzBuzz::1から100までのFizzBuzzの配列を返す">, 0.0035449959977995604]
 test_配列の14番目は文字列のFizzBuzzを返す#FizzBuzz::1から100までのFizzBuzzの配列を返す (0.00s)
        Expected: "FizzBuzz"
          Actual: "FizzBuzzBuzz"
        /workspace/tdd_rb/test/fizz_buzz_test.rb:66:in `test_配列の14番目は文字列のFizzBuzzを返す'

============================================================================|

Finished in 0.00434s
24 tests, 26 assertions, 2 failures, 0 errors, 0 skips
Running RuboCop...
Inspecting 6 files
......

6 files inspected, no offenses detected
Running RuboCop...
Inspecting 6 files
......

6 files inspected, no offenses detected
Running RuboCop...
Inspecting 6 files
......

6 files inspected, no offenses detected
Running RuboCop...
Inspecting 6 files
......

6 files inspected, no offenses detected
[1] guard(main)>

変更を感知してテストが実行されるた結果失敗していましました。コードを元に戻してテストをパスするようにしておきましょう。テストがパスすることが確認できたらコミットしておきましょう。このときターミナルでは guard が動いているので別ターミナルを開いてコミットを実施すると良いでしょう。

$ git add .
$ git commit -m 'chore: タスクの自動化'

これで ソフトウェア開発の三種の神器 の最後のアイテムの準備ができました。次回の開発からは最初にコマンドラインで rake を実行すれば良いコードを書くためのタスクを自動でやってくるようになるのでコードを書くことに集中できるようになりました。では、次のエピソードに進むとしましょう。

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

Leetcode 938: Range Sum of BST

#Given the root node of a binary search tree, return the sum of values of all nodes with value between L and R (inclusive).
#The binary search tree is guaranteed to have unique values.
#The number of nodes in the tree is at most 10000.
#The final answer is guaranteed to be less than 2^31.

require 'test/unit'

# https://leetcode.com/problems/range-sum-of-bst/

class RangeSumOfBst < Test::Unit::TestCase

  def test_range_sum_bst
    #  TODO: Add test
  end


  def range_sum_bst(root, l, r)
    result = 0
    unless root.nil?
      puts result

      result += root.val if root.val >= l && root.val <= r
      result += range_sum_bst(root.right, l, r) unless root.right.nil?
      result += range_sum_bst(root.left, l, r) unless root.left.nil?
    end
    result
  end

end

class TreeNode
  attr_accessor :val, :left, :right

  def initialize(val)
    @val = val
    @left, @right = nil, nil
  end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Leetcode 617: Merge Two Binary Trees

require 'test/unit'
require 'pry'
class MergeTwoBinaryTrees < Test::Unit::TestCase

  # https://leetcode.com/problems/merge-two-binary-trees/


  # Definition for a binary tree node.
  # class TreeNode
  #     attr_accessor :val, :left, :right
  #     def initialize(val)
  #         @val = val
  #         @left, @right = nil, nil
  #     end
  # end

  # @param {TreeNode} t1
  # @param {TreeNode} t2
  # @return {TreeNode}
  #
  def test_calc
    #TODO: Add test case
  end

  def merge_trees(t1, t2)
    if t1.nil?
      return t2
    end
    if t2.nil?
      return t1
    end

    t1.val = t1.val.to_i + t2.val.to_i
    t1.left = merge_trees(t1.left, t2.left)
    t1.right = merge_trees(t1.right, t2.right)
    return t1

  end
end

class TreeNode

  attr_accessor :val, :left, :right

  def initialize(val)
    @val = val
    @left, @right = nil, nil
  end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Leetcode 1108: Defanging an IP Address

require 'test/unit'
require 'pry'
class DefanginAnIpAddress < Test::Unit::TestCase

  # https://leetcode.com/problems/defanging-an-ip-address/
  # Qiita:

  def test_defang_i_paddr
    input = "1.1.1.1"
    output = "1[.]1[.]1[.]1"
    assert_equal(output, defang_i_paddr(input))

    input = "255.100.50.0"
    output = "255[.]100[.]50[.]0"
    assert_equal(output, defang_i_paddr(input))
  end

  def defang_i_paddr(address)
    address.gsub('.', '[.]')
  end

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

Leetcode: Most Common Word

require 'test/unit'
require 'pry'
class MostCommonWord < Test::Unit::TestCase

  #https://leetcode.com/problems/most-common-word/submissions/

  def test_calc
    paragraph = "Bob hit a ball, the hit BALL flew far after it was hit."
    banned = ["hit"]
    output = "ball"
    assert_equal(output, solution(paragraph, banned))

    paragraph = "a, a, a, a, b,b,b,c, c"
    banned = ["a"]
    output = "b"
    assert_equal(output, solution(paragraph, banned))
  end

  def solution(paragraph, banned)
    # change the sentence to lowercase
    # remove , and .
    # iterate over each words, and count the number of same words
    # remove the banned output from the list
    # sort by the highest number
    # return the 1st element

    paragraph = paragraph.downcase
    paragraph = paragraph.gsub(',', ' ').gsub(',', ' ')
    words = paragraph.split(/\W+/)
    list = {}
    words.each do |word|
      #binding.pry
      if list[word]
        list[word] += 1
      else
        list[word] ||= 0
      end
    end
    list

    banned.each do |word|
      list.delete(word)
    end
    list = list.sort { |a, b| b[1] <=> a[1] }
    list.first[0]
  end

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

Leetcode: Construct String from Binary Tree

require 'test/unit'
require 'pry'
class ConstructStringFromBinaryTree < Test::Unit::TestCase

  #https://leetcode.com/problems/construct-string-from-binary-tree/

  def test_calc
    # You need to construct a string consists of parenthesis and integers from a binary tree with the preorder traversing way.
    # The null node needs to be represented by empty parenthesis pair "()".
    # And you need to omit all the empty parenthesis pairs that
    # don't affect the one-to-one mapping relationship between the string and the original binary tree.
    #
    # Input: Binary tree: [1,2,3,4]
    # Output: "1(2(4))(3)"
    # Explanation: Originallay it needs to be "1(2(4)())(3()())",
    # but you need to omit all the unnecessary empty parenthesis pairs.
    # And it will be "1(2(4))(3)".
    #
    #

    input = [1, 2, 3, 4]
    tree = TreeNode.new(1)
    tree.left = TreeNode.new(2)
    tree.right = TreeNode.new(3)
    tree.left.left = TreeNode.new(4)
    tree
    output = "1(2(4))(3)"

    assert_equal(output, tree2str(tree))

    input = [1, 2, 3, nil, 4]
    output = "1(2()(4))(3)"
    tree = TreeNode.new(1)
    tree.left = TreeNode.new(2)
    tree.right = TreeNode.new(3)
    tree.left.left = TreeNode.new(nil)
    tree.left.right = TreeNode.new(4)

    assert_equal(output, tree2str(tree))


    tree= TreeNode.new(1)
    output = "1"
    assert_equal(output, tree2str(tree))

    tree= TreeNode.new(nil)
    output = ""
    assert_equal(output, tree2str(tree))
  end

  def tree2str(t)
    @str = ''
    solution(t)
  end

  def solution(t)
    if t == nil
      return ""
    end

    @str += t.val.to_s

    # If leef node, then return
    if t.left == nil && t.right == nil
      return @str
    end

    # for left subtree
    @str += '('
    solution(t.left)
    @str += ')'

    # Only if right child is present to avoid extra parenthesis
    if t.right != nil
      @str += '('
      solution(t.right)
      @str += ')'
    end
    @str
  end

end

class TreeNode
  attr_accessor :val, :left, :right

  def initialize(val)
    @val = val
    @left, @right = nil, nil
  end
end

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

Leetcode: Decompress Run Length Encoded List

require 'test/unit'
require 'pry'
class DecompressRunLengthEncodedList < Test::Unit::TestCase

  # Qiita: posted

  #https://leetcode.com/problems/decompress-run-length-encoded-list/

  def test_calc
    #We are given a list nums of integers representing a list compressed with run-length encoding.
    #Consider each adjacent pair of elements [a, b] = [nums[2 * i], nums[2 * i + 1]] (with i >= 0).For each such pair, there are a elements with value b in the decompressed list.
    #Return the decompressed list.

    # Input: nums = [1,2,3,4]
    # Output: [2,4,4,4]
    # Explanation: The first pair [1,2] means we have freq = 1 and val = 2 so we generate the array [2].
    # The second pair [3,4] means we have freq = 3 and val = 4 so we generate [4,4,4].
    # At the end the concatenation [2] + [4,4,4,4] is [2,4,4,4].

    input = [1, 2, 3, 4]
    output = [2, 4, 4, 4]
    assert_equal(output, decompress_rl_elist(input))
  end

  def decompress_rl_elist(nums)
    items = []
    nums.each_slice(2) do |pair|
      count = pair[0]
      item = pair[1]
      count.times do
        items << item
      end
    end
    items.flatten
  end

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

Docker-composeでbundle installしてるのにgemの内容反映されないんですけど

注意事項

完全に個人用メモです。公式情報でもなんでもありません。ググってこの記事に来てしまった方はお気をつけください。違っていたら即直しますのでガンガンご指摘ください。(個人用メモなので追加もしていきます)

そもそもbundle installしたらサーバー再起動しろや

なので、gemfile編集してbundle installしたら以下のコマンド打てば良い(環境によるけど)

docker-compose restart・・・コンテナ再起動

そしたらGemの内容が反映されます。

docker勉強不足だって、はっきりわかんだね。

イメージをビルドしなおしたりコンテナ削除したりしなくて良い

dockerを理解してないと以下のようなコマンドを打ち始めます

docker-compose build・・・イメージビルド

docker-compose up -d・・・コンテナ作成&起動

結果、コンテナが増えまくるわけです。(えぇ。。。)

コンテナやたら多いと思ったら原因これかよw(錯乱)

まあこの方法でも解決できるんですけどね。。

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

Docker + rails + mysqlで開発環境を構築

はじめに

今回は、railsの開発環境をDocker+rails+mysqlで用意します。

構成

構成はローカルのMacにDockerを立て、webとdbコンテナをdocker-composeで管理します。

スクリーンショット 2020-01-25 10.06.34.png

開発環境

Docker 19.03.5
Ruby 2.6.5
Rails 5.2.4
MySQL 5.7.29

前提条件

Macを利用

Docker Desktopをインストール

まずは、ローカルのMacにDocker Desktopをインストールします。

https://www.docker.com/get-started

スクリーンショット 2020-01-22 0.09.31.png

「Download Desktop and Take a Tutorial」のボタンをクリックすると画面が遷移し、
アカウントがある人はログイン、ない人は「Sign Up」を押してアカウントを作成します。

スクリーンショット 2020-01-22 0.14.29.png

アカウント作成後、ログインし 「Get started with Docker Desktop」を押すと画面が遷移するので「Download Docker Desktop for Mac」を押しダウンロードします。

スクリーンショット 2020-01-22 0.16.16.png

ダウンロード完了後、インストールします。
install後、Macのターミナルにてdocker versionと打ち、バージョンが出れば無事インストールは完了です。

$ docker version
Client: Docker Engine - Community
 Version:           19.03.5
 API version:       1.40
 Go version:        go1.12.12
 Git commit:        633a0ea
 Built:             Wed Nov 13 07:22:34 2019
 OS/Arch:           darwin/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.5
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.12
  Git commit:       633a0ea
  Built:            Wed Nov 13 07:29:19 2019
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          v1.2.10
  GitCommit:        b34a5c8af56e510852c35414db4c1f4fa6172339
 runc:
  Version:          1.0.0-rc8+dev
  GitCommit:        3e425f80a8c931f88e6d94a8c831b9d5aa481657
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

web(rails)コンテナの構築

開発を行うために任意の作業ディレクトリを作成します。

$ mkdir task_app/
$ cd task_app 

Gemfileの準備

オプションでディレクトリのマウントを指定していることで、Docker環境で作成されたGemfileがローカル環境に作成されます。

$ docker run --rm -v `pwd`:/task_app -w /task_app ruby:2.6 bundle init
Unable to find image 'ruby:2.6' locally
2.6: Pulling from library/ruby
8f0fdd3eaac0: Pull complete 
d918eaefd9de: Pull complete 
43bf3e3107f5: Pull complete 
27622921edb2: Pull complete 
dcfa0aa1ae2c: Pull complete 
0e1f1dc37f65: Pull complete 
c76a82442849: Pull complete 
5161fd3df3c4: Pull complete 
Digest: sha256:f38fce2b70ba23e90d6397995bea8419b86dd3f20b73846681adb52c63c0b002
Status: Downloaded newer image for ruby:2.6
Writing new Gemfile to /task_app/Gemfile      

Gemfileがローカルに作成されますので、railsのバージョンを指定します。
利用したいバージョンに合わせて適宜修正してください。

$ vi Gemfile

# gem "rails"
gem 'rails', '~> 5.2.3'

Dockerイメージの作成

rails環境のDockerイメージを作成します。

Dockerfileの作成 → ビルド → Dockerイメージ(task_app)

※Dockerfileはないので新規作成します。

$ vi Dockerfile

FROM ruby:2.6
WORKDIR /task_app
COPY Gemfile /task_app/Gemfile
RUN bundle install

$ docker build -t task_app .

Sending build context to Docker daemon  3.072kB
Step 1/4 : FROM ruby:2.6
 ---> a161c3e3dda8
Step 2/4 : WORKDIR /task_app
 ---> Running in a6b5dec7f0ed
Removing intermediate container a6b5dec7f0ed
 ---> 16367607e3e5
Step 3/4 : COPY Gemfile /task_app/Gemfile
 ---> 99de34366eef
Step 4/4 : RUN bundle install
 ---> Running in 8839dbf87256
Fetching gem metadata from https://rubygems.org/.............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Fetching rake 13.0.1
Fetching rails 5.2.4.1
Installing rails 5.2.4.1
Bundle complete! 1 Gemfile dependency, 41 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
Post-install message from i18n:

HEADS UP! i18n 1.1 changed fallbacks to exclude default locale.
But that may break your application.

If you are upgrading your Rails application from an older version of Rails:
Please check your Rails app for 'config.i18n.fallbacks = true'.
If you're using I18n (>= 1.1.0) and Rails (< 5.2.2), this should be
'config.i18n.fallbacks = [I18n.default_locale]'.
If not, fallbacks will be broken in your app by I18n 1.1.x.
If you are starting a NEW Rails application, you can ignore this notice.

For more info see:
https://github.com/svenfuchs/i18n/releases/tag/v1.1.0

Removing intermediate container 8839dbf87256
 ---> 89e179509cba
Successfully built 89e179509cba
Successfully tagged task_app:latest

$ docker images

REPOSITORY          TAG                 IMAGE ID            CREATED              SIZE
task_app            latest              89e179509cba        About a minute ago   926MB

作成されてますね。

Railsアプリの作成

作成したDockerイメージ(task_app)を利用してrails newを実行し、Railsアプリを作成します。

$ docker run --rm -v `pwd`:/task_app task_app rails new . -B --database=mysql

       exist  
      create  README.md
      create  Rakefile
      create  .ruby-version
      create  config.ru
      create  .gitignore
    conflict  Gemfile
Overwrite /task_app/Gemfile? (enter "h" for help) [Ynaqdhm] 
       force  Gemfile
         run  git init from "."
Initialized empty Git repository in /task_app/.git/
      create  package.json
      create  app
      create  app/assets/config/manifest.js
      create  app/assets/javascripts/application.js
      create  app/assets/javascripts/cable.js
      create  app/assets/stylesheets/application.css
      create  app/channels/application_cable/channel.rb
      create  tmp/storage
      create  tmp/storage/.keep
      remove  config/initializers/cors.rb
      remove  config/initializers/new_framework_defaults_5_2.rb

Railsからデータベースへ接続するためのdatabase.ymlファイルが作成されますので修正します。

$ vi config/database.yml 

default: &default
  adapter: mysql2
  encoding: utf8
  pool: 
  username: root
  password: xxxxxxx #追加: MysqlのMYSQL_ROOT_PASSWORDと同じ値
  host: db  #追加: MySQLのコンテナ名と同じ値。この後構築します。

docker-compose.ymlにwebコンテナを追加します。

Railsが起動するDockerイメージを作成します。

$ vi Dockerfile 

FROM ruby:2.6
RUN apt-get update -qq && apt-get install -y nodejs #追加
WORKDIR /task_app
COPY Gemfile /task_app/Gemfile
RUN bundle install
CMD ["rails", "server", "-b", "0.0.0.0"] #追加

RailsアプリはDocker Composeを利用して起動させます。
docker-compose.ymlを作成し、webコンテナの起動設定をdocker-compose.ymlに記載します。

# vi docker-compose.yml

version: '3'
services:
  web:
    build: .
    ports:
      - '3000:3000'
    volumes:
      - .:/task_app

コンテナを停止するとmysqlのデータが消えてしまうため、データ永続化のため、volumesを入れています。

Dockerfileを編集したのでdocker-composeコマンドでDockerイメージをビルドします。

$ docker-compose build
Building web
Step 1/6 : FROM ruby:2.6
 ---> a161c3e3dda8
Step 2/6 : RUN apt-get update -qq && apt-get install -y nodejs
 ---> Running in c287be3cb98f
Reading package lists...
Building dependency tree...
Reading state information...
The following additional packages will be installed:
  libc-ares2 libnode64 libuv1 nodejs-doc
Suggested packages:
  npm
The following NEW packages will be installed:
  libc-ares2 libnode64 libuv1 nodejs nodejs-doc
0 upgraded, 5 newly installed, 0 to remove and 0 not upgraded.
Need to get 6753 kB of archives.
After this operation, 30.4 MB of additional disk space will be used.

* For more details, please refer to the Sass blog:
  https://sass-lang.com/blog/posts/7828841

Removing intermediate container 711351e6bba5
 ---> 5486636e708a
Step 6/6 : CMD ["rails", "server", "-b", "0.0.0.0"]
 ---> Running in 82d851651892
Removing intermediate container 82d851651892
 ---> a895f6cec871
Successfully built a895f6cec871
Successfully tagged task_app_web:latest

$ docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

task_app_web        latest              a895f6cec871        8 minutes ago       1.02GB
task_app            latest              89e179509cba        About an hour ago   926MB

ここまででweb(rails)コンテナの構築が完了です。

db(mysql)コンテナの構築

depends_onにdbコンテナを追加します。
※depends_onはwebとDBコンテナの作成順序と依存関係を定義します。

# vi docker-compose.yml

version: '3'
services:
  web:
    build: .
    ports:
      - '3000:3000'
    volumes:
      - .:/task_app
    depends_on: #追加
      - db  #追加
  db:  #追加
    image: mysql:5.7  #追加
    volumes:  #追加
      - ./mysql:/var/lib/mysql  #追加
    environment:  #追加
      MYSQL_ROOT_PASSWORD: 'xxxxx'  #追加: Railsのpasswordと同じ値

一旦、Dockerコンテナを起動します。

$ docker-compose up

ただし、dbコンテナはありますがRuby on Railsで利用するデータベースがないので作成します。

$ docker-compose exec web rake db:create
Created database 'task_app_development'
Created database 'task_app_test'

$ docker-compose exec db mysql -uroot -pxxxxx

mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.

Your MySQL connection id is 7

Server version: 5.7.29 MySQL Community Server (GPL)
Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+----------------------+
| Database             |
+----------------------+
| information_schema   |
| mysql                |
| performance_schema   |
| sys                  |
| task_app_development | ← 作成
| task_app_test        |
+----------------------+
6 rows in set (0.00 sec)
mysql>

データベースも作成できましたので、db(mysql)コンテナの準備も完了です。

ブラウザでアクセス

http://localhost:3000/ でアクセスして確認します。

スクリーンショット 2020-01-19 23.09.22.png

見慣れた画面が出ましたね!

まとめ

Docker環境で簡単にrailsの開発環境が用意できました!

ちなみにDockerのキャラクターは、Moby(モビー)って言うらしいです。

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

【Ruby】returnとselfを使って「浜田麻里」を表示せよ

はじめに

4ヶ月くらい前からプログラミングの勉強をし始め、
Ruby難しいなー、特にreturnとself、難しい。コレガワカラナイ。
と、なんどもreturnとselfを検索しては記事を読みましたがよくわからない。
今でもよくわかってないです。

それをわかるようになるには、
returnとselfを使って「浜田麻里」を表示するしかないと天啓があったので書いてみました。

問題のソースコード

class Return_to
    attr_accessor :to,:my

    def initialize(to,my)
      self.to = to
      self.my = my
    end
    def my_self
      return self.to + self.my
    end

end

return_to = Return_to.new("浜田","麻里")
puts return_to.my_self

selfを使用するには、classを定義しなければいけなかったので、
最初はどうしようかと思いましたが、
return_to.my_selfというメソッドで浜田麻里が表示されたら一番美しいのではないか?
と考え、Return_toというclassを定義しました。

引数を「浜田」と「麻里」にして、それをくっつけた文字列がreturnで戻り値として、
呼び出したメソッド、つまりreturn_to.my_selfに返すようにしています。

selfメソッドはclassを省略して書くことができるので、
ここで言うところのReturn_to.hogeself.hogeとすることがでるそうで、
initializeのところでは、引数で受け取った「浜田」と「麻里」を代入する時に、
self.toself.myと言う形で使用しています。

もうお気付きの方もいらっしゃると思いますが、
このclassとメソッドは、米倉千尋にも使用することが可能です。

class Return_to
    attr_accessor :to,:my

    def initialize(to,my)
      self.to = to
      self.my = my
    end
    def my_self
      return self.to + self.my
    end

end

return_to = Return_to.new("米倉","千尋")
puts return_to.my_self

世代によって使い分けをすることができる汎用性の高い素晴らしいclassとメソッドだと思いました。

終わりに

なぜこんな天啓があったのかというと、returnとselfをなんども検索していたら、
YouTubeの「あなたへのおすすめ」で、浜田麻里氏の「Return to Myself」をという曲が表示され、
CD全盛期のパワーあふれる曲に心打たれてしまったからです。

ちょっと普通に意味わからないのが、
プログラミングのreturnとselfを検索し続けると、returnとselfが入った曲をおすすめしてくるのか?
というところ。

わからなくて何度も検索しているから、
検索エンジンは「この人はプログラミングのreturnとselfを検索している訳ではないのではないか?」
と気を利かせてくれたのでしょうか。
なんにしてもセンスがいいです。

このコードを書こうとしたきっかけは、それこそ4ヶ月くらい前だったのですが、
その時は全くreturnとselfの意味がわからず、
見よう見まねで実装して、「浜田麻里」が出力されましたが、
returnを消しても「浜田麻里」が出るし、self.hogeももっと回りくどく使っていて、どうも人前に出していいものではないなと判断したので、
もっと理解できるようになってから書こうと思いました。

何で書いたんですかね。

世代的には米倉千尋氏のReturn to myselfの方が馴染みがあり、アニメも毎週みてました。

参考資料

Return to Myself 〜しない、しない、ナツ。(ウィキペディア)

「Return to Myself 〜しない、しない、ナツ。」(リターン・トゥ・マイセルフ 〜しない、しない、ナツ。)は、浜田麻里の9枚目のシングル。1989年4月19日発売。発売元はビクターインビテーション。

'89カネボウ化粧品 夏のキャンペーンソングに起用され、大塚寧々出演のCMが大量にオンエアされた。サブタイトルの「しない、しない、ナツ」も、当時のカネボウのキャッチコピー「化粧なおし、しない、しない、ナツ」からとっている。

Return to myself (曲)(ウィキペディア)

「Return to myself」(リターン トゥー マイセルフ)は、米倉千尋の13thシングルである。初回盤は「カード(モンコレナイト)」を封入している。

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

メタプログラミング Ruby memo #1

概要

現在Rubyを勉強しており、「Rubyを勉強するならメタプログラミングRubyは必読!」という先輩からの教えがあり、読んでいるのでそれのまとめメモです。

筆者のRubyに関してのスキルは「プロになるためのRuby」を読んでRailsチュートリアルをさらっとくらいのレベルです。
お見知り置きいただければと。。。

今回はまずメタプログラミングに関してまとめようと思います。

個人的レベル感

「上級者向け過ぎてわからないことも多いだろうなあ」とびびっていたのですが案外わかりやすく、ひとつひとつコードを書きながら進めていくと理解が進むような造りになっていると感じました。

翻訳書なので、表現の仕方が基本的に海外よりなので、そこに関して苦手な人は読みづらいかもです...

何かあるごとに開く本というのが誰しもあると思うのですが、個人的にはこの本はそこにランクインするレベルだと思います。

そもそもメタプログラミングって何?(広い意味での)

  • ロジックを直接コーディングするのではなく、あるパターンをもったロジックを生成する高位ロジックによってプログラミングを行う方法、またその高位ロジックを定義する方法のこと( wikipedia)
  • 他のプログラム ( あるいは自身 ) を操作したり出力するプログラムを書くこと、または作業の一部をコンパイル時に行い、残りの作業を実行時に行うようなプログラムを書くことである。
  • コードジェネレータやコンパイラが行うコード生成のこと。

Rubyは「動的メタプログラミング」

  • Rubyに関して言えば、「コードを生成するためのコードを書くこと」が簡単にできてしまう。 (本書の言葉を借りれば「シームレスかつエレガントに」)
  • 「魔法」や「黒魔術」と呼ばれるくらい、簡潔にコードが書ける。
  • 大いなる力には、大いなる責任が伴うことを忘れてはいけない」 by Matz

言語要素を実行時に操作するコードを記述すること

コードジェネレータやコンパイラが行うコード生成のことも、「広い意味でのメタプログラミング」と言えるのだが、Rubyはこの定義には含まれない。
これを「静的メタプログラミング」と呼ぶならば、Rubyは「動的メタプログラミング」と呼べる

メタプログラミングに利用されている技術の例

実際に本書で使われている技術の一例を紹介しようと思います。
※ もっと沢山あります。

既存のクラスの振る舞いを変更する(モンキーパッチ)

mokey_patch.rb
"abc".reverse # => "cba"

class String
  def reverse
    "オーバーライド"
  end
end

"abc".reverse # => "オーバーライド"

1. 再定義したメソッドから以前のメソッドをエイリアスで呼び出す

around_arias.rb
# String#reverseに新たな機能を追加する
"abc".reverse # => "cba"

class String
  alias_method :old_reverse, :reverse

  def reverse
    "hoge#{old_reverse}hoge"
  end
end

"abc".reverse # => "hogecbahoge"

2. 該当するメソッドのないメッセージに応答する

method_missing.rb
class Hoge; end

hoge = Hoge.new
hoge.no_define_method
# => NoMethodError (undefined method `no_define_method' for #<Hoge:0x00007f9649836be8>)


# BasicObject#method_missingを書き換える(noMethodErrorを返しているメソッド)
class Hoge
  def method_missing(name, *args)
    name.to_s.reverse
  end
end

hogehoge = Hoge.new
hogehoge.no_define_method
# => "hogedohtem_enifed_onhoge"

動的メソッド

dynamic_method.rb
class C; end

C.class_eval do
  define_method :my_method do
    "動的メソッド"
  end
end

obj = C.new
obj.my_method
# => "動的メソッド"

ざっくりまとめると

  • 上記のようなRubyの技術を駆使してコードのリファクタなどを例を用いてわかりやすく説明してくれています。
  • ActiveRecordなどのRailsのコードもどのようなスキルを駆使して書かれているか説明してくれているので、難しいコードを読んだことない人にとってはとっつきやすいかもしれません。
  • 本書の最後のほうでも言っているのですが、メタプログラミングがどうとか考えずにただのプログラミングだと思えという言葉がなぜかしっくりきました。 (自分のバイアスがかかっていただけかも?)
  • Rubyの「特徴」を事細かに説明してくれている良書だと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】Railsとdeviseのデフォルトバリデーションを解除する方法

はじめに

知人がemailの正規表現を使ったバリデーションを自ら作成して設定しようとした際、うまく設定を反映させられませんでした
いろいろ調べたところRailsのバリデーションやらdeviseのバリデーションやらが絡んでいたためでした
今まで知らないで使ってたことなど勉強になったことが多かったので整理してまとめておきます

対象読者

  • form_withやform_forが何かをなんとなく理解している人
  • バリデーションが何かをなんとなく理解してる人

環境

  • Ruby: 2.5.1
  • Rails: 5.2.3

状況

  • 正規表現で~@~.~という形じゃないとアドレスとして登録できないようなカスタムバリデーションを作成した
  • そのバリデーションを有効にしたが、下記の画像のような謎のバリデーションにひっかかった

スクリーンショット 2020-01-25 12.30.17.png

自分で作ったカスタムバリデーションじゃないのがでてきた。。。ってなったんですが
結論、Railsのバリデーションとdeviseのバリデーションを解除してやれば、目的通り自作のカスタムバリデーションのみ反映させられました

以下に、やり方と解説を記載します

Railsのバリデーション設定

Railsでメールアドレスやパスワードをユーザーに入力させるとき、入力フォームを作成するためにform_forやform_withを使いますよね。こんな感じで

new.html.haml
= form_with(model: @user, local: true, class: 'hoge') do |form|
  = form.label :email, 'メールアドレス'
  = form.email_field :email

ここで記載したemail_fieldがさっきのバリデーションの正体です
このように記載することでRailsはこの入力フォームをemailだと認識して、簡単なバリデーションを自動でかけるようになっているみたいです

なので先ほどの記載を以下のように書き換えるとRails側でバリデーションをかけないようにできます

new.html.haml
#変更前
= form.email_field :email

#変更後
= form.text_field :email



結果がこちら
スクリーンショット 2020-01-25 13.03.13.png

先ほどのバリデーションは消えましたが、フラッシュメッセージにバリデーションが2つ出てきてしまいました

deviseのバリデーション設定

今のままだとバリデーションが2つ出てきてしまいます。先ほどの画像の左側がdeviseのバリデーション、右側が自分で設定したカスタムバリデーションです
今回は自分が設定したカスタムバリデーションだけ表示させたいので、deviseのバリデーション設定を解除します

通常、deviseをインストールした場合、モデル内に下記のような記述があると思います

models/user.rb
devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

この:validatableがdeviseのバリデーションなので、この記述を消します

models/user.rb
devise :database_authenticatable, :registerable,
         :recoverable, :rememberable

これでdeviseのデフォルトのバリデーションも解除できました


結果はこちら
スクリーンショット 2020-01-25 13.05.12.png

ちゃんと自分で設定したバリデーションだけが適用されていますね
*カスタムバリデーションの作成の仕方は最後の参考URLに載せておきます

まとめ

  • Railsのemail_fieldではメールアドレス用の簡単なバリデーションをかける仕様になっている
  • deviseではデフォルトで簡単なバリデーションをかけるようになっている
  • バリデーションの優先順位はemail_field > devise, カスタム(自作)
  • カスタムバリデーションのみ適用させる場合は、text_fieldを使い、deviseの:validatableを削除する

Railsもdeviseも知らないところで色々と機能してくれてますが、少しずつそういう部分を理解していくことで、色々応用できそうですね
特にdeviseは便利ですが、意味を理解していないで使っている部分が多いと思ったので今回のようなケースはかなり勉強になりました

余談ですが、password_fieldは入力した文字を"●"で秘匿化してくれてます

間違いなどがあれば指摘していただけると助かります
少しでも参考になった、勉強になったという方がいらっしゃれば幸いです

参考URL

自作のカスタムバリデーションを作成する方法
- https://guides.rubyonrails.org/active_record_validations.html
- https://qiita.com/kouheiszk/items/215afa01eeaadbd99340

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

Rubyでトランプゲームを作ってみた!その2 #2

Rubyで簡単なゲーム作成

プログラミング超初心者ですが、学んだことを最大限に活かして、トランプのブラックジャックを作成していきたいと思います。

前回までに実装したこと

・カードの配列
・カードを引いてマークと数字を決定する

前回(https://qiita.com/okiku-stt/items/98a34e39654b2a35365c) エースを引いた際に
1か11を決める際にjudgeというメソッドを作っています。
そこから開始します。

if sum_figure > 10
    sum_figure = 10
  elsif sum_figure = 1
    puts "Aを引きました。「1」か「11」のどちらかを入力してください。"
    judge(sum_figure)
  else  
    sum_figure
  end

4.Aceの判定

judgeの中身は以下の通りです。

  def judge(draw_ace)
    draw_ace = gets.to_i
    while draw_ace != 1 && draw_ace != 11 do
      puts "もう一度入力してください"
      draw_ace = gets.to_i
    end
    return  draw_ace
  end

getsで入力をしていただき、その数値が1,11以外の場合は再度入力してもらうようにしております。
引数のsum_figureをdraw_aceとしてメソッド内で使用しています。

5.メニュー画面

while true do
  #手札の表示
  puts "〜あなたの手札〜"
  hand_mark.zip(hand_number) do |m,n|
    puts "#{m}\s#{n}"
  end

  #21を超えているかの判定
  sum = hand_figure.inject(:+)
  if sum <= 21
    puts "あなたの合計は「#{sum}」です。"
  else
    puts "あなたの合計は「#{sum}」です。"
    puts "バーストです。あなたの負けです。\nまた遊んでね!!"
    exit
  end

  #メニューの表示
  puts "まだカードを引きますか?"
  puts "[1]カードを引く"
  puts "[2]勝負!!"
  puts "[3]ゲームをやめる"
  menu = gets.to_i

  if menu == 1
    draw(deck,hand_mark,hand_number,hand_figure)
  elsif menu == 2
    battle(enemy_hand,hand_figure)
    exit
  elsif menu == 3
    puts "また遊んでね!!"
    exit
  else
    puts "もう一度入力してください"
  end
end

全体をwhileで繰り返し処理をしています。
「手札の表示」
zipでhand_mark配列とhand_number配列を交互に表示しています。
例えば❤︎5と♣️クイーンを引いた際に、eachを2回使用すると、
❤︎♣️
5Q
という表示になってしまいます。
zipを使うと、
❤︎5
♣️Q
という表示になります。

「21を超えているかの判定」
現在の手札の合計にsumを使用しています。
作り終わってから知ったのですが、inject(:+)でも合計が取り出せるようです、、

「メニューの表示」
1の場合はもう一枚引く処理、2の場合はbattleメソッドを呼び出しています。

6.勝ち負けの判定

enemy_hand = [*(18..25)]

まず、cpuの合計を定義します。
18-25の間でランダムとします。

def battle(enemy_hand,hand_figure)
  puts "勝負!!"
  battle = enemy_hand.sample
  puts "cpuの合計は「#{battle}」です。"
  sum = hand_figure.sum

  if battle >= 22
    puts "cpuはバーストでした。"
    puts "あなたの勝ちです。\nまた遊んでね!!"
  elsif sum < battle
    puts "あなたの負けです。\nまた遊んでね!!"
  elsif sum == battle
    puts "引き分けです。\nまた遊んでね!!"
  else
    puts "あなたの勝ちです。\nまた遊んでね!!"
  end
end

battleにcpuの合計を取得します。
sumは自分の手札の合計です。
cpuが22以上のとき(→cpuがバースト+勝ち)、負け、引き分け、勝ち
の4通りの分岐をして終了となります。

終わりに

初めてコードを0から書くということをしてみましたが、やはり最初が肝心だなと感じました。
ブラックジャックというルールが決まっているゲームにも関わらず、「エースの処理をしなきゃ」、「エースと絵柄は文字列だから合計を別で計算しなきゃ」と色々いじって時間がかかってしまいました。

progateとかドットインストールとかRuby関連の書籍とか1冊読めば誰でも作れると思います。
こいつよりきちんとしたの作れるというモチベーションになれば幸いです。

読んでいただいた方でお気づきの方はいると思いますが、
・クラスとインスタンスを使っていない
ので、修正していきたいなと思います。

機能的には「お金を賭ける」、「cpuもドローする」、「複数人でプレイできるようにする」などありますので、
スキルを上げて実装していきたいです!

あと、エースを引いて11を選んだ後に1に変えることができず、バーストになってしまうのはクソゲーですね。。
直します。。

keyword:
Array,配列,引数,.div,sample,sum,inject,zip

twitter:https://twitter.com/okiku_engin

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

devise 導入直後のエラー(docker)

概要

[Rails] deviseの使い方(rails4版)
このサイト↑に沿ってdevise導入し、そこでこのエラーが発生しました。
その対処法を記載をしています。

エラーの内容

スクリーンショット 2019-12-26 23.25.40.png

ルーティングエラー
Rails routes で調べても特に問題なさそう・・
Rails: deviseをインストールした後のエラー
これ↑を実装したが、エラー治らず・・・

対処法

問題は恐らくdockerなのでは?となり、とりあえず
docker再起動→ローカルを再起動

docker再起動方法
右上にあるクジラを押す→restart 完了!!
スクリーンショット 2020-01-25 14.21.04.png

それでもう一度localhostをリロードすると治りました。
お疲れさまでした!!

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

日本語に翻訳したdeviseをインストールする[Ruby on Rails]

はじめに

ユーザー認証系のアプリに必須のgem"devise"を日本語訳したものをインストールする方法を書いていきます
この記事が超絶分かりやすいのでこれの2. deviseの設定 の 3. flashメッセージの設定までをやってください

deviseの日本語化

gemの追加

gem 'devise-i18n'
gem 'devise-i18n-views'

そして

$ bundle install

日本語化の設定

まず、config/application.rbに以下を追加します

config/application.rb
Bundler.require(*Rails.groups)

module Remonote
  class Application < Rails::Application
    config.load_defaults 5.1

    # 以下を追加
    config.i18n.default_locale = :ja

  end
end

次にターミナルでこう打つと完成です‼︎‼︎

$ rails g devise:views:locale ja
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

IntelliJでanyenv経由のrbenvで入れたRubyを自動で認識させる

IntelliJ IDEAでRubyプロジェクトを作るときに、anyenv経由で入れたrbenvを認識してくれなかったので解決方法をメモ。

環境

  • macOS mojave 10.14.6
  • IntelliJ IDEA 2019.3.1 (Ultimate Edition)
  • anyenv 1.1.1
  • rbenv 1.1.2-17-g7795476

やりたかったこと

タイトルまんまです。
単体で入れてたrbenvを削除してanyenv経由でインストールし直したところ、
IntelliJで新しくプロジェクトを作る際にProjectSDKとしてrbenv配下のRubyたちがリストされなくなりました。
手動でpathを指定してやればそれで済む話ではありますが、rbenv installするたびに手動でIntelliJに追加するのはめんどくさいので対処法を探しました。

解決方法

いきなり結論で恐縮ですが

ln -s ~/.anyenv/envs/rbenv ~/.rbenv

これで解決します。シンボリックリンクを貼ってるだけです。

公式のヘルプによるとrbenvは正式にサポートしているけれどanyenvについてはまったく言及されていないので、おそらくanyenv経由のインストールはサポート外なんだろうと思います。

If you have one of these version managers installed on your local machine, IntelliJ IDEA automatically detects it and lets you switch between available Ruby interpreters

とあるのですが、おそらくこれは単純にrbenvのデフォルトのインストール先(~/.rbenv)が存在するかどうかを探しているのかなと推測します
なので試しにシンボリックリンクでanyenv配下のrbenvを参照させたところ意図した通りの動きをしてくれた、というだけの話でした。

試してないですがanyenvで入れた他のXXenvで似たような問題が起きたら同じ対処でいけるかもしれませんね。
普通にanyenvに対応してくれるのが一番早いのですが。。

ここまで書いて気がついたのですが、こちらの記事で言及されてました。
InteliJ 2017.3のドキュメントでは上記の仕様がはっきり明記されていたみたいですね。
それ消さないで欲しかった。

参考

https://qiita.com/okay_uki/items/5c8fac9cae12b831a157

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

【翻訳】URI.escapeは非推奨メソッドです。あなたのクエリ文字列をパーセントエンコードするには

この記事は以下のブログ記事の日本語訳です。

URI.escape is obsolete. Percent-encoding your query string

【翻訳】URI.escapeは非推奨メソッドです。あなたのクエリ文字列をパーセントエンコードするには

みなさんはRuby 2.7.0を使っているプロジェクトで以下の警告に遭遇しましたか?

warning: URI.escape is obsolete
warning: URI.encode is obsolete

この警告の直し方を見ていきましょう!

歴史について少しだけ

Ruby 2.7.0ではURI.escapeまたはエイリアスメソッドのURI.encodeを呼びだしたときに警告が出ます。これはあたかも新しく追加された警告のように見えますが、実際はなんと・・・10年以上も非推奨とされ続けていたのです!どうしても今までこの警告を目にしなかったんだろう?と不思議に思っている方へ。答えはこうです。これまではverboseモードでスクリプトを実行したときだけ表示されていました。そして、この仕様が最近変わりました。これがその理由です。

じゃあなんでURI.escapeは非推奨メソッドなの?

「URIをエスケープする」という概念は実はやっかいです。なぜならURIは多数の要素(pathqueryなど)から成り立っており、その要素をすべて同じ方法でエスケープするわけではないからです。たとえば、#という文字について考えてみましょう。この文字はURIの最後に出てくるときは問題ありません(これは世間ではアンカーと呼ばれます。URI用語で言うところのfragment要素です)。しかし、ユーザー入力の一部に#が使われたとき(たとえば検索キーワードなど)は、入力値を正しく解釈するためにそれをエンコードしたいと思うはずです。同様に、クエリ文字列に=&といった予約文字が含まれていた場合、それらが間違って区切り文字として解釈されないようにエスケープしたくなるはずです(あたかも予約文字であるかのように)。

URI.escapeは単純にgsubメソッドを使って文字列全体を置換しているだけです。各要素が何であるかは区別しません。なので、上で述べたような複雑な問題は一切考慮されないのです。

どう修正するの?

URI文字列をまるごとを受け取って、各要素の違いをうまく解釈しつつ最適なエスケープ処理を加える、というような既存の解決策はまだ見つかっていません。ですので、私が考えるに、こういう場合は各要素を別々にエンコードするのが良いと思います。最も一般的な(そして最も問題が起きやすい)ユースケースは、おそらくクエリ文字列(query要素)のエンコーディングでしょう。そこで、今回はこのユースケースに焦点を当てます。RubyのURIモジュールには2種類の便利なメソッドが用意されているので、これを使えばうまくいきます!

クエリ文字列をパーセントエンコードする

URI.encode_www_form_component(string, enc=nil)

このメソッドは*, -, ., 0-9, A-Z, _, a-zだけをそのまま残し、それ以外のすべての予約文字をパーセントエンコードします。さらに、スペースも+に置換してくれます。以下の例を見てください。

query = "Tom & Jerry"
query = URI.encode_www_form_component(query)
query # => "Tom+%26+Jerry"
post = "So scared, but let's do this! #yolo"
post = URI.encode_www_form_component(post)
post # => "So+scared%2C+but+let%27s+do+this%21+%23yolo"

ご覧のとおり、適切なエスケープ処理を加えて安全なクエリ文字列を作ることができました。しかし、これだとちょっと手間がかかりすぎると感じた場合は、クエリ全体を適切に処理してくれる便利な方法があります。それがこちらです。

URI.encode_www_form(enum, enc=nil)

このメソッドは先ほどのメソッドとはインターフェースが若干異なり、Enumerable(通常はネストした配列かハッシュ)を受け取ります。それから、そのデータを使ってクエリ文字列を構築します。このメソッドは内部で.encode_www_form_componentを使うため、エンコーディング規則はどちらも同じです。異なるのは使い方だけです。以下の例を見てください。

URI.encode_www_form([["search", "Tom & Jerry"], ["page", "3"]])
# => "search=Tom+%26+Jerry&page=3"

URI.encode_www_form(["search", "2 + 2 = 5"])
# => "search=2+%2B+2+%3D+5"

URI.encode_www_form(search: "why is URI.escape obsolete", category: "meta")
#=> "search=why+is+URI.escape+obsolete&category=meta"

# Shameless plug
URI.encode_www_form(q: "how to speed up your CI?", tag: ["#devops", "#productivity"], lang: "en")
#=> "q=how+to+speed+up+your+CI%3F&tag=%23devops&tag=%23productivity&lang=en"

とても簡単ですね。

Railsの場合は?

Hash#to_query

Hash.to_paramというエイリアスメソッドもあります)

RailsはHashクラスを拡張して、この便利なメソッドを追加しています。このメソッドは正しい形式でクエリ文字列を返します。値は適切にエスケープされます。

query_data = { q: "how to speed up your CI?", tag: ["#devops", "#productivity"], lang: "en" }
query_data.to_query
#=> "lang=en&q=how+to+speed+up+your+CI%3F&tag%5B%5D=%23devops&tag%5B%5D=%23productivity"

(キーがアルファベット順にソートされる点にも注意してください)

このメソッドにはオプション引数としてネームスペースを渡すこともできます。返ってくる文字列はネームスペースが各キーを内包する形になります。たとえば、search[q]="how to speed up your CI?"というような形です(もちろんパーセントエンコードされますが)。

query_data = { q: "how to speed up your CI?", tag: ["#devops", "#productivity"], lang: "en" }
query_data.to_query("search")
#=> "search%5Blang%5D=en&search%5Bq%5D=how+to+speed+up+your+CI%3F&search%5Btag%5D%5B%5D=%23devops&search%5Btag%5D%5B%5D=%23productivity"

まとめ

短い記事ですが、みなさんが「この記事を読んだらURI文字列のエンコードに関する疑問が解消された!」と思ってくれたら嬉しいです。もし、みなさんのRuby/Railsプロジェクトでは何か別の方法を使っているなら、コメント欄にてその方法も教えてください。

そしてもし、みなさんのプロジェクトでCIのビルドが遅いことにお困りでしたら、ぜひKnapsack Proをチェックしてみてください。その問題を解消します。

(翻訳はここまで)

訳者による補足

日本語や"%"が含まれる場合のエンコード結果について

クエリ文字列に日本語が含まれる場合や、文字列に%が含まれる場合のエンコード結果も調べてみました。
結果としてはURI.encodeを使った場合も、URI.encode_www_form_componentURI.encode_www_formを使った場合も同じでした。

# 日本語をエンコードする場合
str = "あいう"

URI.encode(str)
#=> "%E3%81%82%E3%81%84%E3%81%86"

URI.encode_www_form_component(str)
#=> "%E3%81%82%E3%81%84%E3%81%86"

URI.encode_www_form([['query', str]])
#=> "query=%E3%81%82%E3%81%84%E3%81%86"
# %を含む文字列をエンコードする場合
str = "10%"

URI.encode(str)
#=> "10%25"

URI.encode_www_form_component(str)
#=> "10%25"

URI.encode_www_form([['query', str]])
#=> "query=10%25"

公式リファレンスのリンク等

各メソッドの公式リファレンスはこちらです。

また、公式リファレンスではURI.encodeの移行先として、encode_www_form_component以外にも以下のようなメソッドが紹介されています。

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

rubyのmap関数ではreturnではなくnextをつかう

rubyで以下の様なコードを実行しようとして失敗した話

def hoge()
  (1..5).map do |i|
    return 'this is 3' if i == 3
    #~ 複雑な処理 ~
    i
  end
end

p hoge # => "this is 3"

なんとなく上記のようなコードを実行したらmapの結果は[1, 2, 'this is 3', 4, 5]の配列がかえってくると思っていたけど実際には"this is 3"が返ってきたので、慌ててrubyのドキュメントを読んでたら、nextを使う必要があるみたい。
nextも引数を取れるので、条件式が正(true)の時に返したいデータをnextの引数に渡す様にしてあげる。

つまり以下の様にするのが正しい。

def hoge()
  (1..5).map do  |i|
    next 'this is 3' if i  == 3
    #~ 複雑な処理 ~
    i
  end
end

p hoge # => [1, 2, 'this is 3', 4, 5]

あやうくバグを仕込むことになった…

ちなみにJavaScriptとか他の言語ではreturnが多いからrubyもreturnだと思ってた。

var arr = [1, 2, 3, 4, 5];
map_arr = arr.map(i => {
    if (i == 3) {
        return 'this is 3';
    }
    // ~ 複雑な処理 ~
    return i
})

console.log(map_arr) // [ 1, 2, 'this is 3', 4, 5 ]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LeetCode - 21. Merge Two Sorted Lists

ListNodeをつくる

配列からListNodeのテストデータを作る

# Definition for singly-linked list.
class ListNode
  attr_accessor :val, :next
  def initialize(val)
    @val = val
    @next = nil
  end
end

def create_list_node(array)
  array_of_list_nodes = array.map{|n|
    ListNode.new(n)
  }

  lns = ListNode.new(0)
  buf = lns
  array_of_list_nodes.each{|ln|
    lns.next = ln
    lns = lns.next
    #p lns
    #p buf
  }
  buf.next 
end

ln1 =  create_list_node([1,2,4])
ln2 =  create_list_node([1,3,4])

  • buf のところがポイント

ListNodeをマージする

def merge_two_lists(ln1, ln2)
  ln3 = ListNode.new(0)
  buf = ln3

  while ln1 && ln2
    if ln1.val <= ln2.val
      ln3.next = ln1
      ln1 = ln1.next
    else
      ln3.next = ln2
      ln2 = ln2.next
    end
    ln3 = ln3.next
  end

  ln3.next = ln1 || ln2 
  buf.next
end

  • buf のところがポイント
  • 条件式 ln1 && ln2はどちらかが nilfalse
  • buf は先頭ノードを仮に0で作ってるので、nextを返す

スコア

Runtime: 72 ms, faster than 10.21% of Ruby online submissions for Merge Two Sorted Lists.
Memory Usage: 9.6 MB, less than 100.00% of Ruby online submissions for Merge Two Sorted Lists.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【jQuery】RailsでValidation Pluginを使った動的なバリデーションチェックの実装 〜詳細実装/Bootstrap編〜

はじめに

前回の記事では導入編をまとめましたので本記事では詳細実装編をまとめます。後半では以下の場合についてもまとめています。

  • Bootstrapを使用している場合
  • deviseを使用している場合
  • 正規表現チェックメソッドのサンプル一覧

開発環境

Ruby 2.5.1
Rails 5.0.7.2
jQuery 3.4.1
jQuery Validation Plugin 1.19.1
Bootstrap 4.3.1

実装手順

入力フォームを実装済み、プラグインを導入済みであることを前提としています。

パスワードと確認用パスワードのバリデーションチェック

完成イメージ

Image from Gyazo

解説・サンプルコード

eqaulToはデフォルトで使えるメソッドです。指定したidの入力欄と入力値が一致しているかをチェックできます。以下が本箇所のJSファイルの記述サンプルです。

jquery.validate.handler.user.js
$(function () {
  // メソッドの定義
  var methods = {
    password: function (value, element) { // パスワードの正規表現 
      return this.optional(element) || /^(?=.*?[a-z])(?=.*?\d)[a-z\d]{8,100}$/i.test(value);
    },
  }
  // メソッドの追加
  $.each(methods, function (key) {
    $.validator.addMethod(key, this);
  });
  // バリデーションの実行
  $("#signup-form, #charge-form").validate({
    // ルール設定
    rules: {
      "user[password]": {
        required: true, // 入力必須
        password: true // 正規表現
      },
      "user[password_confirmation]": {
        required: true, // 入力必須
        password: true, // 正規表現
        equalTo: "#password" // パスワードと確認用パスワードが一致しているかチェック
      },
    },
    // エラーメッセージの定義
    messages: {
      "user[password]": {
        required: "パスワードを入力してください",
        password: "英字と数字両方を含むパスワードを入力してください"
      },
      "user[password_confirmation]": {
        required: "確認用パスワードを入力してください",
        password: "英字と数字両方を含むパスワードを入力してください",
        equalTo: "パスワードが一致していません"
      },
    },
    errorClass: "invalid",
    errorElement: "p",
    validClass: "valid",
  });
  // 入力欄をフォーカスアウトしたときにバリデーションを実行
  $("#password, #password_confirmation").blur(function () {
    $(this).valid();
  });
});

プルダウンリストの選択状況確認

プルダウンリストのバリデーションチェックを実装します。
OK/NGの基準を以下のように設けました。

  • OK:プルダウンダウンリストが選択されて初期値から値が変わっている
  • NG:初期値から値が変わっていない
sample.html.haml
= form_tag(signup_index_path, method: :post, id:"charge-form", name: "inputForm", class: "pay_way__main__form") do
  %div.pay_way__main__form__content
    %div.pay_way__main__form__content__group
      %label.pay_way__main__form__content__group__label  有効期限
      %span.pay_way__main__form__content__group__require   必須
      .pay_way__main__form__content__group__expire
        .pay_way__main__form__content__group__expire__select
          %i.fas.fa-chevron-down.fa-lg.pay_way__main__form__content__group__expire__select__icon
          %select#exp_month{name: "exp_month", type: "text", class: "pay_way__main__form__content__group__expire__select__pulldown", name: "exp_month" }
            %option{value: ""} --
            %option{value: "1"}01
            %option{value: "2"}02
            %option{value: "3"}03
            %option{value: "4"}04
            %option{value: "5"}05
            %option{value: "6"}06
            %option{value: "7"}07
            %option{value: "8"}08
            %option{value: "9"}09
            %option{value: "10"}10
            %option{value: "11"}11
            %option{value: "12"}12
        %span.pay_way__main__form__content__group__expire__select__text.pay_way__main__form__content__group__expire
        .pay_way__main__form__content__group__expire__select
          %i.fas.fa-chevron-down.fa-lg.pay_way__main__form__content__group__expire__select__icon
          %select#exp_year{name: "exp_year", type: "text", class:"pay_way__main__form__content__group__expire__select__pulldown", name: "exp_year" }
            %option{value: ""} --
            %option{value: "2019"}19
            %option{value: "2020"}20
            %option{value: "2021"}21
            %option{value: "2022"}22
            %option{value: "2023"}23
            %option{value: "2024"}24
            %option{value: "2025"}25
            %option{value: "2026"}26
            %option{value: "2027"}27
            %option{value: "2028"}28
            %option{value: "2029"}29
        %span.pay_way__main__form__content__group__expire__select__text#exp_date_error
jquery.validate.handler.user.js
$(function () {
  // メソッドの定義
  var methods = {
    valueNotEquals: function (value, element, arg) { // プルダウンリストが選択されているかの確認
      return arg !== value;
    },
  }
  // メソッドの追加
  $.each(methods, function (key) {
    $.validator.addMethod(key, this);
  });
  // バリデーションの実行
  $("#charge-form").validate({
    // ルール設定
    rules: {
      exp_month: {
        valueNotEquals: ""
      },
      exp_year: {
        valueNotEquals: ""
      }
    },
    // エラーメッセージの定義
    messages: {
      exp_month: {
        valueNotEquals: "有効期限を選択してください"
      },
      exp_year: {
        valueNotEquals: "有効期限を選択してください"
      }
    },
    groups: { //グループ化
      exp_date: "exp_month exp_year"
    },
    errorClass: "invalid",
    errorElement: "p",
    validClass: "valid",
    // エラーメッセージ表示位置のカスタム設定
    errorPlacement: function (error, element) {
      if (element.attr("name") == "exp_month" || element.attr("name") == "exp_year") {
        error.insertAfter("#exp_date_error");
      }
      else {
        error.insertAfter(element);
      }
    }
  });
  // 選択欄をフォーカスアウトしたときにバリデーションを実行(ウィザードページ毎)
  $("#exp_month, #exp_year").blur(function () {
    $(this).valid();
  });
});

エラーメッセージの表示位置の設定

入力欄が横並びになっている場合、エラーメッセージはデフォルトでは各入力欄の直後にエラーメッセージが表示されます。そのため、入力欄が縦並びに変わってしまいます。そのようなときは本設定でメッセージの位置を指定できます。

完成イメージ

Image from Gyazo

解説・サンプルコード

以下のようにエラーメッセージを表示させたい場所の直前にidを付与した要素を設けます。

sample.html.haml
= f.text_field :lastname, class: "クラス名", placeholder:"例)山田", id: "lastname"
= f.text_field :firstname, class: "クラス名", placeholder:"例)彩", id: "firstname"
%div.#name_error
# 〜エラーを表示させたい場所〜

errorPlacementを使用して場所を指定します。

jquery.validate.handler.user.js
$(function () {
  // バリデーションの実行
  $("#signup-form").validate({
    // ルール設定
    rules: {
      "user[lastname]": {
        required: true
      },
      "user[firstname]": {
        required: true
      }
    },
    // エラーメッセージの定義
    messages: {
      "user[lastname]": {
        required: "姓を入力してください"
      },
      "user[firstname]": {
        required: "名を入力してください"
      },
    errorClass: "invalid",
    errorElement: "p",
    validClass: "valid",
    // エラーメッセージ表示位置のカスタム設定
    errorPlacement: function (error, element) {
      if (element.attr("name") == "user[lastname]" || element.attr("name") == "user[firstname]") {
        error.insertAfter("#name_error"); // 指定した要素の後ろにエラーを表示
      } else {
        error.insertAfter(element);
      }
    }
  });
  // 入力欄をフォーカスアウトしたときにバリデーションを実行
  $("#lastname, #firstname").blur(function () {
    $(this).valid();
  });
});

Bootstrapを使用している場合

Bootstrapを使用している場合、バリデーションチェック後のクラス名追加にBootstrapのValidationのclass名を指定することで使用することができます。

Bootstrap 公式リファレンス(forms)
Bootstrap 日本語リファレンス(forms)

完成イメージ

Image from Gyazo

解説・サンプルコード

Bootstrapを適用したフォームを用意します。

app/views/devise/sessions/new.html.erb
<div class="form-label-group">
  <%= f.email_field :email, placeholder: "Email Address", class: 'form-control' %>
  <%= f.label :email %>
</div>

<div class="form-label-group">
  <%= f.password_field :password, placeholder: "Password", class: 'form-control' %>
  <%= f.label :password %>
</div>

JSファイルでerrorClassとvalidClassを下記のように設定します。

jquery.validate.handler.user.js
// 〜省略〜
errorClass: "is-invalid"
validClass: "is-valid"
// ~省略〜

deviseを使用している場合

deviseを使用している場合でユーザー情報の変更において下記2つの入力パターンが想定されます。

  • パスワードを変更しない場合:現在のパスワードの入力が必須
  • パスワードを変更する場合:確認用パスワードが必須、現在のパスワードの入力は不要

バリデーションチェックを上記どちらでも対応できるように実装します。

完成イメージ

パスワードを変更しない場合:現在のパスワードの入力が必須

Image from Gyazo

パスワードを変更する場合:確認用パスワードが必須、現在のパスワードの入力は不要

Image from Gyazo

解説・サンプルコード

jquery.validate.handler.user.js
// 〜省略〜
"user[password]": { // requiredは設定しない
  password: true
},
"user[password_confirmation]": { // requiredは設定しない
  password: true,
  equalTo: "#user_password" // 新しいパスワードと一致しているか確認
},
"user[current_password]": {
  password: true,
  required: function (element) { // 新しいパスワード欄が空欄の場合は入力必須にする
    return $("#user_password").val() == "";
  }
},
// 〜省略〜

正規表現チェックメソッド一覧

今回の開発で様々な正規表現チェックを実装したので、それもまとめたいと思います。

jquery.validate.handler.user.js
  // メソッドの定義
  var methods = {
    email: function (value, element) { // メールアドレスの正規表現 
      return this.optional(element) || /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/i.test(value);
    },
    password: function (value, element) { // パスワードの正規表現 
      return this.optional(element) || /^(?=.*?[a-z])(?=.*?\d)[a-z\d]{8,100}$/i.test(value);
    },
    phone: function (value, element) { // 電話番号の正規表現
      return this.optional(element) || /^0\d{9,10}$/.test(value);
    },
     // クレジットカード番号の正規表現
     // VISA, MasterCard, Discover, Diners, Amex, JCBの番号規則に対応
    cardNumber: function (value, element) {
      return this.optional(element) || /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6011[0-9]{12}|3(?:0[0-5]|[68][0-9])[0-9]{11}|3[47]{13}|(?:2131|1800|35[0-9]{3})[0-9]{11})$/.test(value);
    },
    cvc: function (value, element) { // セキュリティコードの正規表現
      return this.optional(element) || /^\d{3,4}$/.test(value);
    },
    postalCode: function (value, element) { // 郵便番号の正規表現
      return this.optional(element) || /^\d{3}[-]\d{4}$/.test(value);
    },
    kana: function (value, element) { // カタカナの正規表現
      return this.optional(element) || /^[ァ-ヴ]+$/.test(value);
    },
  }

まとめ

デフォルトで様々なメソッドが準備されていますが、カスタムメソッドを作ることができるため様々なバリデーションチェックが実装できます。Bootstrapのクラス名を組み込めばBootstrapと連携可能です。

参考URL

正規表現を可視化してまとめたチートシート
jquery.validate.jsでセレクトボックスのチェック、他カスタマイズ色々

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

【Rails】 独自 Validation 作成方法(複数モデル適用)

はじめに

バリデーションの作成方法はモデル内にprivateメソッドを作って、そのメソッドをvalidateの引数に指定する方法が一般的かと思います。
今回は、複数のモデルで同じバリデーションを適用したいケースへの対応方法をまとめました。

独自バリデーション作成方法

やること

開始日時カラムと終了日時カラムを持ったモデルの入力フォームに、
どちらのカラムも入力するか、どちらのカラムも入力しないバリデーションをかけたい。

Concern

まずは、app/models/concernsの直下にバリデーション専用のクラスを作成する。
Concernsを使用することで、同じカラムを持った他のモデルにも適用することができ、さらに細かい継承関係を考えなくてもよくなります。

app/models/concerns/work_datetime_validation.rb
class WorkDatetimeValidation < ActiveModel::Validator
  # record には、インスタンスが入る
  def validate(record)
    return if record.work_datetime_begin && record.work_datetime_end
    return if !record.work_datetime_begin && !record.work_datetime_end
    record.errors[:base] << '開始日時と終了日時どちらも入力するか、どちらも空にしてください'
  end
end

Model

次にModelにvalidates_withを用いて、先ほど作成したクラスを指定する。

他のModelにも適用させたい場合は、validates_withを他のクラスに書けばバリデーションが適用されます。

app/models/work.rb
class Work < ApplicationRecord
  validates_with WorkDatetimeValidation
end

以上となります。

まとめ

時間に関するバリデーションは複数のモデルで使用したいというニーズがあると思いますので、ぜひ活用してみてください。

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

#Ruby / URI::DEFAULT_PARSER.make_regexp / RuboCop::Cop::Lint::UriRegexp

# no arg
URI::DEFAULT_PARSER.make_regexp
# => /
#         ([a-zA-Z][\-+.a-zA-Z\d]*):                           (?# 1: scheme)
#         (?:
#            ((?:[\-_.!~*'()a-zA-Z\d;?:@&=+$,]|%[a-fA-F\d]{2})(?:[\-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]]|%[a-fA-F\d]{2})*)                    (?# 2: opaque)
#         |
#            (?:(?:
#              \/\/(?:
#                  (?:(?:((?:[\-_.!~*'()a-zA-Z\d;:&=+$,]|%[a-fA-F\d]{2})*)@)?        (?# 3: userinfo)
#                    (?:((?:(?:[a-zA-Z0-9\-.]|%\h\h)+|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[(?:(?:[a-fA-F\d]{1,4}:)*(?:[a-fA-F\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|(?:(?:[a-fA-F\d]{1,4}:)*[a-fA-F\d]{1,4})?::(?:(?:[a-fA-F\d]{1,4}:)*(?:[a-fA-F\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))?)\]))(?::(\d*))?))? (?# 4: host, 5: port)
#                |
#                  ((?:[\-_.!~*'()a-zA-Z\d$,;:@&=+]|%[a-fA-F\d]{2})+)                 (?# 6: registry)
#                )
#              |
#              (?!\/\/))                           (?# XXX: '\/\/' is the mark for hostport)
#              (\/(?:[\-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*(?:;(?:[\-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*)*(?:\/(?:[\-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*(?:;(?:[\-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*)*)*)?                    (?# 7: path)
#            )(?:\?((?:[\-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]]|%[a-fA-F\d]{2})*))?                 (?# 8: query)
#         )
#         (?:\#((?:[\-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]]|%[a-fA-F\d]{2})*))?                  (?# 9: fragment)
#       /x


'http://example.com/' =~ URI::DEFAULT_PARSER.make_regexp
# => 0
# OK

'https://example.com/' =~ URI::DEFAULT_PARSER.make_regexp
# => 0
# OK

'h://example.com/' =~ URI::DEFAULT_PARSER.make_regexp
# => 0
# Ooops


# With arg
URI::DEFAULT_PARSER.make_regexp('http')
# => /(?=(?-mix:http):)
#         ([a-zA-Z][\-+.a-zA-Z\d]*):                           (?# 1: scheme)
#         (?:
#            ((?:[\-_.!~*'()a-zA-Z\d;?:@&=+$,]|%[a-fA-F\d]{2})(?:[\-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]]|%[a-fA-F\d]{2})*)                    (?# 2: opaque)
#         |
#            (?:(?:
#              \/\/(?:
#                  (?:(?:((?:[\-_.!~*'()a-zA-Z\d;:&=+$,]|%[a-fA-F\d]{2})*)@)?        (?# 3: userinfo)
#                    (?:((?:(?:[a-zA-Z0-9\-.]|%\h\h)+|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[(?:(?:[a-fA-F\d]{1,4}:)*(?:[a-fA-F\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|(?:(?:[a-fA-F\d]{1,4}:)*[a-fA-F\d]{1,4})?::(?:(?:[a-fA-F\d]{1,4}:)*(?:[a-fA-F\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))?)\]))(?::(\d*))?))? (?# 4: host, 5: port)
#                |
#                  ((?:[\-_.!~*'()a-zA-Z\d$,;:@&=+]|%[a-fA-F\d]{2})+)                 (?# 6: registry)
#                )
#              |
#              (?!\/\/))                           (?# XXX: '\/\/' is the mark for hostport)
#              (\/(?:[\-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*(?:;(?:[\-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*)*(?:\/(?:[\-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*(?:;(?:[\-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*)*)*)?                    (?# 7: path)
#            )(?:\?((?:[\-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]]|%[a-fA-F\d]{2})*))?                 (?# 8: query)
#         )
#         (?:\#((?:[\-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]]|%[a-fA-F\d]{2})*))?                  (?# 9: fragment)
#       /x

# with array arg
URI::DEFAULT_PARSER.make_regexp(['http','https'])
# => /(?=(?-mix:http|https):)
#         ([a-zA-Z][\-+.a-zA-Z\d]*):                           (?# 1: scheme)
#         (?:
#            ((?:[\-_.!~*'()a-zA-Z\d;?:@&=+$,]|%[a-fA-F\d]{2})(?:[\-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]]|%[a-fA-F\d]{2})*)                    (?# 2: opaque)
#         |
#            (?:(?:
#              \/\/(?:
#                  (?:(?:((?:[\-_.!~*'()a-zA-Z\d;:&=+$,]|%[a-fA-F\d]{2})*)@)?        (?# 3: userinfo)
#                    (?:((?:(?:[a-zA-Z0-9\-.]|%\h\h)+|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[(?:(?:[a-fA-F\d]{1,4}:)*(?:[a-fA-F\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|(?:(?:[a-fA-F\d]{1,4}:)*[a-fA-F\d]{1,4})?::(?:(?:[a-fA-F\d]{1,4}:)*(?:[a-fA-F\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))?)\]))(?::(\d*))?))? (?# 4: host, 5: port)
#                |
#                  ((?:[\-_.!~*'()a-zA-Z\d$,;:@&=+]|%[a-fA-F\d]{2})+)                 (?# 6: registry)
#                )
#              |
#              (?!\/\/))                           (?# XXX: '\/\/' is the mark for hostport)
#              (\/(?:[\-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*(?:;(?:[\-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*)*(?:\/(?:[\-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*(?:;(?:[\-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*)*)*)?                    (?# 7: path)
#            )(?:\?((?:[\-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]]|%[a-fA-F\d]{2})*))?                 (?# 8: query)
#         )
#         (?:\#((?:[\-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]]|%[a-fA-F\d]{2})*))?                  (?# 9: fragment)
#       /x


# with regexp arg
URI::DEFAULT_PARSER.make_regexp(/\Ahttps?/)

# => /(?=(?-mix:\Ahttps?):)
#         ([a-zA-Z][\-+.a-zA-Z\d]*):                           (?# 1: scheme)
#         (?:
#            ((?:[\-_.!~*'()a-zA-Z\d;?:@&=+$,]|%[a-fA-F\d]{2})(?:[\-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]]|%[a-fA-F\d]{2})*)                    (?# 2: opaque)
#         |
#            (?:(?:
#              \/\/(?:
#                  (?:(?:((?:[\-_.!~*'()a-zA-Z\d;:&=+$,]|%[a-fA-F\d]{2})*)@)?        (?# 3: userinfo)
#                    (?:((?:(?:[a-zA-Z0-9\-.]|%\h\h)+|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[(?:(?:[a-fA-F\d]{1,4}:)*(?:[a-fA-F\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|(?:(?:[a-fA-F\d]{1,4}:)*[a-fA-F\d]{1,4})?::(?:(?:[a-fA-F\d]{1,4}:)*(?:[a-fA-F\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))?)\]))(?::(\d*))?))? (?# 4: host, 5: port)
#                |
#                  ((?:[\-_.!~*'()a-zA-Z\d$,;:@&=+]|%[a-fA-F\d]{2})+)                 (?# 6: registry)
#                )
#              |
#              (?!\/\/))                           (?# XXX: '\/\/' is the mark for hostport)
#              (\/(?:[\-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*(?:;(?:[\-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*)*(?:\/(?:[\-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*(?:;(?:[\-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*)*)*)?                    (?# 7: path)
#            )(?:\?((?:[\-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]]|%[a-fA-F\d]{2})*))?                 (?# 8: query)
#         )
#         (?:\#((?:[\-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]]|%[a-fA-F\d]{2})*))?                  (?# 9: fragment)
#       /x


Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/2959

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

Rubyではメソッドにreturnの記述要らない?

Railsで開発している際に既存コードにreturnが書かれていなかったり、returnを書いてrubocopに指摘されたりしたので調べました。
rubyでは不要であればreturnは書かない方針のようです。

RubyではReturnの記述は不要

Rubyのメソッドでは、最後に評価された値が返るという仕様になっています。
例えば以下のような文字列を返すメソッドがあります。

def hoge(is_hoge)
  if is_hoge
    "hoge"
  else
    "fuga"
  end
end

最後に評価された値が返るので、それぞれ以下を返します。

hoge(true)  # "hoge"

hoge(false) # "fuga"

後置ifでは注意が必要

def hoge(is_hoge)
  "fuga"
  "hoge" if is_hoge
end
hoge(true) # "hoge"

hoge(false) # nil

なんとなくhoge(false)でfugaが帰って来そうですが、最後で評価された値が返るのでnilが帰ってきます。

後置ifでは、ifの右側の値が評価されたあと、trueならば左側が評価されます。
falseであればnilを返します。

例の場合、hoge(false)if is_hogeは成立しないので、左側のfugaは評価されず、
"hoge" if is_hogeが最後に評価された値となり、nilを返します。

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