Question

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!

Was it helpful?

Solution 2

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.

OTHER TIPS

Just something to expand on @JimLim's answer.

First, this works because of the difference between what undef_method and remove_method.

remove_method actually deletes the method entirely. undef_method, according to the documentation:

Prevents the current class from responding to calls to the named method.

(Emphasis mine).

But...

If your problem is what you show in the question, I think you're thinking about this wrong. What's wrong about being able to call RecordPolicy.lookup? It might be useless, but it follows the Principle of Least Astonishment. The Policy class is the right place for this method, but if you implement it something like this:

def self.lookup(obj)
  if(self.superclass == Policy) #It's a subclass, return self
    return self
  else
    #look stuff up
  end
end

Nothing is out of place. Just because a method is useless on an object, if it would make sense *language*wise for it to be there, don't mess around with it. Ruby gives you great power to change these things for clarity, not to break language conventions and make confusing, unintuitive classes and subclassing behaviour.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top