Help us understand the problem. What is going on with this article?

Ruby, Railsの存在確認イディオムまとめ

More than 1 year has passed since last update.

はじめに

  • このメソッドが存在したら
  • この変数が配列で何か入っていたら
  • Item.firstで何か見つかったら

というのは結構出てくるパターンで、簡潔に書ける場合も多いのでまとめました。

使うとコードが分かりにくくなる場合もあるので、容量用法を守って使いましょう。

基本の確認

ローカル変数が定義されているか

前提

定義されていないローカル変数は使おうとすると例外を起こす

hoge #=> NameError: undefined local variable or method `hoge' for main:Object

defined?

定義されているか確認できる

defined? hoge #=> false

オブジェクトのメソッド存在チェック

前提

存在しないメソッドを呼び出すと例外

hoge = nil
hoge.fuga #=> NoMethodError: undefined method `fuga' for nil:NilClass

respond_to?

メソッドが存在しているか確認できる

hoge.respond_to? :fuga #=> false

事例集

ここからRails(Active Support)の導入を前提とします。

便利なメソッド

present?

偽でなくsizeが0でなければtrue

Stringクラスは/\A[[:space:]]*\z/へのマッチもfalseになる

nil.present? #=> false
[Item.first].present? #=> true

compact

nilの要素を消してくれる

[nil].present? #=> true
[nil].compact.present? #=> false

presence

#present?を実行しtrueだったらselfを返す

<% if items = current_user.items.order(id: :asc).limit(5).presence %>
  <h2>アイテム一覧</h2>
  <ul>
    <% items.each do |item| %>
      <li><%= item.name %></li>
    <% end %>
  </ul>
<% end %>

tap

tapはブロック内でselfが使え、selfが返ります。ただ、breakするとその値が返ります。あんまりいい例が思いつかなかった…。

items = [{ id: 1, title: 'item1' }, { id: 2 }]
items.map{|item| "タイトルは" + item.tap{|i| break { title: 'no title' } unless i[:title]}[:title] }

try

メソッドが無ければnilを返す

titles = ['1', nil, '123']
title_lengths = titles.map{|title| title.try(:length) }
#=> [1, nil, 3]

便利な演算子 (Ruby)

and

左辺が真だと右辺が評価される(演算子の優先順位低)

<% if item = current_user.items.first and item.prirority == 100 %>
  <h2><%= item.name %></h2>
<% end %>

&&

左辺が真だと右辺が評価される(演算子の優先順位高)

<% if item.status == :open or item.user == current_user && item.status != :delete %>
  <h2><%= item.name %></h2>
<% end %>

||

左辺が偽だと右辺が評価される(演算子の優先順位高)

next_item = Item.order(id: :asc).where("items.id > ?", @item.id).first || Item.first

&. (Ruby 2.3↑), try! (Active Support)

オブジェクトがnilでない場合は呼び出し、nilの時はnilを返す

hoge = nil

hoge&.fuga #=> nil
hoge.try!(:fuga) #=> nil

こんなことも可能

Item.first&.user&.themes&.first

||=

定義されていなかったら、デフォルト値を代入

old.html.erb
<%= partial 'item' %>
new.html.erb
<%= partial 'item', locals: { hidden_header: true } %>

||=を使って未定義時にfalseを入れておくことで、oldからの呼び出しにも耐えられる。

_item.html.erb
<% hidden_header ||= false %>
<% unless hidden_header %>
  hidden_header is true
<% end %>

ただし変数に偽のものが入っていると考えられる時は、使えない

hoge = nil
hoge ||= "test"
hoge #=> "test"

? :

三項演算子は演算子の優先順位的に連続で使える

a ? 'a is true' : b ? 'b is true' : c ? 'c is true' : 'all false'

caseやelsifを使った方が良いこともままある

後置if

erbでも便利

<div class="<%= :tab_on if action_name == 'index' %>">

後置rescue

require 'open-uri'
res = open('http://hoge.huga').read #=> SocketError: Failed to open TCP connection to hoge.huga:80 (getaddrinfo: nodename nor servname provided, or not known)
res = open('http://hoge.huga').read rescue nil #=> nil

おわりに

「これ書かなくても情報量的には充分だよね?」と思った時、ぐぐってみると結構解決策があったりして便利。

世の中一般には汎用的ではないが、自分のところでは役立つ場合は、ヘルパーなどで定義してしまうのも手。

よいRuby & Railsライフを!

anoworl
appbrew-inc
「ユーザーが熱狂するプロダクトを再現性をもって創造する」をミッションにサービス開発・運営を行うスタートアップ
https://lipscosme.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away