こんにちは、鈴木です。
Rails の View で時間を選択するプルダウンを作りたい場合には、collection_select などのメソッドを使用すると思います。
collection_select で使用する「時間を保持する Hash」ですが、どのように作りますか?
以下のような HOURS という Hash が定義されており、
1 |
HOURS = {0 => '0時', 1 => '1時', 2 => '2時', ..., 23 => '23時'} |
View では次のように使用する想定です。
1 |
<%= form.collection_select(:hour, HOURS, :first, :last) %> |
ここでの HOURS をどのように定義しますか、という話です。
愚直に書いてみる
試しに 愚直に書いてみます。
1 2 3 4 |
HOURS = {0=>"0時", 1=>"1時", 2=>"2時", 3=>"3時", 4=>"4時", 5=>"5時", 6=>"6時", 7=>"7時", 8=>"8時", 9=>"9時", 10=>"10時", 11=>"11時", 12=>"12時", 13=>"13時", 14=>"14時", 15=>"15時", 16=>"16時", 17=>"17時", 18=>"18時", 19=>"19時", 20=>"20時", 21=>"21時", 22=>"22時", 23=>"23時"} |
少しボリュームがありすぎる感じがします。
Enumerable#map と Hash[] を使う
Enumerable#map と Hash[] を組み合わせると、以下のように記述することができます。
1 |
HOURS = Hash[(0..23).map{|x| [x, sprintf('%d時', x)]}] |
1 行にまとまり、すっきりしました。
ポイントは (0..23) を map して、「[[0, '0時'], [1, '1時'], [2, '2時"], ..., [23, '23時']]」という配列を作成し、それを Hash[] に渡している点です。
分を保持する Hash
今度は「分」を保持する Hash を考えてみましょう。
以下のような 0 分から 59 分までの時間を保持する定数 MINUTES を初期化することを考えます。
1 |
MINUTES # => {0=>"0分", 1=>"1分", ..., 59=>"59分"} |
やはり View で時間を選択するプルダウンを作るときに使用する想定です。
1 |
<%= form.collection_select(:minute, MINUTES, :first, :last) %> |
愚直に書いてみる(2)
とりあえず愚直に書いてみます。
1 2 3 4 5 6 7 8 9 10 |
MINUTES = {0=>"0分", 1=>"1分", 2=>"2分", 3=>"3分", 4=>"4分", 5=>"5分", 6=>"6分", 7=>"7分", 8=>"8分", 9=>"9分", 10=>"10分", 11=>"11分", 12=>"12分", 13=>"13分", 14=>"14分", 15=>"15分", 16=>"16分", 17=>"17分", 18=>"18分", 19=>"19分", 20=>"20分", 21=>"21分", 22=>"22分", 23=>"23分", 24=>"24分", 25=>"25分", 26=>"26分", 27=>"27分", 28=>"28分", 29=>"29分", 30=>"30分", 31=>"31分", 32=>"32分", 33=>"33分", 34=>"34分", 35=>"35分", 36=>"36分", 37=>"37分", 38=>"38分", 39=>"39分", 40=>"40分", 41=>"41分", 42=>"42分", 43=>"43分", 44=>"44分", 45=>"45分", 46=>"46分", 47=>"47分", 48=>"48分", 49=>"49分", 50=>"50分", 51=>"51分", 52=>"52分", 53=>"53分", 54=>"54分", 55=>"55分", 56=>"56分", 57=>"57分", 58=>"58分", 59=>"59分"} |
時間(0〜23)よりも分(0〜59)の方が要素数が多いだけに、ぱっと見ただけで圧迫感があります。
Enumerable#map と Hash[] を使う(2)
時間を保持する Hash のときと同様に、Enumerable#map と Hash[] を使用してみましょう。
1 |
MINUTES = Hash[(0..59).map{|x| [x, sprintf('%d分', x)]}] |
やはり 1 行にまとまり、非常にすっきりしました。
5 分間隔の分を保持する Hash
ちょっとした応用問題です。
先程は 1 分間隔の分を保持する Hash を作成しましたが、5 分間隔の場合はどうしますか?
この場合、(0..59) を map する前に、select で不要な要素を除外すれば OK です。
1 |
MINUTES = Hash[(0..59).select{|x| x % 5 == 0}.map{|x| [x, sprintf('%d分', x)]}] |
まとめ
普段 Ruby を使われている方にとっては簡単な内容だったかと思います。
一方で、冗長な書き方のせいで見づらくなっているソースコードを見ることもあります。
言語やフレームワークのレールに乗るための基礎体力が重要だなと思う今日このごろです。
Comments
injectかeach_with_objectよりHash[Array]を使ってメリットありますか?スピードとか、メモリとか。
コメントありがとうございます。
> injectかeach_with_objectよりHash[Array]を使ってメリットありますか?スピードとか、メモリとか。
Hash[Array] が inject や each_with_object より優れている(もしくは劣っている)ということはありません。通常は読みやすいコードを書くべきなので、意図が伝わりやすい方法で Hash を構築すれば良いかと思います。
ちなみにベンチマークを取ったところ、処理時間は以下のようになりました。(Ruby-1.9.3-p327 を使用、各100,000回実行した場合の処理時間)
(1) Hash[Array] の場合 (処理時間: 12.569425)
Hash[(0..59).map{|x| [x, sprintf('%d分', x)]}]
(2) each_with_object (処理時間: 11.335173)
(0..59).each_with_object({}){|i, map| map[i] = sprintf('%d分', i)}
(3) inject (処理時間: 11.026367)
(0..59).inject({}){|map, i| map[i] = sprintf('%d分', i); map}
了解です、ベンチマークまで書いてくださってありがとうございます。