- 投稿日:2020-01-25T22:53:44+09:00
【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
- 投稿日:2020-01-25T22:53:44+09:00
【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 とは何か
- 投稿日:2020-01-25T21:52:47+09:00
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は凄く自由な書き方が出来て面白いと感じます!
(ただ、これが大規模開発には向かないと言われるところなんでしょうか)今後投稿していく中で、もっと分かりやすく伝えられる方法を模索して
初学者でも理解しやすい様に心がけていきたいと思います!
- 投稿日:2020-01-25T19:42:17+09:00
テスト駆動開発から始めるRuby入門 ~ソフトウェア開発の三種の神器を準備する~
エピソード2
初めに
この記事は テスト駆動開発から始めるRuby入門 ~2時間でTDDとリファクタリングのエッセンスを体験する~ の続編です。
自動化から始めるテスト駆動開発
エピソード1ではテスト駆動開発のゴールが 動作するきれいなコード であることを学びました。では、良いコードを書き続けるためには何が必要になるでしょうか?それはソフトウェア開発の三種の神器と呼ばれるものです。
今日のソフトウェア開発の世界において絶対になければならない3つの技術的な柱があります。
三本柱と言ったり、三種の神器と言ったりしていますが、それらは
バージョン管理
テスティング
自動化
の3つです。
バージョン管理 と テスティング に関してはエピソード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 correctedclass 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 Rakefilerequire '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 install
はbundle
に省略できます。$ 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
を実行すれば良いコードを書くためのタスクを自動でやってくるようになるのでコードを書くことに集中できるようになりました。では、次のエピソードに進むとしましょう。
- 投稿日:2020-01-25T18:42:07+09:00
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
- 投稿日:2020-01-25T18:37:57+09:00
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
- 投稿日:2020-01-25T18:34:12+09:00
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
- 投稿日:2020-01-25T18:27:32+09:00
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
- 投稿日:2020-01-25T18:26:13+09:00
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
- 投稿日:2020-01-25T18:24:46+09:00
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
- 投稿日:2020-01-25T18:12:25+09:00
Docker-composeでbundle installしてるのにgemの内容反映されないんですけど
注意事項
完全に個人用メモです。公式情報でもなんでもありません。ググってこの記事に来てしまった方はお気をつけください。違っていたら即直しますのでガンガンご指摘ください。(個人用メモなので追加もしていきます)
そもそもbundle installしたらサーバー再起動しろや
なので、gemfile編集してbundle installしたら以下のコマンド打てば良い(環境によるけど)
docker-compose restart
・・・コンテナ再起動そしたらGemの内容が反映されます。
docker勉強不足だって、はっきりわかんだね。
イメージをビルドしなおしたりコンテナ削除したりしなくて良い
dockerを理解してないと以下のようなコマンドを打ち始めます
docker-compose build
・・・イメージビルド
docker-compose up -d
・・・コンテナ作成&起動結果、コンテナが増えまくるわけです。(えぇ。。。)
コンテナやたら多いと思ったら原因これかよw(錯乱)
まあこの方法でも解決できるんですけどね。。
- 投稿日:2020-01-25T16:56:13+09:00
Docker + rails + mysqlで開発環境を構築
はじめに
今回は、railsの開発環境をDocker+rails+mysqlで用意します。
構成
構成はローカルのMacにDockerを立て、webとdbコンテナをdocker-composeで管理します。
開発環境
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
「Download Desktop and Take a Tutorial」のボタンをクリックすると画面が遷移し、
アカウントがある人はログイン、ない人は「Sign Up」を押してアカウントを作成します。アカウント作成後、ログインし 「Get started with Docker Desktop」を押すと画面が遷移するので「Download Docker Desktop for Mac」を押しダウンロードします。
ダウンロード完了後、インストールします。
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: fec3683web(rails)コンテナの構築
開発を行うために任意の作業ディレクトリを作成します。
$ mkdir task_app/ $ cd task_appGemfileの準備
オプションでディレクトリのマウントを指定していることで、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/GemfileGemfileがローカルに作成されますので、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.rbRailsからデータベースへ接続するための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/ でアクセスして確認します。
見慣れた画面が出ましたね!
まとめ
Docker環境で簡単にrailsの開発環境が用意できました!
ちなみにDockerのキャラクターは、Moby(モビー)って言うらしいです。
- 投稿日:2020-01-25T16:24:02+09:00
【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_selfselfを使用するには、classを定義しなければいけなかったので、
最初はどうしようかと思いましたが、
return_to.my_self
というメソッドで浜田麻里が表示されたら一番美しいのではないか?
と考え、Return_to
というclassを定義しました。引数を「浜田」と「麻里」にして、それをくっつけた文字列が
return
で戻り値として、
呼び出したメソッド、つまりreturn_to.my_self
に返すようにしています。selfメソッドはclassを省略して書くことができるので、
ここで言うところのReturn_to.hoge
をself.hoge
とすることがでるそうで、
initialize
のところでは、引数で受け取った「浜田」と「麻里」を代入する時に、
self.to
、self.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」(リターン トゥー マイセルフ)は、米倉千尋の13thシングルである。初回盤は「カード(モンコレナイト)」を封入している。
- 投稿日:2020-01-25T15:36:38+09:00
メタプログラミング 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.rbclass 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.rbclass C; end C.class_eval do define_method :my_method do "動的メソッド" end end obj = C.new obj.my_method # => "動的メソッド"ざっくりまとめると
- 上記のようなRubyの技術を駆使してコードのリファクタなどを例を用いてわかりやすく説明してくれています。
- ActiveRecordなどのRailsのコードもどのようなスキルを駆使して書かれているか説明してくれているので、難しいコードを読んだことない人にとってはとっつきやすいかもしれません。
- 本書の最後のほうでも言っているのですが、メタプログラミングがどうとか考えずにただのプログラミングだと思えという言葉がなぜかしっくりきました。 (自分のバイアスがかかっていただけかも?)
- Rubyの「特徴」を事細かに説明してくれている良書だと思います。
- 投稿日:2020-01-25T14:55:51+09:00
【Rails】Railsとdeviseのデフォルトバリデーションを解除する方法
はじめに
知人がemailの正規表現を使ったバリデーションを自ら作成して設定しようとした際、うまく設定を反映させられませんでした
いろいろ調べたところRailsのバリデーションやらdeviseのバリデーションやらが絡んでいたためでした
今まで知らないで使ってたことなど勉強になったことが多かったので整理してまとめておきます対象読者
- form_withやform_forが何かをなんとなく理解している人
- バリデーションが何かをなんとなく理解してる人
環境
- Ruby: 2.5.1
- Rails: 5.2.3
状況
- 正規表現で~@~.~という形じゃないとアドレスとして登録できないようなカスタムバリデーションを作成した
- そのバリデーションを有効にしたが、下記の画像のような謎のバリデーションにひっかかった
自分で作ったカスタムバリデーションじゃないのがでてきた。。。ってなったんですが
結論、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先ほどのバリデーションは消えましたが、フラッシュメッセージにバリデーションが2つ出てきてしまいました
deviseのバリデーション設定
今のままだとバリデーションが2つ出てきてしまいます。先ほどの画像の左側がdeviseのバリデーション、右側が自分で設定したカスタムバリデーションです
今回は自分が設定したカスタムバリデーションだけ表示させたいので、deviseのバリデーション設定を解除します通常、deviseをインストールした場合、モデル内に下記のような記述があると思います
models/user.rbdevise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatableこの:validatableがdeviseのバリデーションなので、この記述を消します
models/user.rbdevise :database_authenticatable, :registerable, :recoverable, :rememberableこれでdeviseのデフォルトのバリデーションも解除できました
結果はこちら
ちゃんと自分で設定したバリデーションだけが適用されていますね
*カスタムバリデーションの作成の仕方は最後の参考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
- 投稿日:2020-01-25T14:36:47+09:00
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 end4.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 endgetsで入力をしていただき、その数値が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 endbattleにcpuの合計を取得します。
sumは自分の手札の合計です。
cpuが22以上のとき(→cpuがバースト+勝ち)、負け、引き分け、勝ち
の4通りの分岐をして終了となります。終わりに
初めてコードを0から書くということをしてみましたが、やはり最初が肝心だなと感じました。
ブラックジャックというルールが決まっているゲームにも関わらず、「エースの処理をしなきゃ」、「エースと絵柄は文字列だから合計を別で計算しなきゃ」と色々いじって時間がかかってしまいました。progateとかドットインストールとかRuby関連の書籍とか1冊読めば誰でも作れると思います。
こいつよりきちんとしたの作れるというモチベーションになれば幸いです。読んでいただいた方でお気づきの方はいると思いますが、
・クラスとインスタンスを使っていない
ので、修正していきたいなと思います。機能的には「お金を賭ける」、「cpuもドローする」、「複数人でプレイできるようにする」などありますので、
スキルを上げて実装していきたいです!あと、エースを引いて11を選んだ後に1に変えることができず、バーストになってしまうのはクソゲーですね。。
直します。。keyword:
Array,配列,引数,.div,sample,sum,inject,ziptwitter:https://twitter.com/okiku_engin
- 投稿日:2020-01-25T14:22:27+09:00
devise 導入直後のエラー(docker)
概要
[Rails] deviseの使い方(rails4版)
このサイト↑に沿ってdevise導入し、そこでこのエラーが発生しました。
その対処法を記載をしています。エラーの内容
ルーティングエラー
Rails routes で調べても特に問題なさそう・・
Rails: deviseをインストールした後のエラー
これ↑を実装したが、エラー治らず・・・対処法
問題は恐らくdockerなのでは?となり、とりあえず
docker再起動→ローカルを再起動docker再起動方法
右上にあるクジラを押す→restart 完了!!
それでもう一度localhostをリロードすると治りました。
お疲れさまでした!!
- 投稿日:2020-01-25T13:43:11+09:00
日本語に翻訳した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.rbBundler.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
- 投稿日:2020-01-25T12:50:22+09:00
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のドキュメントでは上記の仕様がはっきり明記されていたみたいですね。
それ消さないで欲しかった。参考
- 投稿日:2020-01-25T12:32:40+09:00
【翻訳】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は多数の要素(
path
やquery
など)から成り立っており、その要素をすべて同じ方法でエスケープするわけではないからです。たとえば、#
という文字について考えてみましょう。この文字は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_component
やURI.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
以外にも以下のようなメソッドが紹介されています。
- 投稿日:2020-01-25T11:41:33+09:00
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 ]
- 投稿日:2020-01-25T11:41:14+09:00
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
はどちらかがnil
でfalse
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.
- 投稿日:2020-01-25T10:24:47+09:00
【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実装手順
入力フォームを実装済み、プラグインを導入済みであることを前提としています。
パスワードと確認用パスワードのバリデーションチェック
完成イメージ
解説・サンプルコード
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_errorjquery.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(); }); });エラーメッセージの表示位置の設定
入力欄が横並びになっている場合、エラーメッセージはデフォルトでは各入力欄の直後にエラーメッセージが表示されます。そのため、入力欄が縦並びに変わってしまいます。そのようなときは本設定でメッセージの位置を指定できます。
完成イメージ
解説・サンプルコード
以下のようにエラーメッセージを表示させたい場所の直前に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)完成イメージ
解説・サンプルコード
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つの入力パターンが想定されます。
- パスワードを変更しない場合:現在のパスワードの入力が必須
- パスワードを変更する場合:確認用パスワードが必須、現在のパスワードの入力は不要
バリデーションチェックを上記どちらでも対応できるように実装します。
完成イメージ
パスワードを変更しない場合:現在のパスワードの入力が必須
パスワードを変更する場合:確認用パスワードが必須、現在のパスワードの入力は不要
解説・サンプルコード
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でセレクトボックスのチェック、他カスタマイズ色々
- 投稿日:2020-01-25T10:15:13+09:00
【Rails】 独自 Validation 作成方法(複数モデル適用)
はじめに
バリデーションの作成方法はモデル内にprivateメソッドを作って、そのメソッドをvalidateの引数に指定する方法が一般的かと思います。
今回は、複数のモデルで同じバリデーションを適用したいケースへの対応方法をまとめました。独自バリデーション作成方法
やること
開始日時カラムと終了日時カラムを持ったモデルの入力フォームに、
どちらのカラムも入力するか、どちらのカラムも入力しないバリデーションをかけたい。Concern
まずは、
app/models/concerns
の直下にバリデーション専用のクラスを作成する。
Concernsを使用することで、同じカラムを持った他のモデルにも適用することができ、さらに細かい継承関係を考えなくてもよくなります。app/models/concerns/work_datetime_validation.rbclass 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 endModel
次にModelに
validates_with
を用いて、先ほど作成したクラスを指定する。他のModelにも適用させたい場合は、
validates_with
を他のクラスに書けばバリデーションが適用されます。app/models/work.rbclass Work < ApplicationRecord validates_with WorkDatetimeValidation end以上となります。
まとめ
時間に関するバリデーションは複数のモデルで使用したいというニーズがあると思いますので、ぜひ活用してみてください。
- 投稿日:2020-01-25T08:30:32+09:00
#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) # /xOriginal by Github issue
- 投稿日:2020-01-25T02:09:36+09:00
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 endhoge(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を返します。