Ruby nested modules as namespaces
OK, since the only answer which was relevant to my question has been deleted, I'll try to answer my question myself, basing on that deleted answer by @sawa (thanks to @sawa for hinting me in the correct direction). I modified it somewhat to better fit my needs and be more elegant. Later I'll describe why the original @sawa's answer wasn't what I was looking for yet.
OK, so without further ado, here's my own attempt at the solution:
module Lib
module A
extend self
def foo
puts 'Lib::A::foo'
end
def helper
puts 'Lib::A::helper'
foo
end
end
module B
extend A
def self.bar
puts 'Lib::B::bar'
helper
end
end
end
puts 'Calling Lib::A::foo:'
Lib::A::foo # => Lib::A::foo
puts 'Calling Lib::A::helper:'
Lib::A::helper # => Lib::A::helper; Lib::A::foo
puts 'Calling Lib::B::bar:'
Lib::B::bar # => Lib::B::bar; Lib::A::helper; Lib::A::foo
Here's how it works:
First off, it defines all its methods as instance methods of the particular module class itself (A
in this case). But then, to make them available for external use without instantiating (which is impossible for modules after all), I extend
the A
module with itself, which makes those methods become its class methods too. But thanks to the fact that they're also instance methods of the module A
, they can be called from inside other methods of this module without prefixing them with the module name. The same goes for the module B
, which also extend
s itself with the module A
's methods, making them his own. Then I can call them inside B
's methods too without prefixing, just as I wanted.
As you can see, not only I can call both modules' methods from the outside as if they were their class's methods, and I can call A::helper
from B::foo
without fully qualifying its name, but I can also call A::foo
from A::helper
without qualifying. This is exactly what I needed and seems to work as I expected.
The only problem with this approach could be with the fact that their interfaces are mixing together. This is not much a problem inside B
, since that's what I actually wanted: to be able to access A
's methods as if it were B
's methods, without the need for prefixing them with a full qualification. So I've got what I deserved. But this might cause a problem from the outside, because it's an implementation detail of B
that it uses A
's methods internally. It shouldn't leak to the outside world, but it does. I'll try to fix it somehow with access control, maybe it'll be possible somehow.
Edit: Yup, it can be done by inserting the following line just after extend A
in B
:
private_class_method *A.public_instance_methods
This way B
can call A
's methods internally, but they're not accessible from the outside world.
Now what was wrong with the original @sawa's solution:
It's been using a third module only to proxy the interface through it. For me this was rather an ugly hack than an elegant solution, because it introduces this additional module which will confuse the users of such a library. They will not know whether they should use A
or C
, and why such a contraption is used at all. It's not obvious how it works by mere looking at it. It needs some more thorough analysis to figure out what it really does and why it is constructed that way. It's not a clean design.
In my solution, on the other hand, there are only two modules, as originally designed, and their purpose should be clear for users of this library. There's this strange extend self
, but still it appears to be a more common idiom in Ruby than spreading proxy modules all over the place.
So thanks guys for your attempts. Next time try to be less arogant (when you see someone asking a question, it's not always the case he's a noob) and fixated on your beloved One True Language (don't get me wrong, I like Ruby language, it's quite cool and clean, but it has some drawbacks too, as any language, and it's better to seek for solving them instead of burying your heads and pretending that there's no problem at all, since it's not something the language was designed for).
SasQ
I used to post some good answers on this site, answers that earn upvotes to this day. But the way Stack Overflow works these days no longer seems to be about sharing knowledge, getting answers, and welcoming community. Now your answers must be "politically correct" and complying to the highest nazi standards of those who didn't even ask for them, and who claim to know better than the original poster what's good for them. Often they even edited my answers and corrupted them from their original intended meaning, because of what they thought is a better answer. (Hey, if you think of a better answer, why don't you write your own? :q ) Same goes with questions: most of them are claimed as "dumb", "off topic", "duplicate", or whatever lame excuse they can come up with to close/delete it. Especially when they can't answer the question themselves immediately with a canned response copy-pasted from Wikipedia. I don't like it that Stack Overflow has become just one huge middle-fingered RTFM / SEO link farm for Wikipedia, and I don't want to participate in it anymore. That's not the type of "community" I want to be part of. I won't delete my old answers (and most likely they wouldn't allow me to do it anyway – they would revert them), but don't expect any more answers from me. I'm done with this site.
Updated on June 21, 2022Comments
-
SasQ about 2 years
I have a nested structure of modules, which are used only for namespacing purposes; no mixing into classes etc. So I have a code like this:
module Lib module A def A.foo puts 'Lib::A::foo' end end module B def B.bar puts 'Lib::B::bar' end end end
Now suppose I want to add one more helper method in module
A
, and I want it to be used easily by both modules. I get the following:module Lib module A def A.foo puts 'Lib::A::foo' end def A.helper puts 'Lib::A::helper' end end module B def B.bar puts 'Lib::B::bar' A::helper end end end
and it seems to work, but it has some drawback I'd like to get rid of:
I don't want to callhelper
by its full qualified name (A::helper
) all the time from insideB
. I'd prefer to tell Ruby somehow that this namespace prefix is "default" and call it simply ashelper
. In C++ I could just writeusing A::helper
inside namespaceB
and it would solve the problem. But how to do it in Ruby?I tried adding
include A
andextend A
insideB
, but none of them works. They seem to work only inside classes, when these modules are mixed in, but not when they're stand-alone, used just for namespacing purposes.Are there any other ways to make it work the way I want?
Oh, and one more thing:
Suppose a different scenario, where I want theA::helper
to be used only from insideA
's methods, because it's just some implementation function, where I factored out some common code used by many functions insideA
, but now I don't want it to be visible for the outside world, just toA
's methods. How can I do it?I tried with
module_function
+ removing theA.
prefix from all other functions which are supposed to be hidden, but then they're hidden also for other methods inA
, because they're instance methods then, and modules cannot be instantiated. So how can I hide a module method from outside world and still allow it for the internal use by other module methods?Edit Why downvoting? I tried to be as clear as I possibly can, that what I need is defaulting a namespace inside another namespace to get rid of long fully-qualified names altogether (not just aliasing them to something shorter), and that my problem doesn't concern classes & objects at all, just plain modules used for namespacing purposes only. How else could I explain it?
It's not my fault that namespacing mechanisms doesn't seem to be fully supported in Ruby natively as in languages such as C++, and that it seems to be just a side-effect of using modules and classes to have some functionality of true namespaces (they quack like ducks, they have ducky beaks, but they're platypuses, not ducks, and shouldn't be advertised as namespaces which they apparently aren't, because it only confuses people coming to Ruby from other languages with true namespaces), nor that you apparently don't understand concepts from other programming languages if they aren't easily possible in Ruby (since I see that you seem to pretend that my problem doesn't exist and revert back to what you can easily do in Ruby instead; but this is not what I need).
And why the only answer which was relevant to my question has been deleted? Not cool ;.
-
SasQ almost 11 yearsYes, this will work, as long as you don't need to call
A
's methods from each other. For example,helper
won't be able to callfoo
, nor the other way around, because they're instance methods now. But I need both: cross-calling insideA
, and default namespacing ofA
insideB
to get rid of long namespace prefixes at all. (I know I can just alias them to something short, but this is still not the same as getting rid of the prefix altogether by making it default.) -
SasQ almost 11 yearsI would suggest rethinking Ruby if it cannot natively support common OOP design strategies supported in other languages instead of burying your heads in the sand and blaming me for not doing what's possible in Ruby & following The Ruby Way(TM). Or calling modules "namespaces" which they apparently aren't. And please don't suggest I'm a noob, since I have a lot of experience in OO design from other languages, and I'm not a noob in Ruby either.
-
Steve Benner almost 11 yearsRuby is not designed like other languages, it is designed to be flexible, with a fundamental philosophy of 'there is more than one way to do it'. No one is suggesting you are a 'noob', but you are obvoiusly unaware of some of the established principles of Ruby. The purpose of a module is to be mixed in, which is why I mentioned there is 'more out there' to look at, it's a complex topic. Look, within a minute I found a source that could perfectly apply to your use case: blog.rubybestpractices.com/posts/gregory/…
-
SasQ almost 11 years"The purpose of a module is to be mixed in, which is why I mentioned there is 'more out there' to look at" I know mixins very well, and I even mentioned it at the beginning of my original post that it's not what I need. I need just namespaces, and modules are advertised as being the right tool for that job. So why they aren't? They seem to just pretend to be namespaces, but they're no more namespaces than classes are, if one is not able to alias or default them (which was the original purpose of introducing namespaces in the first place).
-
SasQ almost 11 yearsThanks for the link to the article. It doesn't address my problem at all, since it's just about some basic namespacing to avoid name clashes, nothing more. But thanks anyway, since I found there more examples of how Ruby blows it all up at many different places (such as silently mixing the interfaces of two classes from different namespaces without giving any error message).