20

Preface: This is in the context of a Rails application. The question, however, is specific to Ruby.

Let's say I have a Media object.

class Media < ActiveRecord::Base
end

I've extended it in a few subclasses:

class Image < Media
  def show
    # logic 
  end
end

class Video < Media
  def show
    # logic 
  end  
end

From within the Media class, I want to call the implementation of show from the proper subclass. So, from Media, if self is a Video, then it would call Video's show method. If self is instead an Image, it would call Image's show method.

Coming from a Java background, the first thing that popped into my head was 'create an abstract method in the superclass'. However, I've read in several places (including Stack Overflow) that abstract methods aren't the best way to deal with this in Ruby.

With that in mind, I started researching typecasting and discovered that this is also a relic of Java thinking that I need to banish from my mind when dealing with Ruby.

Defeated, I started coding something that looked like this:

def superclass_method
  # logic
  this_media = self.type.constantize.find(self.id)
  this_media.show      
end

I've been coding in Ruby/Rails for a while now, but since this was my first time trying out this behavior and existing resources didn't answer my question directly, I wanted to get feedback from more-seasoned developers on how to accomplish my task.

So, how can I call a subclass's implementation of a method from the superclass in Rails? Is there a better way than what I ended up (almost) implementing?

trushkevich
  • 2,657
  • 1
  • 28
  • 37
Shaun
  • 4,789
  • 3
  • 22
  • 27
  • Do you mean you want to call a specific subclass's `show` method, or you want to call `show` on `self` within a method of the `Media` superclass? – outis Jan 30 '11 at 22:38
  • The former. Updated my question to clarify. – Shaun Jan 30 '11 at 23:10
  • Calling a specific subclass's `show` method would mean calling (e.g.) `Image#show`, no matter what the actual type of `self` is. – outis Jan 30 '11 at 23:27
  • You could do something like this: https://gist.github.com/BKSpurgeon/f9b675db7ff73ab909e02266c4af57b4 – BenKoshy Feb 12 '18 at 00:03

4 Answers4

23

Good question, but you are making it too complicated. Keep in mind a few principles and it should all be clear...

  • The types will be resolved dynamically, so if a show exists anywhere in the object's class hierarchy at the moment it is actually called then Ruby will find it and call it. You are welcome to type in method calls to anything that may or may not exist in the future and it's legal ruby syntax and it will parse. You can type in an expression that includes a reference to this_will_never_be_implemented and no one will care unless it actually gets called.

  • Even in Java, there is only one actual object. Yes, you may have a method in the superclass that's calling a method, but it is an instance of the derived class (as well as an instance of the base class) and so you can count on the new show being called.

  • In a sense, every Ruby class is an abstract class containing stubs for every possible method that might be defined in the future. You can call anything without access qualifiers in the base class or derived class.

If you want a null superclass implementation, you may want to define one that does nothing or raises an exception.

Update: Possibly, I should have just said "call show like any other method" and left it at that, but having come this far I want to add: You can also implement show with Ruby's version of multiple inheritance: include SomeModule. Since you are obviously interested in Ruby's object model, you might implement your attribute with a mixin just for fun.

DigitalRoss
  • 143,651
  • 25
  • 248
  • 329
  • 3
    My first reaction to this answer was "This doesn't have anything to do with the question. You are explaining object-orientation." But upon re-reading the question, I realize that the OP has absolutely no clue what object-orientation is, so this indeed pretty much the only sensible answer. +1 – Jörg W Mittag Jan 31 '11 at 02:45
  • 2
    Thanks, that cleared things up. Somewhere along the line, I picked up the notion that referring to self in a superclass got you an instance of the superclass type and that the instance couldn't respond with subclass functionality. Clearly, this notion was incorrect. – Shaun Jan 31 '11 at 04:23
  • 4
    @Shaun: that's the behavior in C++ for non-virtual functions, which use the static type of a variable (rather than the type of the object bound to the variable) to resolve method calls. Perhaps that is where you got the notion. – outis Jan 31 '11 at 07:56
10

As you know having a superclass know about subclass functionality is a big no-no, which is why you wanted the abstract method.

What you want to do is define show in your superclass. Then you can call it in the superclass and the subclass will call its own version but the superclass won't throw an error.

class Media < ActiveRecord::Base
  def show
    # This method should be overloaded in a subclass
    puts "Called from Media" 
  end

  def do_something
    show
  end
end

class Image < Media
  def show
    puts "Called from Image" 
  end
end

class Video < Media
  def show
    puts "Called from Video" 
  end  
end

i = Image.new
i.do_something
=> Called from Image

v = Video.new
v.do_something
=> Called from Video
3

Simple answer. Just call it. Ruby does not have compile-time checking so there is no one to complain that show isn't defined on Media. If @example is an instance of Image, then any call to @example.show will be sent to Image#show first, wherever it is made. Only if Image#show doesn't exist then the call will be passed on to Media, even if the call originated from code defined in Media

edgerunner
  • 14,873
  • 2
  • 57
  • 69
  • Keep in mind he's saying the `show` method would be called from the `Media` class so it breaks encapsulation to have a parent class know about the children that inherit from it. It's better to just add an empty `show` method to the superclass. –  Jan 30 '11 at 23:29
  • Encapsulation isn't a very Rubyish concept. If any piece of code can rely on duck-typing(which is a Rubyish thing to do), so can a parent have such an expectation from its children without having anything to make up for it. But of course there's nothing wrong with having a back-up method on the parent, empty or not. – edgerunner Jan 30 '11 at 23:35
  • Hmmm... I'm a bit torn on that but OK let's avoid the use of the word encapsulation. On the one hand it is not DRY to write a method that is never called in the parent class but it's tight coupling to have a parent class depend on a child class method. –  Jan 31 '11 at 00:34
  • I think that kind of tight coupling is acceptable if a parent class is built to be inherited(i.e. not used as-is), and if it is not, just write the fallback method on the parent. – edgerunner Jan 31 '11 at 00:40
2

If you want to call show on self from within a method of Media, simply do it. However, make sure self responds to the method call.

class Media < ActiveRecord::Base
    def foo
        if self.respond_to?(:show)
            self.show
        else
            ... // *
        end
    end
    ...
end

To avoid the branch, implement show on Media, using the * as the body of show

class Media < ActiveRecord::Base
    def foo
        self.show
    end
    def show
        ...
    end
end
outis
  • 75,655
  • 22
  • 151
  • 221
  • Sorry, I worded that part of my question poorly. What I want is to be able to call the implementation of show from the proper subclass. So, from Media, if `self` is a `Video`, then it would call Video's show method. – Shaun Jan 30 '11 at 23:12