While I have been working on code communicating with Google Data API I found that it would be very convenient to enclose components specific to particular Google service under same namespace, particularly because they could share common constants defined in that namespace. Since these components have many things in common I imlemented them as a Template Method, i.e. there is common implementation in base classes and derived classes supposed to customize it by fullfilling obligations imposed by it. Here is the short snapshot:
module Base
class Service
def atom_header
header["GData-Version"] = API_VERSION
end
end
end
module Contacts
API_VERSION = "1.0"
class Service < Base::Service
end
end
Here the Base::Service relies on constant API_VERSION accessible within derived class. However code above would not work, because Ruby rules for constant lookup are:
- Check lexical scope
- Check included modules
- Check superclass
- call #const_missing
Neither of them give us API_VERSION. One way to solve it would be to access API_VERSION via current class, i.e. via self.class:
module Base
class Service
def atom_header
header["GData-Version"] = self.class::API_VERSION
end
end
end
But to make it work we have to move Contacts::API_VERSION to the Contacts::Service::API_VERSION, i.e:
module Contacts
class Service < Base::Service
API_VERSION = "1.0"
end
end
Which might be Ok but as soon we have to access API_VERSION from, say, Base::Entry we have to define it in Contacts::Entry in similar manner, and that sort of duplication is better to be avoided. What we really want to do is to ask self.class - “Hey, what is your lexical scope?”. Ruby has method for getting lexical scope - Module#nesting:
module Base
class Service
def atom_header
Module.nesting # - > [Base::Service, Base]
end
end
end
Unfortunately there is not much help we could get from Module#nesting, because it gives us a lexical context of point of call, when we need a lexical context for self.class. It seems like an easiest solution would be just to parse self.class.name and find out a lexical context from there. That is what essencially the mini-gem namespaces does:
require 'namespaces'
module Base
class Service
include Namespaces
def atom_header
header["GData-Version"] = namespaces.first::API_VERSION
end
end
end
module Contacts
class Service < Base::Service
API_VERSION = "1.0"
end
end
Comments