views:

72

answers:

2

I'm writing a framework for querying the Mediawiki API. I have a Page class which represents articles on the wiki, and I've also got a Category class, which is-a Page with more specific methods (like being able to count the number of members in the category. I've also got a method Page#category? which determines if an instantiated Page object is actually representative of a Mediawiki category page, by querying the API to determine the namespace of the article.

class Page
  def initialize(title)
    # do initialization stuff
  end

  def category?
    # query the API to get the namespace of the page and then...
    namespace == CATEGORY_NAMESPACE
  end
end

class Category < Page
  # ...
end

What I would like to do is be able to detect if the user of my framework tries to instantiate a Mediawiki category using a Page object (ie. Page.new("Category:My Category")), and if so, instantiate a Category object, instead of a Page object, directly from the Page constructor.

It seems to me that this should be possible because it's reminiscent of single table inheritance in Rails, but I'm not sure how to go about getting it to work.

+1  A: 

You could define a method that instantiate the class and returns the instance. This is know as Factory Pattern


class PageFactory
  def create() // the pattern uses "create".. but "new" is more Ruby' style
    if ( namespace == CATEGORY_NAMESPACE )
       return Category.new
    else
       return Page.new
    end
  end
end

class ClientClass
  def do_something()
    factory = PageFactory.new
    my_page = factory.create() 
    my_page.do_someting()
  end
end

Eridal
This won't work. `new` method in `PageFactory` is an instance method, and you are calling class method in `doSomething`. Also, it is not good idea to redefine `new` method, as it is used to instantiate the class on it has been called. BTW, Ruby convention is to use underscore_separated method names, not camelCased.
Mladen Jablanović
+1  A: 

Ok, couple of things:

You can't convert an instance of a class A to an instance of A's subclass B. At least, not automatically. B can (and usually does) contain attributes not present in A, it can have completely different constructor etc. So, AFAIK, no OO language will allow you to "convert" classes that way.

Even in static-typed languages, when you instantiate B, and then assign it to a variable a of type A, it is still instance of B, it is not converted to its ancestor class whatsoever.

Ruby is a dynamic language with powerful reflection capabilities, so you can always decide which class to instantiate in the runtime - check this out:

puts "Which class to instantiate: "
class_name = gets.chomp
klass = Module.const_get class_name
instance = klass.new

So, no need for any conversion here - just instantiate the class you need in the first place.

Another thing: as I mentioned in the comment, method category? is simply wrong, as it violates OOP principles. In Ruby, you can - and should - use method is_a?, so your check will look like:

if instance.is_a? Category
  puts 'Yes, yes, it is a category!'
else
  puts "Nope, it's something else."
end

This is just a tip of the iceberg, there's lot more about instantiating different classes, and another question I have linked in the comment can be a great starting point, although some code examples there might confuse you. But it is definitely worth understanding them.

Edit: After re-reading your updated question, it seems to me that the right way for you would be to create a factory class and let it do the detecting and instantiating different page types. So, user wouldn't call Page.new directly, but rather call something like

MediaWikiClient.get_page "Category:My Category"

and get_page method would instantiate corresponding class.

Mladen Jablanović
I tried to make it clearer in my edit, but `Page#category?` isn't checking what type of class the object is, it's using a page's information to determine what namespace it falls under. I don't know where else I would put that...
Daniel Vandersluis
Anyways, I guess I wasn't really familiar with the Factory pattern, but that does seem to be the way to go, thanks.
Daniel Vandersluis
Ah, sorry, I didn't realize that you were talking about WikiMedia namespaces, not Ruby namespaces! :) Anyway, the place to check the WM namespace and instantiate appropriate class accordingly would be the factory. Of course, you _can_ put factory method into your `Page` class, again take a look at the other question - Brian Campbell gave great answer and code samples there.
Mladen Jablanović
"AFAIK, no OO language will allow you to "convert" classes that way" Oh, there's always an exception! :-) In this case, CLOS has `CHANGE-CLASS` which does just that. There are generic functions that can be used to control how exactly it works.
Ken