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?
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 tothis_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.
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
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
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
精彩评论