Rails の link_to_if にブロックを渡したときの挙動が分かりにくいので注意

Railslink_to_if にブロックを渡したときの挙動が分かりづらいので、使う場合の注意点をまとめてみました。

TL;DR

ブロックの内容を条件によってリンクにしたい場合は capture を使うのがシンプルで良いです。

<% body = capture do %>
  <%= image_tag 'icon.png' %>
  <span><%= user.name %></span>
<% end %>
<%= link_to_if condition, body, root_path %>

link_to_unlesslink_to_unless_current の場合も同じ。

link_to_if にブロックを渡す例

ユーザーのアイコンと名前を条件によってリンクにする場合を考えます。*1

<% if condition %>
  <% link_to root_path do %>
    <img href="icon.png"></img>
    <span><%= user.name %></span>
  <% end %>
<% else %>
  <img href="icon.png"></img>
  <span><%= user.name %></span>
<% end %>

これを DRY にするために link_to_if を使おうとしても、うまく動きません。

<% link_to_if condition, root_path do %>
  <img href="icon.png"></img>
  <span><%= user.name %></span>
<% end %>

# If the condition is true
# => <a href="/">/</a>
# else
# => <img href="icon.png"><span>name</span>

条件が true の場合、ブロックが 評価されない からです。

ブロックを必ず評価するメソッド

条件が true の時にaタグで囲むメソッドを作ってみました。

def link_to_block_if(condition, options = {}, html_options = {}, &block)
  if condition
    link_to(options, html_options, &block)
  else
    capture(&block)
  end
end

使い方はこんな感じ。

<% link_to_block_if condition, root_path do %>
  <img href="icon.png"></img>
  <span><%= user.name %></span>
<% end %>

Rails へプルリクを送ることを検討

rails/rails で検索してみると、 link_to_if でブロックを必ず評価するプルリクが出て、1度マージされた後 revert されていました。

また、 Qiita にも同じような記事がありました。

似たようなことで困っている人がいることは分かったので、 Rails にプルリク送ってみても良いかもしれないと考えた。

メソッド名の検討

link_to_block_if は微妙なので、他のメソッド名を考えてみましたが、どれも微妙・・・。

- link_to_content_if
- link_or_content_tag
- wrap_link_to_if

link_to_ifcache_if のように _if のメソッドは条件が true の時にブロックが評価されない。 また、 find_or_create_by のように _or_ のメソッドは前半が true の時にブロックが評価されない。

Rails の他メソッドと一貫性の無いメソッド名になるのはダメそう。

命名に時間がかかるメソッド

そもそも、命名に時間がかかるメソッドは何かがダメです。 そこで、1つのメソッドにする事を止めて、下記のように2つのメソッドで書いてみました。

<% body = capture do %>
  <%= image_tag 'icon.png' %>
  <span><%= user.name %></span>
<% end %>
<%= link_to_if condition, body, root_path %>

あれ、これで良いのでは・・・。Rails の標準メソッドの組み合せなので、後から読んでも分かりやすいし。

まとめ

  • link_to_if にブロックを渡した時の挙動は少し分かりづらい
  • ただ、 Rails_if のメソッドとしては一貫性がある
  • 命名に悩むような難しいメソッドは、そもそも設計がダメ

Rails にプルリクを投げる前に気づくことができて良かった。

RubyKaigi 2016 で色んな Rubyistlink_to_if について話を聞いて、助言を頂いたお陰です。感謝。

*1:似たような例だと「公開日時以降はバナーをリンク可能にする」「公開設定のリソースをリンク可能にする」など