😍

【Rails】compact / compact_blank / empty? の使い分け

に公開

目次

  1. はじめに
  2. なぜこれらのメソッドを理解する必要があるのか
  3. compact - nil だけを削除する
  4. compact_blank - 空っぽなものすべてを削除する
  5. empty? - 空かどうかを判定する
  6. 3つのメソッドの比較表
  7. 実務でよくあるユースケース
  8. よくある間違いと注意点
  9. まとめ

はじめに

Railsアプリケーション開発では、配列やハッシュから不要な値を取り除く処理が頻繁に発生します。この記事では、似ているようで動作が異なる compactcompact_blankempty? の3つのメソッドについて、実務を想定した使い分けを詳しく解説します。


なぜこれらのメソッドを理解する必要があるのか

実務では以下のような場面に日常的に遭遇します:

  • フォーム送信時: ユーザーが入力しなかった項目が空文字やnilとして送られてくる
  • API連携時: 外部APIからのレスポンスに不要な空データが含まれている
  • バリデーション前: データが空かどうかで処理を分岐させる必要がある
  • 検索機能: 空の検索条件を除外してクエリを構築する

これらの状況に応じて、適切なメソッドを選択することでコードの品質と保守性が向上します。


compact - nil だけを削除する

基本動作

compactnil のみを配列やハッシュから取り除くRuby標準メソッドです。

# 配列の場合
array = [1, nil, 2, "", [], false, 0]
array.compact
# => [1, 2, "", [], false, 0]

重要なポイント

  • nil は削除される
  • ❌ 空文字 "" は残る
  • ❌ 空配列 [] は残る
  • false は残る(false は nil ではない!)
  • 0 は残る

実務での使用例

ケース1: データベースのカラムに nil だけを許可しない場合

# ユーザー登録時の追加情報(空文字は許可するが nil は保存したくない)
user_attributes = {
  name: "太郎",
  nickname: nil,
  bio: "",
  website: nil
}

user_attributes.compact
# => { name: "太郎", bio: "" }
# nickname と website(nil)は削除されるが、bio(空文字)は残る

ケース2: 配列の要素から nil だけを除去

# APIから取得したIDリストに nil が混ざっている場合
user_ids = [1, 2, nil, 3, nil, 4]
User.where(id: user_ids.compact)
# => SELECT * FROM users WHERE id IN (1, 2, 3, 4)

compact と compact! の違い

array = [1, nil, 2]

# compact(非破壊的)- 元の配列は変更されない
result = array.compact
# result => [1, 2]
# array => [1, nil, 2](元のまま)

# compact!(破壊的)- 元の配列が変更される
array.compact!
# array => [1, 2](変更される)

compact_blank - 空っぽなものすべてを削除する

基本動作

compact_blank は Rails が追加した便利メソッドで、blank? が true になるものをすべて取り除きます。

blank? とは?

Railsの blank? メソッドは以下を true と判定します:

  • nil
  • 空文字 ""
  • 空白文字列 " "(スペースのみ)
  • 空配列 []
  • 空ハッシュ {}

⚠️ 注意: false0 は blank ではありません!

使用例

# 配列の場合
array = [1, nil, 2, "", "  ", [], false, 0]
array.compact_blank
# => [1, 2, false, 0]

# ハッシュの場合
params = {
  name: "太郎",
  email: "",
  age: nil,
  tags: [],
  active: false,
  score: 0
}

params.compact_blank
# => { name: "太郎", active: false, score: 0 }

実務での使用例

ケース1: フォームパラメータのクリーニング(最頻出!)

# app/controllers/users_controller.rb
def create
  # フォームから送信されたパラメータには空文字が含まれることが多い
  # compact_blank で一括削除すると便利
  
  cleaned_params = user_params.compact_blank
  @user = User.create(cleaned_params)
end

private

def user_params
  params.require(:user).permit(:name, :email, :bio, :website, :phone)
end

実際の動作イメージ:

# フォームからの送信データ
{
  name: "太郎",
  email: "taro@example.com",
  bio: "",           # 入力されなかった
  website: "",       # 入力されなかった
  phone: "090-1234-5678"
}

# compact_blank 後
{
  name: "太郎",
  email: "taro@example.com",
  phone: "090-1234-5678"
}
# 空文字の bio と website が削除される

ケース2: 検索条件の構築

# app/controllers/products_controller.rb
def index
  # 検索フォームから送られた条件で絞り込み
  search_params = {
    category: params[:category],    # => ""(未選択)
    min_price: params[:min_price],  # => nil
    max_price: params[:max_price],  # => "5000"
    keyword: params[:keyword]       # => "Ruby"
  }.compact_blank
  
  # => { max_price: "5000", keyword: "Ruby" }
  # 空の条件が自動的に除外されるので、クエリがシンプルになる
  
  @products = Product.where(search_params)
end

ケース3: JSON APIレスポンスのクリーニング

# app/controllers/api/v1/users_controller.rb
def show
  user_data = {
    id: @user.id,
    name: @user.name,
    bio: @user.bio,           # => nil の可能性
    website: @user.website,   # => "" の可能性
    avatar_url: @user.avatar_url
  }.compact_blank
  
  render json: user_data
  # 空のフィールドを含まないクリーンなJSONを返せる
end

empty? - 空かどうかを判定する

基本動作

empty? は、オブジェクトが空かどうかを判定するメソッドで、真偽値(true / false)を返します。

⚠️ 重要: compactcompact_blank と違い、要素を削除しません

使用例

# 文字列
"".empty?      # => true
"hello".empty? # => false

# 配列
[].empty?      # => true
[1, 2].empty?  # => false

# ハッシュ
{}.empty?      # => true
{ a: 1 }.empty? # => false

実務での使用例

ケース1: 条件分岐での使用

# app/controllers/orders_controller.rb
def create
  @cart_items = current_user.cart_items
  
  if @cart_items.empty?
    redirect_to products_path, alert: "カートが空です"
    return
  end
  
  # カートに商品がある場合の処理
  @order = Order.create_from_cart(@cart_items)
end

ケース2: ビューでの条件表示

<!-- app/views/users/show.html.erb -->
<% if @user.posts.empty? %>
  <p>まだ投稿がありません</p>
<% else %>
  <% @user.posts.each do |post| %>
    <%= render post %>
  <% end %>
<% end %>

ケース3: バリデーション前のチェック

# app/models/order.rb
class Order < ApplicationRecord
  validate :must_have_items
  
  private
  
  def must_have_items
    if order_items.empty?
      errors.add(:base, "注文には最低1つの商品が必要です")
    end
  end
end

empty? vs blank? vs present?

Railsには似たようなメソッドが他にもあります:

value = ""

# empty? - 空かどうか(Rubyネイティブ)
value.empty?   # => true

# blank? - 空っぽかどうか(Rails拡張、より広範囲)
value.blank?   # => true

# present? - 値があるかどうか(blank? の逆)
value.present? # => false
# nil の場合の違い
nil.empty?   # => NoMethodError(nil には empty? がない!)
nil.blank?   # => true
nil.present? # => false

# 実務では blank? / present? を使うことが多い
if params[:email].present?
  # メールアドレスが入力されている場合の処理
end

3つのメソッドの比較表

メソッド 対象 動作 返り値 削除されるもの
compact Array / Hash nil を削除 新しい配列/ハッシュ nil のみ
compact_blank Array / Hash blank? なものを削除 新しい配列/ハッシュ nil, "", " ", [], {}
empty? 単一オブジェクト 空かどうか判定 true / false 削除しない(判定のみ)

値ごとの動作比較

compact で削除? compact_blank で削除? empty? の結果
nil ✅ Yes ✅ Yes エラー
"" ❌ No ✅ Yes true
" " ❌ No ✅ Yes false
[] ❌ No ✅ Yes true
{} ❌ No ✅ Yes true
false ❌ No ❌ No エラー
0 ❌ No ❌ No エラー

実務でよくあるユースケース

ユースケース1: フォーム送信データの処理

# 悪い例 ❌
def create
  @article = Article.new(article_params)
  # 空文字がそのまま保存されてしまう
  @article.save
end

# 良い例 ✅
def create
  cleaned_params = article_params.compact_blank
  @article = Article.new(cleaned_params)
  @article.save
end

ユースケース2: 複数の検索条件

def search
  conditions = {}
  
  # 悪い例 ❌ - 一つ一つチェックして追加
  conditions[:category] = params[:category] if params[:category].present?
  conditions[:status] = params[:status] if params[:status].present?
  conditions[:user_id] = params[:user_id] if params[:user_id].present?
  
  # 良い例 ✅ - compact_blank で一括処理
  conditions = {
    category: params[:category],
    status: params[:status],
    user_id: params[:user_id]
  }.compact_blank
  
  @results = Product.where(conditions)
end

ユースケース3: 配列の空チェックと処理

# 悪い例 ❌ - エラーの可能性
def process_items
  items = fetch_items
  items.each do |item|  # items が nil ならエラー
    process(item)
  end
end

# 良い例 ✅ - 安全な処理
def process_items
  items = fetch_items
  
  return if items.nil? || items.empty?  # ガード節
  
  items.compact.each do |item|  # nil も除外
    process(item)
  end
end

ユースケース4: APIレスポンスの整形

# app/serializers/user_serializer.rb
class UserSerializer
  def as_json
    {
      id: user.id,
      name: user.name,
      email: user.email,
      profile: {
        bio: user.bio,
        website: user.website,
        twitter: user.twitter,
        github: user.github
      }.compact_blank,  # 空のSNSアカウントは含めない
      stats: {
        posts_count: user.posts.count,
        followers_count: user.followers.count
      }
    }
  end
end

よくある間違いと注意点

間違い1: false を削除してしまう

# 危険な例 ❌
settings = {
  notifications: false,  # 通知オフ
  public_profile: true,
  auto_save: false       # 自動保存オフ
}.compact_blank

# => { public_profile: true }
# false が削除されてしまい、設定が失われる!

# 正しい例 ✅
# false も有効な値なので compact_blank は使わない
settings = {
  notifications: false,
  public_profile: true,
  auto_save: false
}.compact  # nil だけ削除

間違い2: nil に対して empty? を呼ぶ

# エラーになる例 ❌
value = nil
value.empty?  # => NoMethodError

# 正しい例 ✅
value.nil? || value.empty?  # nil チェックを先に

# または Rails なら
value.blank?  # nil でも安全

間違い3: 破壊的メソッドの誤用

# 意図しない動作 ❌
original = [1, nil, 2]
filtered = original.compact!  # 元の配列を変更してしまう

puts original  # => [1, 2](変更されている)

# 正しい例 ✅
original = [1, nil, 2]
filtered = original.compact  # 新しい配列を返す

puts original  # => [1, nil, 2](元のまま)
puts filtered  # => [1, 2]

間違い4: 数値の 0 を削除してしまう

# 問題のある例 ❌
scores = [100, 0, 50, nil, 80]
valid_scores = scores.compact_blank

# compact_blank は 0 を削除しない(0 は blank ではない)
# => [100, 0, 50, 80]  # 正しく動作

# でも、0 を「無効なスコア」として扱いたい場合は別の方法が必要
valid_scores = scores.compact.reject(&:zero?)
# => [100, 50, 80]

まとめ

使い分けの判断フロー

データをクリーニングしたい?
├─ Yes → nil だけ削除したい?
│        ├─ Yes → compact を使う
│        └─ No → 空文字・空配列も削除したい?
│                └─ Yes → compact_blank を使う
│
└─ No → 空かどうか判定したい?
         └─ Yes → empty? を使う
                  (Rails なら blank? / present? も検討)

想定使用

  1. フォームパラメータのクリーニングcompact_blank
  2. 検索条件の構築compact_blank
  3. nil だけを除外したいcompact
  4. 条件分岐での判定empty?(または blank? / present?
  5. false や 0 も有効な値として扱うcompactcompact_blank は使わない)

最後に

これらのメソッドを適切に使い分けることで、コードがより安全で読みやすくなります。特に compact_blank はRails開発で非常に便利なので、ぜひ活用してください。

実務では、まず動作を確認してから使用することをお勧めします。rails console で実際に試してみると理解が深まります:

# rails console で試してみよう
irb(main):001:0> [1, nil, "", false].compact
irb(main):002:0> [1, nil, "", false].compact_blank
irb(main):003:0> [].empty?

Happy coding! 🚀

Discussion