So, I've been playing with some models, and I've run into a situation where I'd really like to limit the inheritance of a class method by a subclass. Trouble is, my experimentation has so far confirmed my understanding that this cannot be done.
I naïvely tried the following:
class Policy
class << self
def lookup(object)
#returns a subclass by analyzing the given object, following a naming convention
end
def inherited( sub )
sub.class_eval { remove_method :lookup }
end
end
end
Of course that doesn't work because the subclass doesn't have the method, it's on the super class. After that I tried:
def inherited( sub )
class << Policy
remove_method :lookup
end
end
That works like a charm, haha, except for the tiny detail that it works by taking the method off the superclass the first time a subclass is loaded. Oops!
So, I'm pretty sure this can't work due to the way Ruby looks up methods.
The reason I'm interested is that in the behavior I'm working on you could have many different policies, following a naming convention, and I'd like to have a nice clean way to get a reference to the policy for any other class of object. To me, syntatically, it seems nice to do this:
class RecordPolicy < Policy
# sets policy concerning records,
# inherits common policy behavior from Policy
end
class Record
end
$> record = Record.new
=> #<Record:0x0000>
$> Policy.lookup(record)
=> RecordPolicy
However, I don't think it makes any sense to be able to call RecordPolicy#lookup
. You've got the policy, there's nothing underneath to find.
So, my question is two parts:
1) Is there, in fact, some way to selectively define what class methods can be inherited in Ruby?
At this point I'm pretty much certain the answer to that is no, therefore:
2) Given that I want to encapsulate the logic for inferring a policy name for any given object somewhere, and it looks to me like what I've tried so far demonstrates that the Policy class is the wrong place for it, where would you put this kind of thing instead?
Thanks!
Update
Thanks to JimLim for answering part 1, and Linuxios for responding to part 2. Very useful insight from both.
FWIW, after reflecting on what Linuxios said, here is what I decided to do:
class << self
def lookup( record )
if self.superclass == Policy
raise "No default naming convention exists for subclasses of Policy. Override self.lookup if you want to use it in a subclass."
else
# naming convention lookup goes here
end
end
end
I feel like this is the least astonishing thing for how this code will be used. If someone has some reason to provide a #lookup method on a subclass, they can set one, but if they call the one that was inherited they get an exception that tells them it doesn't make sense to do so.
As to how to decide who gets the "answer" since they both answered 1/2 of my question, my personal habit in the case of a "tie" has been to accept the answer from the person with less reputation at the time.
Thank you both for your help!
undef_method
seems to work. According to the documentation,
Prevents the current class from responding to calls to the named method. Contrast this with remove_method, which deletes the method from the particular class; Ruby will still search superclasses and mixed-in modules for a possible receiver.
class Policy
def self.lookup(object)
end
end
class RecordPolicy < Policy
class << self
undef_method :lookup
end
end
Policy.lookup nil
# => nil
RecordPolicy.lookup
# => NoMethodError: undefined method `lookup' for RecordPolicy:Class
Using #inherited
, we can do
class Policy
def self.lookup(object)
end
def self.inherited(sub)
sub.class_eval do
class << self
undef_method :lookup
end
end
end
end
class RecordPolicy < Policy
end
RecordPolicy.lookup
# => NoMethodError: undefined method `lookup' for RecordPolicy:Class
Using sub.class_eval { undef_method :lookup }
won't work, because that will undefine an instance method of RecordPolicy
. It needs to be invoked on RecordPolicy's eigenclass instead.