1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Ruby][RSpec] method_missing で動的に定義したメソッドをスタブする

Last updated at Posted at 2025-08-26

問題

BasicObject#method_missing を使って slice_hoge という名前のメソッドを動的に定義する。

class Mole < ApplicationRecord
  # mole.slice_hoge で mole.slice(:hoge) を呼び出す。
  # ただし hoge の部分は Mole のカラム名に限定する。
  def method_missing(name, ...)
    column_name = name.to_s.slice(/\Aslice_(\w+)\z/, 1)&.to_sym
    return super unless column_name

    column_names = self.class.column_names.map(&:to_sym)
    return super unless column_name.in?(column_names)

    slice(column_name)
  end
end

mole = Mole.first!
mole.slice_name
#=> {"name"=>"クルテク"}

動的に定義したメソッドを RSpec で allow を使ってスタブしようとすると <#Mole ...> does not implement: slice_name というエラーになる 😢

let(:mole) { FactoryBot.create(:mole) }

before do
  allow(mole).to receive(:slice_name).and_return(name: 'もぐたろう')
end

# NG
# #<Mole id: 1, ...> does not implement: slice_name
it { expect(mole.slice_name).to eq(name: 'もぐたろう') } 

解決方法

Object#respond_to_missing? をオーバーライドすれば OK :ok_woman:

Ruby のリファレンスマニュアルにも

BasicObject#method_missing を override した場合にこのメソッドも override されるべきです。

と記載されている。そして RuboCop にも respond_to_missing? の定義漏れを検知する Style/MissingRespondToMissing という Cop が存在する。

class Mole
  def method_missing(name, ...)
    return super unless respond_to?(name)

    column_name = get_column_name_from_slice_method(name)
    return super unless column_name

    slice(column_name)
  end

  def respond_to_missing?(name, _include_private)
    column_name = get_column_name_from_slice_method(name)
    return super unless column_name

    column_names = self.class.column_names.map(&:to_sym)
    return super unless column_name.in?(column_names)

    true
  end

  private

  def get_column_name_from_slice_method(name)
    name.to_s.slice(/\Aslice_(\w+)\z/, 1)&.to_sym
  end
end
let(:mole) { FactoryBot.create(:mole) }

before do
  allow(mole).to receive(:slice_name).and_return(name: 'もぐたろう')
end

# OK
it { expect(mole.slice_name).to eq(name: 'もぐたろう') }

バージョン情報

RUBY_VERSION
#=> "3.3.8"

RSpec::Mocks::Version::STRING
#=> "3.13.5"

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?