Skip to content
This repository was archived by the owner on Nov 30, 2024. It is now read-only.

Add Support.class_of for extracting class of any object #325

Merged
merged 1 commit into from
Aug 11, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Enhancements:

* Improve compatibility with `--enable-frozen-string-literal` option
on Ruby 2.3+. (Pat Allan, #320)
* Add `Support.class_of` for extracting class of any object.
(Yuji Nakayama, #325)

Bug Fixes:

Expand Down
10 changes: 10 additions & 0 deletions lib/rspec/support.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ def self.method_handle_for(object, method_name)
end
end

# @api private
#
# Used internally to get a class of a given object, even if it does not respond to #class.
def self.class_of(object)
object.class
rescue NoMethodError
singleton_class = class << object; self; end
singleton_class.ancestors.find { |ancestor| !ancestor.equal?(singleton_class) }
end

# A single thread local variable so we don't excessively pollute that namespace.
def self.thread_local_data
Thread.current[:__rspec] ||= {}
Expand Down
3 changes: 1 addition & 2 deletions lib/rspec/support/object_formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,7 @@ def inspect
end

def klass
singleton_class = class << object; self; end
singleton_class.ancestors.find { |ancestor| !ancestor.equal?(singleton_class) }
Support.class_of(object)
end

# http://stackoverflow.com/a/2818916
Expand Down
4 changes: 2 additions & 2 deletions spec/rspec/support/object_formatter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ def with_delegate_loaded
end
end

context 'with an object that does not respond to #inspect such as BasicObject' do
context 'with an object that does not respond to #class and #inspect such as BasicObject' do
subject(:output) do
ObjectFormatter.format(input)
end
Expand All @@ -197,7 +197,7 @@ def self.to_s
'BasicObject'
end

undef inspect, respond_to?
undef class, inspect, respond_to?
end
end

Expand Down
67 changes: 67 additions & 0 deletions spec/rspec/support_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,73 @@ def method_missing(name, *args, &block)
end
end

describe '.class_of' do
subject(:klass) do
Support.class_of(object)
end

context 'with a String instance' do
let(:object) do
'foo'
end

it { should equal(String) }
end

context 'with a BasicObject instance' do
let(:object) do
basic_object_class.new
end

let(:basic_object_class) do
defined?(BasicObject) ? BasicObject : fake_basic_object_class
end

let(:fake_basic_object_class) do
Class.new do
def self.to_s
'BasicObject'
end

undef class, inspect, respond_to?
end
end

it { should equal(basic_object_class) }
end

context 'with nil' do
let(:object) do
nil
end

it { should equal(NilClass) }
end

context 'with an object having a singleton class' do
let(:object) do
object = 'foo'

def object.some_method
end

object
end

it 'returns its non-singleton ancestor class' do
expect(klass).to equal(String)
end
end

context 'with a Class instance' do
let(:object) do
String
end

it { should equal(Class) }
end
end

describe "failure notification" do
before { @failure_notifier = RSpec::Support.failure_notifier }
after { RSpec::Support.failure_notifier = @failure_notifier }
Expand Down