Go to list of users who liked
Share on X(Twitter)
Share on Facebook
More than 3 years have passed since last update.
[初心者向け] RubyやRailsでリファクタリングに使えそうなイディオムとか便利メソッドとか
はじめに: 遠回りせずに「近道」を探す
RubyやRailsを始めたばかりの人は、もっと短く書く方法や便利な標準ライブラリの存在を知らずに遠回りした書き方をしてしまいがちです。
そこで、RubyやRails初心者の人によく見かける「遠回り(または車輪の再発明)」と、それを回避する「近道」をいろいろ集めてみました。
2013.11.06 追記
この投稿を書くに至った経緯などを自分のブログに書きました。
こちらも合わせてどうぞ!
Ruby編
以下はRubyの標準機能を使ったイディオムやメソッドです。
Railsプロジェクトでもそれ以外でも使えます。(Ruby 1.9以上を想定)
後置ifで行数を減らす
ifuser.active?send_mail_to(user)endsend_mail_to(user)ifuser.active?if + notではなく、unlessを使う
user.destroyif!user.active?user.destroyunlessuser.active?ただし、unlessの条件がandやorでつながっていたり、否定形の条件が入っていたりすると、読み手の脳に負担がかかるので、複雑な条件はifを使う方が良いです。
# こんなunlessは理解するのに時間がかかるのでNGuser.destroyunless(user.active?||user.admin?)&&!user.spam?三項演算子を使って行数を減らす
ifuser.admin?"I appreciate for that."else"Thanks."enduser.admin??"I appreciate for that.":"Thanks"ただし、三項演算子をネストさせたりすると極めて読みにくくなるのでやめておきましょう。
# 三項演算子のネストは読みづらいuser.admin??user.active??"I appreciate for that.":"Are you OK?":"Thanks."== trueや== false、== nilを明示的に書かない
Rubyではif文などの条件分岐で、明示的に== trueや== falseを書くことは(特別な理由がない限り)ありません。
もしこんなコードを書いていたら、
iffoo==true# ...endifbar==false# ...end次のように修正しましょう。
iffoo# ...endunlessbar# ...end# またはif!bar# ...endまた、== nilについても以下のように修正できます。(nilは偽なので、falseと同じように扱う)
ifbaz==nil# ...endunlessbaz# ...end# またはif!baz# ...endもし、nilとfalseを区別する必要がある場合は、nil?メソッドを使うと良いでしょう。
ifbaz.nil?# bazがnilのときだけ実行される# (bazがfalseだったら実行されない)end代入してからifで存在を確認、をまとめて書く
user=find_userifusersend_mail_to(user)endifuser=find_usersend_mail_to(user)endただし、このイディオムは「==と=を書き間違えたんじゃないか?」と読み手に勘違いされる恐れもあるので、好き嫌いが分かれるのも事実です。
子どものオブジェクトが存在する場合にのみ、そのプロパティやメソッドを呼び出して条件を確認する、をひとつのifで書く
以下のコードは、parent.childrenがnilになっている可能性があるので、childrenが存在するときだけchildren.singleton?を呼び出したい、というようなケースです。
ifparent.childrenifparent.children.singleton?singleton=parent.children.firstsend_mail_to(singleton)endendifparent.children&&parent.children.singleton?singleton=parent.children.firstsend_mail_to(singleton)endRuby 2.3では safe navigation operator という新しい演算子(&.)が追加されました。
これを使うとnil かもしれないオブジェクトにメソッド呼び出しを試すことができます。
もしオブジェクトがnil であれば戻り値もnil になります。
# Ruby 2.3以降(childrenがnilでもエラーにならず、nilが返る)ifparent.children&.singleton?singleton=parent.children.firstsend_mail_to(singleton)endその他、Ruby 2.3の新機能についてはこちらの記事をご覧ください。
サンプルコードでわかる!Ruby 2.3の主な新機能 - Qiita
メソッドの戻り値を返すときにreturnを使わない
他の言語からやってきた人はついついreturnを使いたくなりますが、returnを使わない書き方の方がRubyっぽいです。
defbuild_message(user)message='hello'message+='!!'ifuser.admin?returnmessageenddefbuild_message(user)message='hello'message+='!!'ifuser.admin?messageend「初期化、プロパティセット、戻り値として返す」の代わりにObject#tapを使う
tapを使わなくても行数は同じですが、ローカル変数の宣言や値を返却するためだけに書く最後の行がいらなくなります。
defbuild_useruser=User.newuser.email="hoge@hoge.com"user.name="Taro Yamada"userenddefbuild_userUser.new.tapdo|user|user.email="hoge@hoge.com"user.name="Taro Yamada"endend"+”ではなく"#{ }"で文字列を連結する
"Hello, "+user.name+"!""Hello,#{user.name}!"複数行にわたる文字列はヒアドキュメントを使う
text="Hello, world!\nGood-bye, world!"text=<<-TEXTHello, world!Good-bye, world!TEXTRubyのヒアドキュメントは高機能なので、もっと詳しく知りたい方は公式ドキュメントやネット上の情報を参考にしてください。
定数はfreezeさせる
文字列であれ、配列であれ、ハッシュであれ、定数宣言した値はfreezeしておく方が無難です。万一変更されると困るので。
文字列の場合
CONTACT_PHONE_NUMBER="03-1234-5678"CONTACT_PHONE_NUMBER<<"@#$%^"putsCONTACT_PHONE_NUMBER# => 03-1234-5678@#$%^CONTACT_PHONE_NUMBER="03-1234-5678".freezeCONTACT_PHONE_NUMBER<<"@#$%^"# => RuntimeError: can't modify frozen String配列の場合
ADMIN_NAMES=["Tom","Alice"]ADMIN_NAMES<<"Taro"ADMIN_NAMES[0].downcase!pADMIN_NAMES# => ["tom", "Alice", "Taro"]ADMIN_NAMES=["Tom","Alice"].freeze.each(&:freeze)ADMIN_NAMES<<"Taro"# => RuntimeError: can't modify frozen ArrayADMIN_NAMES[0].downcase!# => RuntimeError: can't modify frozen String整数の場合
整数(FixNum)は変更不能なのでfreezeしなくても問題ありません。
# エラーにはならないが、あまり意味が無いITEM_LIMIT=500.freeze配列やハッシュを初期化する際、最後の要素をあえてカンマ付きで書いておく
配列やハッシュを初期化する場合は、カンマで要素を区切ります。
countries=[:japan,:italy,:uk]capitals={japan:'Tokyo',italy:'Rome',uk:'London'}Rubyでは次のように、最後の要素にカンマを付けても文法上エラーになりません。
countries=[:japan,:italy,:uk,]capitals={japan:'Tokyo',italy:'Rome',uk:'London',}将来的に要素が追加される可能性が高い配列やハッシュであれば、1つ前の行を修正せずに新しい要素を追加できるので、プログラム修正の手間が少し省けます。
countries=[:japan,:italy,:uk,:india,# <= 1つ前の行を修正せずに追加]capitals={japan:'Tokyo',italy:'Rome',uk:'London',india:'New Delhi',# <= 1つ前の行を修正せずに追加}また、すべての要素にカンマを付けておけば、要素の順番を入れ替えたいときも行単位の単純なカット&ペーストで修正できますね。
配列を作るとき、[ ]の代わりに%w( )、%i( )を使う
文字列だけの配列を作りたい場合は%w( )を使うと少し短く書けます。
actions=['index','new','create']actions=%w(index new create)# => ['index', 'new', 'create']Ruby 2.0なら%i( )でシンボルの配列も作れます。
actions=%i(index new create)# => [:index, :new, :create]配列を順番に処理するとき、"object.method"の代わりに"&:method"を使う
names=users.map{|user|user.name}names=users.map(&:name)mapに限らず、eachやselectなどブロックで配列の中身を受け取るようなメソッドは同じように&:methodで処理できます。
nilか配列かを区別せず、Array( )で処理してしまう
基本的に配列だが、nilが渡される場合もある変数を処理する場合、Array()(Kernel#Array)を使うと条件分岐を無くせます。
# usersはnilが渡される場合もあるので分岐するifusersusers.each{|user|send_direct_mail(user)}end# Array()を使うと、nilの場合は空の配列([])が、それ以外は元の配列が返されるので分岐が不要Array(users).each{|user|send_direct_mail(user)}大きな数値を宣言する場合、"_"を入れて読みやすくする
ITEM_LIMIT=1000000000ITEM_LIMIT=1_000_000_000単純なgetterメソッドを定義する代わりに、attr_readerを使う
classPersondefinitialize@name="No name"enddefname@nameendendclassPersonattr_reader:namedefinitialize@name="No name"end# いらない# def name# @name# endend要素の順番に意味がある配列は、同時に別々の変数で受け取る
変数 = 配列のように書くと変数には配列が格納されますが、変数, 変数 = 配列のように書くと配列の各要素を別々の変数に格納できます。
ans_array=14.divmod(3)puts"商は#{ans_array[0]}"# => 商は4puts"あまりは#{ans_array[1]}"# => あまりは2quotient,remainder=14.divmod(3)puts"商は#{quotient}"# => 商は4puts"あまりは#{remainder}"# => あまりは2ハッシュをeachで回したときに、ブロックが受け取る引数も同じですね。
# keyとvalueを配列として受け取る{name:'Tom',email:'hoge@hoge.com'}.eachdo|key_and_value|puts"key:#{key_and_value[0]}"puts"value:#{key_and_value[1]}"end# keyとvalueを別々の変数で受け取る{name:'Tom',email:'hoge@hoge.com'}.eachdo|key,value|puts"key:#{key}"puts"value:#{value}"end配列を連結するのに+ではなく、*(splat)を使う
numbers=[1,2,3]numbers_with_zero_and_100=[0]+numbers+[100]# => [0, 1, 2, 3, 100]numbers=[1,2,3]numbers_with_zero_and_100=[0,*numbers,100]# => [0, 1, 2, 3, 100]ちなみに*がないと、こうなります。(配列が展開されない)
[0,numbers,100]# => [0, [1, 2, 3], 100]nilだったら初期化、の代わりに ||= を使う
いわゆる遅延初期化のイディオムですね。
deftwitter_client@twitter_client=Twitter::REST::Client.newif@twitter_client.nil?@twitter_clientenddeftwitter_client@twitter_client||=Twitter::REST::Client.newend初期化の処理が複数行にわたる場合はbegin/endを使うことができます。(参考)
deftwitter_clientreturn@twitter_clientif@twitter_client@twitter_client=Twitter::REST::Client.new# 初期化に必要な処理# ...@twitter_clientenddeftwitter_client@twitter_client||=beginclient=Twitter::REST::Client.new# 初期化に必要な処理# ...clientendendハッシュのキーには文字列ではなくシンボルを使う
ハッシュに値をセットする場合、キーには文字列よりもシンボルを使う方がベターです。
# キーに文字列を使うcurrencies={'japan'=>'yen','america'=>'dollar','italy'=>'euro'}currencies['japan']# => 'yen'# キーにシンボルを使うcurrencies={japan:'yen',america:'dollar',italy:'euro'}currencies[:japan]# => 'yen'シンボルを使うと以下のようなメリットがあります。
{ key: value }のように簡潔なリテラルで書ける。- 文字列よりも速い。
- 文字列よりもメモリの使用効率が良い。
参考:Why use symbols as hash keys in Ruby? - Stack Overflow
メソッド全体rescueの対象にするときはbegin/endを省く
defprocess_user(user)beginsend_to_mail(user)rescue# 例外処理endenddefprocess_user(user)send_to_mail(user)rescue# 例外処理endExceptionをrescueするのではなく、StandardErrorをrescueする
JavaやC#をやっていた人は「すべての例外を捕捉したい = Exceptionを捕捉する」と考えがちです。
しかし、RubyでExceptionを捕捉すると、NoMemoryError等の致命的な例外も捕捉してしまいます。
実行時エラーを表すRubyの例外クラスはExceptionのサブクラスであるStandardErrorです。
rescueでデフォルトで捕捉するのはStandardErrorとそのサブクラスなので、すべての実行時エラーを捕捉したい場合はrescue節に具体的な例外クラス名を書く必要はありません。
defprocess_user(user)send_to_mail(user)rescueException=>ex# NoMemoryError等の致命的な例外まで捕捉してしまうので良くないenddefprocess_user(user)send_to_mail(user)rescue=>ex# すべての実行時エラー(= StandardErrorとそのサブクラス)が捕捉されるend一度rescueした例外をもう一度再raiseする
「ある特定の例外クラス」だけでなく、「例外メッセージの中身」も確認して条件に合致すればrescue、そうでなければ対象外のエラーなのでそのままシステムエラーにしたい、というケースがたまにあります。
その場合はrescue節の中でraiseを呼ぶと元のエラーを再raiseできます。
defprocess_user(user)send_to_mail(user)rescueArgumentError=>exifex.message=~/blah blah blah/# ArgumentErrorかつ、メッセージも条件に合致すれば# 別の処理を実行してそのまま続行するsend_to_admin(user,ex)else# メッセージが条件に合致しなかった場合は対処不能なエラーとして# 元のエラーを再度raiseするraiseendendprivate 以下の行にクラスメソッドを定義して、privateなクラスメソッドを作ったと勘違いしない
以下のようにprivateの下にインスタンスメソッドとクラスメソッドを定義したとします。
classUserprivatedefsecret_name# 外部から呼ばれたくないインスタンスメソッド"secret!"enddefself.secret_data# 外部から呼ばれたくないクラスメソッド!?"secret!!"endend上のコードを実行すると、secret_nameは呼び出せませんが、self.secret_data呼び出すことが可能です。
user=User.new# 呼び出せないuser.secret_name# => NoMethodError: private method `secret_name' called for #<User:0x007fa90c1e7cd0># 呼び出せるUser.secret_data# => "secret!!"以下のようにprivate_class_methodを付けると、外部から呼び出せなくなります。
classUserprivatedefsecret_name# 外部から呼ばれたくないインスタンスメソッド"secret!"enddefself.secret_data# 外部から呼ばれたくないクラスメソッド"secret!!"end# secret_dataをprivateなクラスメソッドにするprivate_class_method:secret_dataendUser.secret_data# => NoMethodError: private method `secret_data' called for User:Classですが、privateなインスタンスメソッドを作るときに比べると手間がかかるので、「どうしても」という場合以外は「クラスメソッドはpublicのままにしておく」という選択肢もアリかもしれません。
(そもそもクラスメソッドを多用しすぎている場合は、クラス設計に何か問題がある可能性が高いです)
参考:クラスメソッドのprivate化 - @tmtms のメモ
privateメソッドはそのクラスでしか呼び出せない、と勘違いしない
以下のようにprivateなインスタンスメソッド(secret_price)を定義したとします。
classItemprivatedefsecret_price1000endendさらに、上のItemクラスを継承したクラスを定義します。この中でsecret_priceメソッドを呼び出すようにします。
classBook<Itemdefpublic_pricesecret_priceendendすると、Bookクラスのインスタンスから問題なく(public_priceメソッド経由で)secret_priceメソッドを呼び出すことができます。
book=Book.newbook.public_price# => 1000Javaや.NETの経験が長いと、privateメソッドはそのクラスでしか呼び出せないと思ってしまいがちですが、Rubyでは継承先でprivateメソッドが呼ばれる可能性があります。
そのクラスの中で呼び出されていないからといって安易にprivateメソッドを削除すると、継承先のクラスで呼び出されたときにエラーが発生するかもしれないので気をつけてください。
参考:JavaやC#の常識が通用しないRubyのprivateメソッド - give IT a try
size - 1ではなく、マイナスのインデックスで最後の文字や要素を指定する
numbers=[1,2,3,4,5]name='Taro Yamada'numbers[numbers.size-1]# => 5name[name.size-1]# => 'a'numbers[1..numbers.size-2]# => [2, 3, 4]name[1..name.size-2]# => "aro Yamad"numbers=[1,2,3,4,5]name='Taro Yamada'numbers[-1]# => 5name[-1]# => 'a'numbers[1..-2]# => [2, 3, 4]name[1..-2]# => "aro Yamad"配列の便利なメソッドいろいろ
find: 最初に見つかったものを返す
deffind_admin(users)users.eachdo|user|returnuserifuser.admin?endnilenddeffind_admin(users)users.find(&:admin?)end最初の見つかった要素のインデックスを返す場合はfind_index。
select: 条件に合うものすべてを返す
deffind_admins(users)admins=[]users.eachdo|user|admins<<userifuser.admin?endadminsenddeffind_admins(users)users.select(&:admin?)endselectとは反対でfalseになる要素だけを集める場合はreject。
count: 条件に合う要素の数を返す
defcount_admin(users)count=0users.eachdo|user|count+=1ifuser.admin?endcountenddefcount_admin(users)users.count(&:admin?)endmap: ある配列から別の配列を作る
defuser_names(users)names=[]users.eachdo|user|names<<user.nameendnamesenddefuser_names(users)users.map(&:name)endflat_map: mapの結果をネストしないフラットな配列として受け取る
nested_array=[[1,2,3],[4,5,6]]mapped_array=nested_array.map{|array|array.map{|n|n*10}}# => [[10, 20, 30], [40, 50, 60]]flat_array=mapped_array.flatten# => [10, 20, 30, 40, 50, 60]nested_array=[[1,2,3],[4,5,6]]flat_array=nested_array.flat_map{|array|array.map{|n|n*10}}# => [10, 20, 30, 40, 50, 60]compact: nil以外の要素を集める
numbmers_and_nil=[1,2,3,nil,nil,6]only_numbers=numbmers_and_nil.reject(&:nil?)# => [1, 2, 3, 6]numbers_and_nil=[1,2,3,nil,nil,6]only_numbers=numbers_and_nil.compact# => [1, 2, 3, 6]filter_map: mapした上で偽の要素を除外
(2022.2.1 追記)
Ruby 2.7以降ではmapした上で偽の要素(nilまたはfalse)を除外してくれるfilter_mapメソッドが使えます。
# mapとcompactを組み合わせて偶数の要素だけ値を10倍する(奇数は要素から除外)[1,2,3,4,5].map{|n|n*10ifn.even?}.compact#=> [20, 40]# filter_mapを利用して偶数の要素だけ値を10倍する(奇数は要素から除外)[1,2,3,4,5].filter_map{|n|n*10ifn.even?}#=> [20, 40]any?: 最低でも1つ条件に合う要素があればtrueを返す
defcontains_nil?(users)users.eachdo|user|returntrueifuser.nil?endfalseenddefcontains_nil?(users)users.any?(&:nil?)endすべての要素が条件に合っている場合にtrueを返す場合はall?。
empty?: 1件もなければtrueを返す
puts"empty!"ifusers.size==0puts"empty!"ifusers.empty?first/last: 最初と最後の要素を返す
first_user=users[0]last_user=users[users.size-1]first_user=users.firstlast_user=users.lastsample: 任意の要素を返す
users[rand(users.size)]users.sampleeach_with_index: eachでループしつつ、カウンタも同時に取得する
counter=0users.eachdo|user|puts", "ifcounter>0putsuser.namecounter+=1endusers.each_with_indexdo|user,counter|puts", "ifcounter>0putsuser.nameendループ処理系のメソッド + with_index: カウンタ付きで元のループ処理を実行する
counter=1users_with_index=users.mapdo|user|[counter,user]counter+=1endusers_with_index=users.map.with_indexdo|user,counter|[counter+1,user]endwith_indexはカウンタの初期値を指定できます。(デフォルトはゼロ)
なので、上のコードは次のように書いても同じです。
users_with_index=users.map.with_index(1)do|user,counter|[counter,user]endjoin: 配列を1つの文字列として返す
defnumbers_text(numbers)text=''numbers.each_with_indexdo|number,i|text+=', 'ifi>0text+=number.to_sendtextenddefnumbers_text(numbers)numbers.join(', ')# [1, 2, 3] => "1, 2, 3"endmax/max_by: 最大の要素を返す
defoldest_user(users)oldest=nilusers.eachdo|user|oldest=userifoldest.nil?||user.age>oldest.ageendoldetenddefoldest_user(users)users.max_by(&:age)end単純な数値や文字列の配列ならnumbers.maxだけでもOK。
最小の要素を返す場合はminやmin_byを使う。
each_with_object: ループを回しつつ、別のオブジェクトを組み立ててそれを返す
defadmin_names(users)ret=[]users.eachdo|user|ret<<user.nameifuser.admin?endretenddefadmin_names(users)users.each_with_object([])do|user,names|names<<user.nameifuser.admin?endendまあ、上のサンプルのような場合はusers.select(&:admin?).map(&:name)って書けばいいんですけどね。
2022.2.18追記
この場合だとfilter_mapを使うのが一番シンプルに済みそうです。
defadmin_names(users)users.filter_mapdo|user|user.nameifuser.admin?endendto_h: ループを回しつつ、新たにハッシュオブジェクトを組み立ててそれを返す
names=%w(Alice Bob Carol)users={}names.eachdo|name|users[name]=find_user(name)endnames=%w(Alice Bob Carol)users=names.to_hdo|name|# 最初の要素がキー、2番目の要素が値となる配列をブロックの戻り値として返す[name,find_user(name)]endちなみに上の書き方ができるのはRuby 2.6以降です。それまでは次のようなコードをよく書いていました。
names=%w(Alice Bob Carol)# 以下はちょっと古い書き方# mapとto_hを組み合わせて使うusers=names.mapdo|name|[name,find_user(name)]end.to_h# each_with_objectを使うusers=names.each_with_object({})do|name,h|h[name]=find_user(name)endその他の情報源
全部挙げていくとキリがないので、配列を操作するロジックを書く前にまず、ArrayやEnumerableのAPIドキュメントを読んで「車輪の再発明」をしていないかチェックしてください。
英語の文法や品詞を意識する
ケースバイケースで原則から外れる場合は十分ありえますが、文法や品詞の使いわけに明らかな逸脱(間違い)があると読み手が混乱します。
配列の変数名や配列を返すメソッド名は原則複数形にする
# number = [1, 2, 3]numbers=[1,2,3]# def find_even_number(numbers)# numbers.select(&:even?)# enddeffind_even_numbers(numbers)numbers.select(&:even?)endプロパティや変数名、クラス名は原則名詞や形容詞に、何かを操作するメソッドは原則動詞にする
# reserve=予約する(動詞)、reserved=予約済みである(形容詞)# chair.reserve?chair.reserved?# => false# chair.reserved('Tom')chair.reserve('Tom')chair.reserved?# => trueその他、英語の使い方については以下の記事も参考になると思うので、あわせて読んでみてください。
モデルやメソッドに名前を付けるときは英語の品詞に気をつけよう
直接実行したときだけ実行し、requireされたときは実行しないコードを書く
(2020.9.13追記)
たとえば、以下のようなコード(hello.rb)を書いたとします。
defhello(name)puts"Hello,#{name}!"endhello("Alice")あたりまえですが、次のようにすればコマンドラインから実行可能です。
$ ruby hello.rbHello, Alice!ただし、次のようにhello.rbをrequireしてhelloメソッドを使いたいときはちょっと問題があります。
require'./hello'hello("Bob")このコード(runner.rb)を実行すると、次のように"Hello"の文字が2回表示されてしまいます。
$ ruby runner.rbHello, Alice!Hello, Bob!これはなぜかというと、hello.rbをrequireしたタイミングで、hello.rbに書いたhelloメソッドの呼び出しが実行されてしまうからです。
defhello(name)puts"Hello,#{name}!"endhello("Alice")# ←requireしたときにこの行が実行されるこの問題を避けるためには、if __FILE__ == $0という条件分岐を入れます。
defhello(name)puts"Hello,#{name}!"endif__FILE__==$0hello("Alice")endこうするとrequireしてもhello.rbのhelloメソッドの呼び出しが実行されません(runner.rbに書いたメソッド呼び出しだけが実行されます)。
$ ruby runner.rbHello, Bob!一方、hello.rbを単体で動かした場合は従来通り、helloメソッドの呼び出しが実行されます。
$ ruby hello.rbHello, Alice!FILE と $0 はいったい何か
__FILE__は「現在のソースファイル名」を表す擬似変数です(参考)。
一方、$0は「現在実行中の Ruby スクリプトの名前を表す文字列」です。(参考)
__FILE__ == $0が真になるということは、「現在のソースファイル名と現在実行中のRubyスクリプト名が一致する」ということです。つまり、「自分自身が直接実行されている」ということを意味します。
それ以外の場合は「別の場所から呼ばれている(requireされて使われている)」ということになります。
先ほどのコードを少し修正して、__FILE__と$0に何が入っているか、確認してみましょう。
defhello(name)puts"Hello,#{name}!"end# __FILE__と$0の中身を確認するputs"#{__FILE__} |#{$0}"if__FILE__==$0hello("Alice")end実行結果は次のようになります。
$ ruby hello.rb hello.rb | hello.rbHello, Alice!$ ruby runner.rb /your-path/to/hello.rb | runner.rbHello, Bob!!ruby hello.rbを直接実行した場合は__FILE__と$0が一致し、ruby runner.rbを実行した場合(requireされた場合)は__FILE__と$0が異なることがわかります。
(2020.9.14追記)
$0の代わりに$PROGRAM_NAMEを使うこともできます(参考)。
この方が可読性が高いですね。RuboCopもこちらの書き方を推奨しているみたいです。
defhello(name)puts"Hello,#{name}!"endif__FILE__==$PROGRAM_NAMEhello("Alice")endRails編
以下はRails開発時にのみ使えるイディオムやメソッドです。
標準のRubyでは用意されていないので使えません。
ただし、大半のメソッドはActiveSupportのGemを導入することで使えるようになります。
$ gem install active_supportrequire'active_support/all'nil.blank?# => truenilチェックの代わりにObject#try(:method_name)を使う
ifparent.children&&parent.children.singleton?singleton=parent.children.firstsend_mail_to(singleton)end# childrenがnilならtry(:singleton?)はnilを返す# nilでなければ、children.singleton?が普通に呼ばれるifparent.children.try(:singleton?)singleton=parent.children.firstsend_mail_to(singleton)end「nil、もしくは空っぽい値」のチェックにblank?/present?を使う
# Stringname=nilname.blank?# => truename=""name.blank?# => truename=" "name.blank?# => truename="Tom"name.blank?# => false# Arraynumbers=nilnumbers.blank?# => truenumbers=[]numbers.blank?# => truenumbers=[1,2,3]numbers.blank?# => false# Hashparams=nilparams.blank?# => trueparams={}params.blank?# => trueparams={name:"Tom",email:"hoge@hoge.com"}params.blank?# => falsepresent?はblank?の反対で、「空ではない値」のときにtrueを返します。
# Stringname=""name.present?# => falsename="Tom"name.present?# => true「空なら別の値を代入」の代わりにpresenceを使う
ifuser.name.blank?name="What's your name?"elsename=user.nameendname=user.name.presence||"What's your name?""".presenceや[].presenceはnilを返すので注意してください。(blank?かどうかを判別しているため)
name=""putsname.presence||"What's your name?"# => What's your name?2014.11.12 追記presence を使うと便利なイディオムがありました。
# Newsが1件でも存在すればメール送信&ツイート発信good_news=company.good_newsifgood_news.count>0send_mail(good_news)tweet(good_news)end上のようなコードはpresenceを使うと一行で代入と条件判断ができます。
ifgood_news=company.good_news.presencesend_mail(good_news)tweet(good_news)endcompany.good_news.presence はcompany.good_news が0件だとnil が返るのでfalse 扱いになり、if文の中が実行されません。
同様に、「文字列に何かしらの値が入っている場合」を分岐させるケースでも役立ちます。
# nameがnilや空文字列("")だったらメッセージを表示したくないname=blog.user.nameifname.present?show_message("Hello,#{name}!")endifname=blog.user.name.presenceshow_message("Hello,#{name}!")end存在の有無を確認する場合はblank?/present?を積極的に使う
Rubyの標準APIではnil?やempty?のように「無い」を表すメソッドしかないので、「もしあるなら」をコードで表現するとぎこちなくなります。
ifuser# userがいれば、何かを実行する# =>「もしuserがいれば」ではなく「もしuserなら」と読めてしまうendunlessusers.empty?# usersが空ではないなら、何かを実行する# => empty?の逆がないので、否定形で条件を書かざるを得ないendRailsであれば、present?を使って「もしあれば」を明示的に書くことができます。
ifuser.present?# userがいれば、何かを実行する# =>「もしuserがいれば」と明示的に読めるendifusers.present?# usersに1つ以上の要素があれば、何かを実行する# => 肯定形で条件が書けるend文字列の存在チェックはnil?ではなく、blank?を積極的に使う
「文字列に値が入っていない」状態はnilと""の区別をしないことが多いと思います。
(nilは空白だが""は空白ではない、とわざわざ区別することはほとんどないはず)
nil?を使うと""は入力済みであるというような条件文を書いてしまう恐れがあります。
なのでRailsではnil?の代わりにblank?を使う癖を付けておく方が良いです。
ifemail.nil?# => emailが "" なら入力済みとして扱われてしまい、コールされないputs"Please input email!"endifemail.blank?# => emailが "" や " " の場合でも未入力と扱われるので、コールされるputs"Please input email!"end同じ理由でModelのvalidatesで指定するオプションも、特別な理由が無い限りallow_nil: trueではなく、allow_blank: trueを使うようにしましょう。
ロジックではなく、クエリでフィルタリングする
Ruby編で配列の便利なメソッドをいろいろ紹介しましたが、RailsのModelをフィルタリングしたい場合は配列を操作するのではなく、データベース(SQL)上でフィルタリングした方が効率的です。
defadmin_usersUser.all.select(&:admin?)enddefadmin_usersUser.where(admin:true)endmapではなく、pluckを使う
pluckを使うと必要なカラムだけをデータベースから取得するので処理効率が良くなります。
defadmin_user_idsUser.where(admin:true).map(&:id)enddefadmin_user_idsUser.where(admin:true).pluck(:id)endtap の代わりに new + ブロックを使う
ActiveRecordであればnew に直接ブロックを渡して、プロパティをセットすることができます。
(@sachin21 さん、コメントありがとうございます! )
defbuild_userUser.new.tapdo|user|user.email="hoge@hoge.com"user.name="Taro Yamada"endenddefbuild_userUser.newdo|user|user.email="hoge@hoge.com"user.name="Taro Yamada"endendRailsにおけるタイムゾーンの扱いを理解する
Railsの場合、application.rbに設定したタイムゾーンが使われる場合と、環境変数 TZ に設定されたタイムゾーンが使われる場合の2パターンがあります。
両者のタイムゾーン設定が異なる場合、予期せぬ不具合が生まれる恐れがあります。
そうした不具合を防ぐため、コード内ではapplication.rbに設定されたタイムゾーンを使うように統一することが望ましいです。
具体的には、Date.todayではなくDate.currentを、Time.nowではなくTime.current(またはTime.zone.now)を使うようにしてください。
詳しい内容はこちらの記事にまとめてあるので読んでみてください。
RubyとRailsにおけるTime, Date, DateTime, TimeWithZoneの違い
日付や日時の便利メソッドを活用する
システム日付から見た昨日/明日を求める
Date.current# => Tue, 05 Nov 2013Date.yesterday# => Tue, 04 Nov 2013Date.tomorrow# => # => Tue, 06 Nov 2013システム日時から見たxx年前/後、xxヶ月前/後、xx週間前/後、xx日前/後、etcを求める
Date.current# => 2013-11-052.years.ago# => 2011-11-05 06:21:40 +09002.years.since# => 2015-11-05 06:21:40 +09002.months.ago# => 2013-09-05 06:21:40 +09002.months.since# => 2014-01-05 06:21:40 +0900weeks, days, hours, minutes, secondsでも同じように使えます。
特定の日付/日時から見た昨日/明日、先週/来週、xx日前/後、etcを求める
結果を求める方法は一つだけでなく、いろいろな書き方があります。
date=Date.current# => 2013-11-05date.yesterday# => 2013-11-04date.tomorrow# => 2013-11-06date.prev_day# => 2013-11-04date.next_day# => 2013-11-06date.prev_day(2)# => 2013-11-03date.next_day(2)# => 2013-11-07date-2.days# => 2013-11-03date+2.days# => 2013-11-07date.ago(2.days)# => 2013-11-03date.since(2.days)# => 2013-11-07date.prev_month# => 2013-10-05date.next_month# => 2013-12-05date.prev_month(2)# => 2013-09-05date.next_month(2)# => 2014-01-05date-2.months# => 2013-09-05date+2.months# => 2014-01-05date.months_ago(2)# => 2013-09-05date.months_since(2)# => 2014-01-05date.ago(2.months)# => 2013-09-05date.since(2.months)# => 2014-01-05week, year等でも考え方は同じです。
Time型でも使えます。
ある日付/日時から見た始まりと終わりの日付/日時を求める
date=Date.current# => 2013-11-05date.beginning_of_month# => 2013-11-01date.end_of_month# => 2013-11-30date.beginning_of_day# => 2013-11-05 00:00:00 +0900date.end_of_day# => 2013-11-05 23:59:59 +0900datetime=Time.current# => 2013-11-05T06:43:53+09:00datetime.beginning_of_hour# => 2013-11-05T06:00:00+09:00datetime.end_of_hour# => 2013-11-05T06:59:59+09:00week, year等でも考え方は同じです。
Time型でも使えます。
1日の初めから終わりまで、月の初めから終わりまで、といった範囲を求める
all_xxx メソッドを使うと、「xxxの始めから終わりまで」をRangeオブジェクトとして取得できます。
time=Time.current# => Mon, 23 Nov 2015 16:45:23 JST +09:00# その日の始まりと終わりの日時を取得time.all_day# => Mon, 23 Nov 2015 00:00:00 JST +09:00..Mon, 23 Nov 2015 23:59:59 JST +09:00# その日から見た月の始まりと、月の終わりの日時を取得するtime.all_month# => Sun, 01 Nov 2015 00:00:00 JST +09:00..Mon, 30 Nov 2015 23:59:59 JST +09:00# その日から見た年の始まりと、年の終わりの日時を取得するtime.all_year# => Thu, 01 Jan 2015 00:00:00 JST +09:00..Thu, 31 Dec 2015 23:59:59 JST +09:00他にもall_weekやall_quarterといったメソッドが定義されています。
ある日付から見た先週/来週のxx曜日を求める
date=Date.current# => 2013-11-05date.tuesday?# => truedate.prev_week(:monday)# => 2013-10-28date.next_week(:monday)# => 2013-11-11他の曜日でも考え方は同じです。
その他の情報源
日付、日時の操作例を挙げ始めるとキリがないので、詳しくはActiveSupportのAPIを参照してください。
語形やフォーマットを変える (2013.11.14追記)
# キャメルケースにする"my_book".camelize# => "MyBook"# アンダースコア区切り(スネークケース)にする"MyBook".underscore# => "my_book"# ダッシュ(ハイフン)区切りにする"my_book".dasherize# => "my-book"# 複数形にする"book".pluralize# => "books""person".pluralize# => "people""fish".pluralize# => "fish""book_and_person".pluralize# => "book_and_people""book and person".pluralize# => "book and people""BookAndPerson".pluralize# => "BookAndPeople"# 単数形にする"books".singularize# => "book""people".singularize# => "person""books_and_people".singularize# => "books_and_person""books and people".singularize# => "books and person""BooksAndPeople".singularize# => "BooksAndPerson"# 人間が読みやすくする(一文字目は大文字 + スペース区切り)"my_books".humanize# => "My books"# タイトル形式にする(各単語の一文字目が大文字 + スペース区切り)"my_books".titleize# => "My Books"# クラス名にする(キャメルケース + 単数形)"my_book".classify# => "MyBook""my_books".classify# => "MyBook"# テーブル名にする(アンダースコア区切り + 複数形)"my_book".tableize# => "my_books""MyBook".tableize# => "my_books"その他の情報源
constantizeやdemodulizeなど、個人的に使用頻度が低そうだなと思ったメソッドは紹介しませんでした。
他のメソッドも気になる方はRailsのAPIドキュメントを参照して下さい。
余分なスペースや改行を取り除く (2013.11.14追記)
" My\r\n\t\n books ".squish# => "My books"まとめ: 良いコードを書くために
魚ではなく、「魚の釣り方」を覚える
「遠回り」や「車輪の再発明」をする前に、もっと良い書き方はないか、すでに同じメソッドが用意されていないか、書籍やAPIドキュメントをしっかり読みましょう。
- プログラミング言語 Ruby - オライリージャパン
- Rails3レシピブック 190の技 - ソフトバンククリエイティブ
- ruby-doc.org
- String
- Array
- Hash
- Enumerable
- RailsGuides
- Active Record Query Interface
チーム内でコードレビューをする
書籍やAPIドキュメントだけでなく、同僚の知見も有効な情報源です。
チーム内で定期的にコードレビューを行い、お互いの知見を交換しあいましょう。
また、コードの短さや簡潔さに凝りすぎると、独りよがりでわかりにくいコードを書いてしまう恐れもあるため、コードレビューを通じてチーム内で「わかりやすいコード」「わかりにくいコード」の判断基準を共有しておくことも重要です。
おまけ: トリビア的なテクニック
こういう書き方もあるけど無理して使わなくても良いかも、と思うような記法も紹介しておきます。
「1文字」を表すのに「?+文字」を使う
"index,new,create".split(',')# => ["index", "new", "create"]"index,new,create".split(?,)# => ["index", "new", "create"]タイプ量は減りますが、直感的なわかりやすさに関しては微妙かも・・・。
配列を順番に処理するとき、直接メソッドを呼ぶ代わりに"&method(:name)"を使う
普通にブロックを書く方が一般的ですが、&method(:name)みたいな引数を渡すこともできます。
defprocess_usersusers.eachdo|user|process_user(user)endenddefprocess_user(user)send_mail_to(user)user.mail_sent_at=Time.nowuser.saveenddefprocess_usersusers.each(&method(:process_user))enddefprocess_user(user)send_mail_to(user)user.mail_sent_at=Time.nowuser.saveend配列を順番に処理するとき、ブロックを直接書く代わりにlambdaを使う
複雑な条件式とかをlambdaにして明示的な名前を付けておくと多少読みやすくなるかも、です。
defdestroy_aged_admins(users,limit_age)users.select{|user|user.admin?&&user.age>limit_age}.each(&:destroy)enddefdestroy_aged_admins(users,limit_age)aged_admin=->(user){user.admin?&&user.age>limit_age}users.select(&aged_admin).each(&:destroy)endでも上のサンプルだとこう書いた方がわかりやすいかな。。。
defdestroy_aged_admins(users,limit_age)aged_admins=users.select{|user|user.admin?&&user.age>limit_age}aged_admins.each(&:destroy)end何が何でもtrueかfalseで条件分岐させたいとき、!!を使う
Rubyではfalseとnilがfalse、それ以外の値がすべてtrueと評価されます。
JavaやC#を長くやっていたので最初は僕も違和感がありましたが、使っているうちにこれはこれで良くできてるなと感じてきました。
しかし、世の中には「わしゃtrueかfalseで条件分岐させなきゃイヤなんぢゃ!!」というプログラマもいるかもしれません。
そんな人は「イヤなんぢゃ!!」の!!を値の前に付ければ、Rubyが必ずtrueかfalseを返してくれます。
!!nil# => false!!false# => false!!0# => true!![]# => true!!true# => true実はBasicObjectクラスには!というメソッドが用意されているので、こんな書き方もできます。
nil.!.!# => falsefalse.!.!# => false0.!.!# => true[].!.!# => truetrue.!.!# => trueこのメソッドを使えば、nil?やempty?の逆も作れます。
puts"User exists!"ifuser.nil?.!puts"Some users exist!"ifusers.empty?.!・・・が、何もそこまでがんばる必要はないんじゃないかと僕は思います ^^;
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
- You can use dark theme
