Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby: selective class inheritance?

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!

like image 814
Andrew Avatar asked Dec 05 '25 03:12

Andrew


1 Answers

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.

like image 64
Linuxios Avatar answered Dec 06 '25 16:12

Linuxios



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!