Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

each...or else――Rubyで空のコレクションをエレガントに処理する方法

Rubyのeachは非常に便利なメソッドですが、ひとつ悩ましい問題があります。コレクションが空だった場合はどうなるのでしょうか?

[].eachを呼び出しても何も起こらず、単に[]が返されるだけです。それが望ましい動作であるケースもあります。しかし多くの場合、特にUIを構築している際には、空のリストを特別に扱いたくなるものです。たとえば私なら、データがひとつもないときには別のメッセージを表示したいと考えます。

ところが[].each[]を返すのであってnilではないため、次のような書き方をするしかありません。

app/views/users/index.html.erb
<% if @users.empty? -%>
  <p>You don't have any users. <%= link_to "Add one!", new_user_path %></p>
<% else -%>
  <% @users.each do |user| -%>
      <p><%= user.name %></p>
  <% end -%>
<% end -%>

これでも動作しますが、どこかぎこちなく感じます。むしろ「このコレクションの各要素にこの処理を行う。ただし中身が空なら、まったく別の処理をする」と言いたいところです。つまり、「or else(さもなくば)」付きのeachが欲しいのです。

Railsビューでのコレクション描画

ビューの中であれば、Railsが力になってくれます。Railsはオブジェクトのコレクション描画がとても得意です。

app/views/users/index.html.erb
<%= render @users %>
app/views/users/_user.html.erb
<p><%= user.name %></p>

render@usersを渡すと、@users内のユーザーごとに_user.html.erbが一度ずつ描画されます。わざわざeachを書く必要はありません。

さらに嬉しいことに、@usersが空の場合、rendernilを返してくれます。まさに望みどおりの挙動です!したがって、次のように書けば、最初のバージョンと同じ出力が得られます。

app/views/users/index.html.erb
<%= render(@users) || render('empty') %>
app/views/users/_user.html.erb
<p><%= user.name %></p>
app/views/users/_empty.html.erb
<p>You don't have any users. <%= link_to "Add one!", new_user_path %></p>

Railsの規約さえ理解していれば、ずっと直接的で読みやすいコードになります。

ビューの外ではどうする?

Railsの流儀に従えば、コレクションを渡したrenderは、オブジェクト群を高速かつ強力に描画できる手段となります。

しかし、項目ごとにパーシャルを追加で用意したくないこともあるでしょう。あるいはrenderではデザイン要件を満たせないかもしれません。そもそもビューの中にいないケースもあります。

今のところ私が見つけた最良の解決策は、presenceです。

list.presenceは、次のコードと同じ意味になります。

if list.present?
  list
else
  nil
end

つまり、リストに何か入っていればそのリストが返り、空ならnilが返るということです。

presenceを使えば、次のように書けます。

@users.each do |user|
  puts user.name
end.presence || puts("You don't have any users.")

@usersにユーザーがいれば各名前が出力され、いなければ「You don't have any users.」と表示されます。

とはいえ、presenceなしで書ければそれに越したことはありません。これはハックであり、実際にハックだからです。もし言語側でサポートされているなら、次のような書き方のほうが自然でしょう。

@users.each do |user|
  puts user.name
end || puts("You don't have any users.")

あるいはSmalltalk風に、こんな感じでしょうか。

@users.each(if_empty: lambda { puts "You don't have any users." }) do |user|
  puts user.name
end

さらには(驚くなかれ)、こんな構文も想像できます。

for user in @users
  puts user.name
else
  puts "You don't have any users."
end

ただ現時点では、私はpresenceを使うか、素朴なif blank?; else eachパターンを採用することがほとんどです。

あなたはどの方法が良いと思いますか? 空のリストはどのように処理していますか? もっと良い方法を見つけた経験があれば、ぜひ教えてください!

  1. JavaScriptの条件分岐とは?if文・else文・else if文の使い方を解説

    JavaScriptには、条件に応じて処理を切り替えるための「条件分岐(条件文)」が用意されています。条件式が真(true)か偽(false)かを評価することで、実行するコードを制御できます。代表的な条件文は次の3種類です。 if文 − 指定した条件が満たされた場合にのみ、ifブロック内のコードを実行します。最も基本的な条件分岐です。 if…else文 − 条件が真の場合と偽の場合で、それぞれ異なる処理を実行したいときに使います。2つのケースを切り替えるのに適しています。 if…else if…else文 − 3つ以上の条件を扱いたい場合に使用します。条件を上から順に評価し、最初に一致したブ

  2. SQL ServerのIF...ELSE文とは?基本構文と実践例をわかりやすく解説

    他のプログラミング言語と同様に、SQL Serverでも条件分岐を行うためのIF...ELSEコマンドが用意されています。本記事では、IF...ELSE文の構文の使い方を、わかりやすい具体例とともに詳しく解説します。 概要 SQL ServerにおけるIF...ELSE文は、条件に応じて処理を分岐させるために使用されるステートメントです。条件がTRUE(真)と評価された場合は一方的な処理を実行し、FALSE(偽)と評価された場合には別の処理を実行します。 IF...ELSE文の構文 SQL ServerでIF...ELSEによる分岐処理を行うには、以下の構文に従って記述します。 IF 条件式{