Ruby nested modules as namespaces

12,947

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 extends 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).

Share:
12,947
SasQ
Author by

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, 2022

Comments

  • SasQ
    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 call helper by its full qualified name (A::helper) all the time from inside B. I'd prefer to tell Ruby somehow that this namespace prefix is "default" and call it simply as helper. In C++ I could just write using A::helper inside namespace B and it would solve the problem. But how to do it in Ruby?

    I tried adding include A and extend A inside B, 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 the A::helper to be used only from inside A's methods, because it's just some implementation function, where I factored out some common code used by many functions inside A, but now I don't want it to be visible for the outside world, just to A's methods. How can I do it?

    I tried with module_function + removing the A. prefix from all other functions which are supposed to be hidden, but then they're hidden also for other methods in A, 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
    SasQ almost 11 years
    Yes, 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 call foo, nor the other way around, because they're instance methods now. But I need both: cross-calling inside A, and default namespacing of A inside B 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
    SasQ almost 11 years
    I 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
    Steve Benner almost 11 years
    Ruby 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
    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
    SasQ almost 11 years
    Thanks 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).