I can easily ascend the class hierarchy in Ruby:
String.ancestors # [String, Enumerable, Comparable, Object, Kernel]
Enumerable.ancestors # [Enumerable]
Comparable.ancestors # [Comparable]
Object.ancestors # [Object, Kernel]
Kernel.ancestors # [Kernel]
Is there any way to descend the hierarchy as well? I'd like to do this
Animal.descendants # [Dog, Cat, Human, ...]
Dog.descendants # [Labrador, GreatDane, Airedale, ...]
Enumerable.descendants # [String, Array, ...]
but there doesn't seem to be a descendants method.
(This question comes up because I want to find all the models in a Rails application that descend from a base class and list them; I have a controller that can work with any such model and I'd like to be able to add new models without having to modify the controller.)
Here is an example:
class Parent
def self.descendants
ObjectSpace.each_object(Class).select { |klass| klass < self }
end
end
class Child < Parent
end
class GrandChild < Child
end
puts Parent.descendants
puts Child.descendants
puts Parent.descendants gives you:
GrandChild
Child
puts Child.descendants gives you:
GrandChild
If you use Rails >= 3, you have two options in place. Use .descendants if you want more than one level depth of children classes, or use .subclasses for the first level of child classes.
Example:
class Animal
end
class Mammal < Animal
end
class Dog < Mammal
end
class Fish < Animal
end
Animal.subclasses #=> [Mammal, Fish]
Animal.descendants #=> [Dog, Mammal, Fish]
Ruby 1.9 (or 1.8.7) with nifty chained iterators:
#!/usr/bin/env ruby1.9
class Class
def descendants
ObjectSpace.each_object(::Class).select {|klass| klass < self }
end
end
Ruby pre-1.8.7:
#!/usr/bin/env ruby
class Class
def descendants
result = []
ObjectSpace.each_object(::Class) {|klass| result << klass if klass < self }
result
end
end
Use it like so:
#!/usr/bin/env ruby
p Animal.descendants
Override the class method named inherited. This method would be passed the subclass when it is created which you can track.
Alternatively (updated for ruby 1.9+):
ObjectSpace.each_object(YourRootClass.singleton_class)
Ruby 1.8 compatible way:
ObjectSpace.each_object(class<<YourRootClass;self;end)
Note that this won't work for modules. Also, YourRootClass will be included in the answer. You can use Array#- or another way to remove it.
Although using ObjectSpace works, the inherited class method seems to be better suitable here inherited(subclass) Ruby documentation
Objectspace is essentially a way to access anything and everything that's currently using allocated memory, so iterating over every single one of its elements to check if it is a sublass of the Animal class isn't ideal.
In the code below, the inherited Animal class method implements a callback that will add any newly created subclass to its descendants array.
class Animal
def self.inherited(subclass)
#descendants = []
#descendants << subclass
end
def self.descendants
puts #descendants
end
end
I know you are asking how to do this in inheritance but you can achieve this with directly in Ruby by name-spacing the class (Class or Module)
module DarthVader
module DarkForce
end
BlowUpDeathStar = Class.new(StandardError)
class Luck
end
class Lea
end
end
DarthVader.constants # => [:DarkForce, :BlowUpDeathStar, :Luck, :Lea]
DarthVader
.constants
.map { |class_symbol| DarthVader.const_get(class_symbol) }
.select { |c| !c.ancestors.include?(StandardError) && c.class != Module }
# => [DarthVader::Luck, DarthVader::Lea]
It's much faster this way than comparing to every class in ObjectSpace like other solutions propose.
If you seriously need this in a inheritance you can do something like this:
class DarthVader
def self.descendants
DarthVader
.constants
.map { |class_symbol| DarthVader.const_get(class_symbol) }
end
class Luck < DarthVader
# ...
end
class Lea < DarthVader
# ...
end
def force
'May the Force be with you'
end
end
benchmarks here:
http://www.eq8.eu/blogs/13-ruby-ancestors-descendants-and-other-annoying-relatives
update
in the end all you have to do is this
class DarthVader
def self.inherited(klass)
#descendants ||= []
#descendants << klass
end
def self.descendants
#descendants || []
end
end
class Foo < DarthVader
end
DarthVader.descendants #=> [Foo]
thank you #saturnflyer for suggestion
(Rails <= 3.0 ) Alternatively you could use ActiveSupport::DescendantsTracker to do the deed.
From source:
This module provides an internal implementation to track descendants which is faster than iterating through ObjectSpace.
Since it is modularize nicely, you could just 'cherry-pick' that particular module for your Ruby app.
A simple version that give an array of all the descendants of a class:
def descendants(klass)
all_classes = klass.subclasses
(all_classes + all_classes.map { |c| descendants(c) }.reject(&:empty?)).flatten
end
Ruby Facets has Class#descendants,
require 'facets/class/descendants'
It also supports a generational distance parameter.
Class#subclasses (Ruby 3.1+)
Starting from Ruby 3.1, there is a built-in method - Class#subclasses.
It returns an array of classes where the receiver is the direct superclass of the class, excluding singleton classes.
As a result, there is no more need to depend on ActiveSupport or write monkey-patches in order to use it.
class A; end
class B < A; end
class C < B; end
class D < A; end
A.subclasses #=> [D, B]
B.subclasses #=> [C]
C.subclasses #=> []
Sources:
Class#subclasses from official Ruby docs.
Add Class#descendants.
Add Class#subclasses.
Feature #14394.
Ruby 3.1 adds Class#subclasses.
Rails provides a subclasses method for every object, but it's not well documented, and I don't know where it's defined. It returns an array of class names as strings.
You can require 'active_support/core_ext' and use the descendants method. Check out the doc, and give it a shot in IRB or pry. Can be used without Rails.
Building on other answers (particularly those recommending subclasses and descendants), you may find that in Rails.env.development, things get confusing. This is due to eager loading turned off (by default) in development.
If you're fooling around in rails console, you can just name the class, and it will be loaded. From then on out, it will show up in subclasses.
In some situations, you may need to force the loading of classes in code. This is particularly true of Single Table Inheritance (STI), where your code rarely mentions the subclasses directly. I've run into one or two situations where I had to iterate all the STI subclasses ... which does not work very well in development.
Here's my hack to load just those classes, just for development:
if Rails.env.development?
## These are required for STI and subclasses() to eager load in development:
require_dependency Rails.root.join('app', 'models', 'color', 'green.rb')
require_dependency Rails.root.join('app', 'models', 'color', 'blue.rb')
require_dependency Rails.root.join('app', 'models', 'color', 'yellow.rb')
end
After that, subclasses work as expected:
> Color.subclasses
=> [Color::Green, Color::Blue, Color::Yellow]
Note that this is not required in production, as all classes are eager loaded up front.
And yes, there's all kinds of code smell here. Take it or leave it...it allows you to leave eager loading off in development, while still exercising dynamic class manipulation. Once in prod, this has no performance impact.
Using descendants_tracker gem may help. The following example is copied from the gem's doc:
class Foo
extend DescendantsTracker
end
class Bar < Foo
end
Foo.descendants # => [Bar]
This gem is used by the popular virtus gem, so I think it's pretty solid.
This method will return a multidimensional hash of all of an Object's descendants.
def descendants_mapper(klass)
klass.subclasses.reduce({}){ |memo, subclass|
memo[subclass] = descendants_mapper(subclass); memo
}
end
{ MasterClass => descendants_mapper(MasterClass) }
To compute the transitive hull of an arbitrary class
def descendants(parent: Object)
outSet = []
lastLength = 0
outSet = ObjectSpace.each_object(Class).select { |child| child < parent }
return if outSet.empty?
while outSet.length == last_length
temp = []
last_length = outSet.length()
outSet.each do |parent|
temp = ObjectSpace.each_object(Class).select { |child| child < parent }
end
outSet.concat temp
outSet.uniq
temp = nil
end
outSet
end
end
For Ruby 3.1+ Class#subclasses is available. Class#descendants is not implemented:
class A; end
class B < A; end
class C < B; end
class D < A; end
A.subclasses => [B, D]
A.descendants => NoMethodError: undefined method 'descendants' for A:Class
A.methods.grep('descendants') => []
For Ruby < 3.1 this is slightly faster than the Rails implementation:
def descendants
ObjectSpace.each_object(singleton_class).reduce([]) do |des, k|
des.unshift k unless k.singleton_class? || k == self
des
end
end
The Ruby 3.1+ #subclasses appears much faster than the descendants method given above.
If you have access to code before any subclass is loaded then you can use inherited method.
If you don't (which is not a case but it might be useful for anyone who found this post) you can just write:
x = {}
ObjectSpace.each_object(Class) do |klass|
x[klass.superclass] ||= []
x[klass.superclass].push klass
end
x[String]
Sorry if I missed the syntax but idea should be clear (I don't have access to ruby at this moment).
Related
I'm trying to wrap my head around delegation vs. inheritance so I'm manually delegating a version of Array. One of the specific reasons I read to do this is because when you use things like enumerables, your returned value on the inherited methods reverts back to the parent class (i.e. Array). So I did this:
module PeepData
# A list of Peeps
class Peeps
include Enumerable
def initialize(list = [])
#list = list
end
def [](index)
#list[index]
end
def each(...)
#list.each(...)
end
def reverse
Peeps.new(#list.reverse)
end
def last
#list.last
end
def join(...)
#list.join(...)
end
def from_csv(csv_table)
#list = []
csv_table.each { |row| #list << Peep.new(row.to_h) }
end
def include(field, value)
Peeps.new(select { |row| row[field] == value })
end
def exclude(field, value)
Peeps.new(select { |row| row[field] != value })
end
def count_by_field(field)
result = {}
#list.each do |row|
result[row[field]] = result[row[field]].to_i + 1
end
result
end
protected
attr_reader :list
end
end
When I instantiate this, my include and exclude function great and return a Peeps class but when using an enumerable like select, it returns Array, which prevents me from chaining further Peeps specific methods after the select. This is exactly what I'm trying to avoid with learning about delegation.
p = Peeps.new
p.from_csv(csv_generated_array_of_hashes)
p.select(&:certified?).class
returns Array
If I override select, wrapping it in Peeps.new(), I get a "SystemStackError: stack level too deep". It seems to be recursively burying the list deeper into the list during the select enumeration.
def select(...)
Peeps.new(#list.select(...))
end
Any help and THANKS!
I would recommend using both Forwardable and Enumerable. Use Forwardable to delegate the each method to your list (to satisfy the Enumerable interface requirement), and also forward any Array methods you might want to include that are not part of the Enumerable module, such as size. I would also suggest not overriding the behavior of select as it is supposed to return an array and would at the very least lead to confusion. I would suggest something like the subset provided below to implement the behavior you are looking for.
require 'forwardable'
class Peeps
include Enumerable
extend Forwardable
def_delegators :#list, :each, :size
def initialize(list = [])
#list = list
end
def subset(&block)
selected = #list.select(&block)
Peeps.new(selected)
end
protected
attr_reader :list
end
Example usage:
peeps = Peeps.new([:a,:b,:c])
subset = peeps.subset {|s| s != :b}
puts subset.class
peeps.each do |peep|
puts peep
end
puts peeps.size
puts subset.size
produces:
Peeps
a
b
c
3
2
I think that if Peeps#select will return an Array, then it is OK to include Enumerable. But, you want Peeps#select to return a Peeps. I don't think you should include Enumerable. It's misleading to claim to be an Enumerable if you don't conform to its interface. This is just my opinion. There is no clear consensus on this in the ecosystem. See "Examples from the ecosystem" below.
If we accept that we cannot include Enumerable, here's the first implementation that comes to my mind.
require 'minitest/autorun'
class Peeps
ARRAY_METHODS = %i[flat_map map reject select]
ELEMENT_METHODS = %i[first include? last]
def initialize(list)
#list = list
end
def inspect
#list.join(', ')
end
def method_missing(mth, *args, &block)
if ARRAY_METHODS.include?(mth)
self.class.new(#list.send(mth, *args, &block))
elsif ELEMENT_METHODS.include?(mth)
#list.send(mth, *args, &block)
else
super
end
end
end
class PeepsTest < Minitest::Test
def test_first
assert_equal('alice', Peeps.new(%w[alice bob charlie]).first)
end
def test_include?
assert Peeps.new(%w[alice bob charlie]).include?('bob')
end
def test_select
peeps = Peeps.new(%w[alice bob charlie]).select { |i| i < 'c' }
assert_instance_of(Peeps, peeps)
assert_equal('alice, bob', peeps.inspect)
end
end
I don't normally use method_missing, but it seemed convenient.
Examples from the ecosystem
There doesn't seem to be a consensus on how strictly to follow interfaces.
ActionController::Parameters used to inherit Hash. Inheritance ceased in Rails 5.1.
ActiveSupport::HashWithIndifferentAccess still inherits Hash.
As mentioned in the other answer, this isn't really proper usage of Enumerable. That said, you could still include Enumerable and use some meta-programming to override the methods that you want to be peep-chainable:
module PeepData
class Peeps
include Enumerable
PEEP_CHAINABLES = [:map, :select]
PEEP_CHAINABLES.each do |method_name|
define_method(method_name) do |&block|
self.class.new(super(&block))
end
end
# solution for select without meta-programming looks like this:
# def select
# Peeps.new(super)
# end
end
end
Just so you know, this really has nothing to do with inheritance vs delegation. If Peeps extended Array, you would have the exact same issue, and the exact solution above would still work.
I want to make a ruby gem which standardizes a serious of APIs.
The logic associated with connecting to each API needs to be abstracted into .rb files. To load each of the API's logic, I'm looping through the files of a folder:
# Require individual API logic
Dir[File.dirname(__FILE__) + "/standard/apis/*.rb"].each do |file|
require file
end
Each API is a constant of the StandardAPI, so I can to iterate some code over each API:
StandardAPI.constants.each do |constant|
# Standardize this stuff
end
However, I have a VERSION constant too. It loops over my API logic classes just fine, but when I gets to VERSION, I run into:
:VERSION is not a class/module (TypeError)
How can I loop over each of the APIs ignoring constants that aren't my required classes?
Since Module#constants returns an array of symbols, you have to first look up the constant with Module#const_get:
Math.const_get(:PI) #=> 3.141592653589793
StandardAPI.const_get(:VERSION) #=> the value for StandardAPI::VERSION
Then you can check if it is a class:
Math.const_get(:PI).class #=> Float
Math.const_get(:PI).is_a? Class #=> false
StandardAPI.const_get(:VERSION).is_a? Class #=> false
To filter all classes:
StandardAPI.constants.select { |sym| StandardAPI.const_get(sym).is_a? Class }
Another approach is to collect the sub classes, maybe with Class#inherited:
# standard_api/base.rb
module StandardAPI
class Base
#apis = []
def self.inherited(subclass)
#apis << subclass
end
def self.apis
#apis
end
end
end
# standard_api/foo.rb
module StandardAPI
class Foo < Base
end
end
# standard_api/bar.rb
module StandardAPI
class Bar < Base
end
end
StandardAPI::Base.apis
#=> [StandardAPI::Foo, StandardAPI::Bar]
It seems you should be able to use is_a?
class Test
end
Test.is_a?(Class)
=> true
VERSION = 42
VERSION.is_a?(Class)
=> false
I guess you can catch that exception and proceed
StandardAPI.constants.each do |constant|
begin
# Standardize this stuff
rescue TypeError
next
end
end
or as #jonas mentioned
StandardAPI.constants.each do |constant|
if constant.is_a?(Class)
# Standardize this stuff
end
end
I have a class that wraps other instances to provide additional functionality (a presenter) and want to now have subclasses that provide the same functionality. So it would be something like this:
class BasePresenter
attr_accessor :base_object
def initialize(base_object)
self.base_object = base_object
end
end
class WalrusPresenter < BasePresenter
end
And I want to be able to do this:
BasePresenter(:bubbles)
#=> <# BasePresenter #base_object=:bubbles >
WalrusPresenter(:frank)
#=> <# WalrusPresenter #base_object=:frank >
Update
I think the functional differences are outside of the scope of the question, but they seem to be a sticking point so I'll add them.
I am not trying to delegate .new.
BasePresenter.new takes a single argument and wraps it in a presenter. BasePresenter() takes an object and:
If it is already a presenter, return it
If it is an array of objects, map over them and create a new presenter
If it is a single object, wrap it in a presenter and return it.
This is closer to ActiveSupport's Array#wrap, but I think the parenthetical syntax is rather communicative of its functionality so I'd like to use that if possible. The inheritance part is what is tripping me up; defining it once one the base class so it is useable on all children.
BasePresenter(:bubbles)
#=> <# BasePresenter #base_object=:bubbles >
BasePresenter([:waldorf, :statler])
#=> [ <# BasePresenter #base_object=:waldorf >, <# BasePresenter #base_object=:staler> ]
WalrusPresenter.new(:frank)
#=> <# WalrusPresenter #base_object=:frank >
WalrusPresenter([:harold, :richard])
#=> [ <# WalrusPresenter #base_object=:harold >, <# WalrusPresenter #base_object=:richard > ]
WalrusPresenter(WalrusPresenter(WalrusPresenter(:frank)))
#=> <# WalrusPresenter #base_object=:frank >
I might be missing the point of your question, but to me it looks like you just forgot to use new to create instances of your classes:
BasePresenter.new(:bubbles)
# => #<BasePresenter:0x00000001ae33b8 #base_object=:bubbles>
WalrusPresenter.new(:frank)
# => #<WalrusPresenter:0x00000001ae7878 #base_object=:frank>
Update: Mischa has already responded to your comment in about the same way I would. Kernel#Array tries calling to_ary on its argument, then tries calling to_a on it if that fails, then creates an Array with the argument as its sole element if that fails too.
It's not clear what behavior you want; it seems like you just want to get around using new, which of course you can do, but it would be silly:
def BasePresenter(base)
BasePresenter.new(base)
end
def WalrusPresenter(base)
WalrusPresenter.new(base)
end
You could do some metaprogramming to avoid repeating yourself when creating the wrappers. But I fail to see the reason you want to avoid new, and without a very good reason to do so, I'd have to recommend against it. Ruby uses new to instantiate objects.
Update 2: Thanks for clarifying what you're looking for. Here's the first thing that came to mind for me. You can definitely clean it up a bit, but something like this should get you started. (wrap doesn't need to be in BasePresenter at all.) Anyway, here you go:
class BasePresenter
attr_accessor :base_object
def initialize(base_object)
self.base_object = base_object
end
def self.wrap klass
Object.class_eval do
define_method klass.to_s do |object|
case object
when BasePresenter
object
when Array
object.map{|base| klass.new(base)}
else
klass.new(object)
end
end
end
end
BasePresenter.wrap BasePresenter
def self.inherited child_klass
BasePresenter.wrap child_klass
end
end
class WalrusPresenter < BasePresenter
end
This seems to do what you want:
BasePresenter(:bubbles)
# => #<BasePresenter:0x00000001db05a0 #base_object=:bubbles>
BasePresenter([:waldorf, :statler])
# => [#<BasePresenter:0x00000001da7c98 #base_object=:waldorf>,
#<BasePresenter:0x00000001da7c70 #base_object=:statler>]
WalrusPresenter.new(:frank)
# => #<WalrusPresenter:0x00000001da4070 #base_object=:frank>
WalrusPresenter([:harold, :richard])
# => [#<WalrusPresenter:0x00000001d773e0 #base_object=:harold>,
#<WalrusPresenter:0x00000001d773b8 #base_object=:richard>]
WalrusPresenter(WalrusPresenter(WalrusPresenter(:frank)))
# => #<WalrusPresenter:0x00000001d6c760 #base_object=:frank>
First, I don't think what you're trying to do is super wise - there are probably better ways to accomplish what you want. But if you really want to create a new operator against a class by adding parenthesis handling, the code below does the job I think..
I'm copying the gnarly class handling code from this great blog post by CodeErr.
Here is my solution for your problem using his code:
class Object
alias_method :orig_method_missing, :method_missing
def method_missing(m, *a, &b)
klass = begin
(self.is_a?(Module) ? self : self.class).const_get(m)
rescue NameError
end
return klass.send(:parens, *a, &b) if klass.respond_to? :parens
orig_method_missing m, *a, &b
end
end
class X
##class_arg = {}
def self.class_arg
##class_arg[self.name]
end
def self.parens(*args)
##class_arg[self.name] = args[0]
Class.new(self).class_eval do
define_method :initialize do
super(*args)
end
self
end
end
end
class Y < X
end
class Z < X
end
Z(:bubbles)
Y(:frank)
puts Z.class_arg # "bubbles"
puts Y.class_arg # "frank"
My solution:
class BasePresenter
attr_accessor :base_object
def initialize(base_object)
self.base_object = base_object
end
Object.class_eval do
def BasePresenter base_object
BasePresenter.new base_object
end
end
def self.inherited child_klass
Object.class_eval do
define_method child_klass.to_s.to_sym do |base_object|
child_klass.new base_object
end
end
end
end
class WalrusPresenter < BasePresenter
end
class Test < WalrusPresenter; end
puts BasePresenter(:bubbles)
puts WalrusPresenter(:frank)
puts Test(:test)
I'm trying to override a dynamically-generated method by including a module.
In the example below, a Ripple association adds a rows= method to Table. I want to call that method, but also do some additional stuff afterwards.
I created a module to override the method, thinking that the module's row= would be able to call super to use the existing method.
class Table
# Ripple association - creates rows= method
many :rows, :class_name => Table::Row
# Hacky first attempt to use the dynamically-created
# method and also do additional stuff - I would actually
# move this code elsewhere if it worked
module RowNormalizer
def rows=(*args)
rows = super
rows.map!(&:normalize_prior_year)
end
end
include RowNormalizer
end
However, my new rows= is never called, as evidenced by the fact that if I raise an exception inside it, nothing happens.
I know the module is getting included, because if I put this in it, my exception gets raised.
included do
raise 'I got included, woo!'
end
Also, if instead of rows=, the module defines somethingelse=, that method is callable.
Why isn't my module method overriding the dynamically-generated one?
Let's do an experiment:
class A; def x; 'hi' end end
module B; def x; super + ' john' end end
A.class_eval { include B }
A.new.x
=> "hi" # oops
Why is that? The answer is simple:
A.ancestors
=> [A, B, Object, Kernel, BasicObject]
B is before A in the ancestors chain (you can think of this as B being inside A). Therefore A.x always takes priority over B.x.
However, this can be worked around:
class A
def x
'hi'
end
end
module B
# Define a method with a different name
def x_after
x_before + ' john'
end
# And set up aliases on the inclusion :)
# We can use `alias new_name old_name`
def self.included(klass)
klass.class_eval {
alias :x_before :x
alias :x :x_after
}
end
end
A.class_eval { include B }
A.new.x #=> "hi john"
With ActiveSupport (and therefore Rails) you have this pattern implemented as alias_method_chain(target, feature) http://apidock.com/rails/Module/alias_method_chain:
module B
def self.included(base)
base.alias_method_chain :x, :feature
end
def x_with_feature
x_without_feature + " John"
end
end
Update Ruby 2 comes with Module#prepend, which does override the methods of A, making this alias hack unnecessary for most use cases.
Why isn't my module method overriding the dynamically-generated one?
Because that's not how inheritance works. Methods defined in a class override the ones inherited from other classes/modules, not the other way around.
In Ruby 2.0, there's Module#prepend, which works just like Module#include, except it inserts the module as a subclass instead of a superclass in the inheritance chain.
If you extend the instance of the class, you will can do it.
class A
def initialize
extend(B)
end
def hi
'hi'
end
end
module B
def hi
super[0,1] + 'ello'
end
end
obj = A.new
obj.hi #=> 'hello'
Say, I have the following 2 classes:
class A
def a_method
end
end
class B < A
end
Is it possible to detect from within (an instance of) class B that method a_method is only defined in the superclass, thus not being overridden in B?
Update: the solution
While I have marked the answer of Chuck as "accepted", later Paolo Perrota made me realize that the solution can apparently be simpler, and it will probably work with earlier versions of Ruby, too.
Detecting if "a_method" is overridden in B:
B.instance_methods(false).include?("a_method")
And for class methods we use singleton_methods similarly:
B.singleton_methods(false).include?("a_class_method")
If you're using Ruby 1.8.7 or above, it's easy with Method#owner/UnboundMethod#owner.
class Module
def implements_instance_method(method_name)
instance_method(method_name).owner == self
rescue NameError
false
end
end
class A
def m1; end
def m2; end
end
class B < A
def m1; end
def m3; end
end
obj = B.new
methods_in_class = obj.class.instance_methods(false) # => ["m1", "m3"]
methods_in_superclass = obj.class.superclass.instance_methods(false) # => ["m2", "m1"]
methods_in_superclass - methods_in_class # => ["m2"]
you can always to the following and see if its defined there:
a = A.new
a.methods.include?(:method)
Given an object b which is an instance of B, you can test to see whether b's immediate superclass has a_method:
b.class.superclass.instance_methods.include? 'a_method'
Notice that the test is against the method name, not a symbol or a method object.
"thus not being overridden in B" - Just knowing that the method is only defined in A is difficult because you can define the method on an individual instances of A and B... so I think it's going to be difficult to test that a_method is only defined on A, because you'd have to round up all the subclasses and subinstances in the system and test them...