I'm working on a module that, among other things, will add some generic 'finder' type functionality to the class you mix it into. The problem: for reasons of convenience and aesthetics, I want to include some functionality outside the class, in the same scope as the class itself.
For example:
class User
include MyMagicMixin
end
# Should automagically enable:
User.name('Bob') # Returns first user named Bob
Users.name('Bob') # Returns ALL users named Bob
User(5) # Returns the user with an ID of 5
Users # Returns all users
I can do the functionality within these methods, no problem. And case 1 (User.name('Bob')
) is easy. Cases 2–4, however, require being able to create new classes and methods outside User
. The Module.included
method gives me access to the class, but not to its containing scope. There is no simple "parent" type method that I can see on Class nor Module. (For namespace, I mean, not superclass nor nested modules.)
The best way I can think to do this is with some string parsing on the class's #name
to break out its namespace, and then turn the string back into a constant. But that seems clumsy, and given that this is Ruby, I feel like there should be a more elegant way.
Does anyone have ideas? Or am I just being too clever for my own good?
In your example,
User
is just a constant that points to aClass
object. You can easily create another constant pointer whenMyMagicMixin
is included:Of course this doesn't answer whether you should do such a thing.
This is a problem that comes up sometimes on the mailinglists. It's also a problem that comes up in Rails. The solution is, as you already suspected, basically Regexp munging.
However, there is a more fundamental problem: in Ruby, classes do not have a name! A class is just an object like any other. You can assign it to an instance variable, to a local variable, to a global variable, to a constant or even not assign it to anything at all. The
Module#name
method is basically just a convenience method that works like this: it looks through the list of defined constants until it finds one that points to the receiver. If it finds one, it returns the first one it can find, otherwise it returnsnil
.So, there's two failure modes here:
Module#name
will only return the first one it findsNow, if someone tries to call
As
to get a list ofA
s, they will be pretty surprised to find that that method doesn't exist, but that they can callBs
instead to get the same result.This does actually happen in reality. In MacRuby, for example
String.name
returnsNSMutableString
,Hash.name
returnsNSMutableDictionary
andObject.name
returnsNSObject
. The reason for this is that MacRuby integrates the Ruby runtime and the Objective-C runtime into one, and since the semantics of an Objective-C mutable string are identical to a Ruby string, the entire implementation of Ruby's string class is essentially a single line:String = NSMutableString
. And since MacRuby sits on top of Objective-C, that means that Objective-C starts first, which means thatNSMutableString
gets inserted into the symbol table first, which means it gets found first byModule#name
.I'd lean toward being too clever.
Even if there was an elegant solution, it seems rather odd to be including a module inside a class which creates classes outside the class.