Archive for December, 2012

redirect_to and return と書いて良いのか?

redirect_to root_url unless logged_in?

みたいなコードを初めのうちはつい書いてしまいハマる。
正しくは、以下のように return しなければならない。

unless logged_in?
  redirect_to root_url
  return 
end

しかし1行で書けていたものが4行になってしまうのはいかにも不格好である。

あちこちでコードを読んでいるとたまにこんなのを見かける。

redirect_to root_url and return unless logged_in?

これは見た目もかっこいいし意味も判り易くて cool だ。

ところで、 and return ってなんだ?

これは何の事はない、 redirect_to の戻り値がたまたま true なのを利用して、 and で受けて次の文を書いているだけである。
redirect_to が万が一 false や nil を返してきたら return は実行されない。
ちなみに redirect_to の実装を見ると、少なくとも現時点では false や nil にならないことは確実だが、将来に渡って保証されているわけではないので気持ち悪い。

and の代わりに true/false に関わらず確実に実行する接続詞として ; があるので、以下のように書いてみる。

redirect_to root_url; return unless logged_in?

残念ながらこれはうまくいかない。 unless より ; のほうが優先順位が高いので、

redirect_to root_url
return unless logged_in?

という意味になってしまう。

仕方なくカッコでくくる。

(redirect_to root_url; return) unless logged_in?

これは正しく動くが、かっこわるいよねえ。カッコ付けててかっこ悪いってやつ?

さらに他のコードを読み漁ると、こんな書き方もある。

return redirect_to(root_url) unless logged_in?

and return 記法に比べればcoolさは劣るが、比較的スマートな書き方だ。何の意味も無い値を return することについては一瞬不安になるが、 action の戻り値を気にしなければならなくなるような仕様変更は将来的にも有り得ないと思われるので、問題ないだろう。

というわけで、結論としては return redirect_to 記法をオススメ、というところで筆を置くつもりだったのだが、何やら公式っぽいドキュメントに、「and return」って書くといいよ! と記述されてるので、いいのかもしれんね。
(だったら redirect_to の実装の末尾に true の一行を足しておきたいなあ)

2つ以上の情報源がある際の truncate

以下のコードをお好きな model なり helper なりに。

  def smart_truncate(str, len)
    return str.truncate(len) if str.is_a?(String)
    raise ArgumentError.new(:str) unless str.is_a?(Array)
    num = str.length - 1
    return str[0].truncate(len) if num == 0
    len -= str.last.length
    (0...num).to_a.reverse.each do |i|
      each_len = len / (i+1)
      str[i] = str[i].truncate(each_len)
      len -= str[i].length
    end
    str.join("")
  end

使い方は、省略可能な文字列のセットを配列にしたものと、最終的に納めたい文字数。例えばこんな感じだ。
smart_truncate([subject, content, author, url], 140)
最後の要素は省略されずに必ず全て表示される。

動作の結果は以下のとおり。

  describe "smart_truncate" do
    it { smart_truncate("abcde", 4).should == "a..." }
    it { smart_truncate(["abcde"], 4).should == "a..." }
    it { smart_truncate(["123456","78"], 8).should == "12345678" }
    it { smart_truncate(["123456","78"], 7).should == "12...78" }
    it { smart_truncate(["123456","789012", "3456"], 16).should == "1234567890123456" }
    it { smart_truncate(["123456","789012", "3456"], 15).should == "12345678...3456" }
    it { smart_truncate(["123456","789012", "3456"], 14).should == "12...78...3456" }
    it { smart_truncate(["123456","789012", "3456"], 13).should == "12...7...3456" }
    it { smart_truncate(["12345" ,"789012", "3456"], 13).should == "123457...3456" }
  end