views:

137

answers:

5

Hello,

I have some existing ruby classes in a app/classes folder:

class A
   ...
end

class B
   ...
end

I'd like to group those classes in a module MyModule

I know I could do like:

module MyModule
  class A
      ...
   end
   class B
      ...
   end
end

but is there a meta programming shortcut that could do the same so I could "import" all the existing classes ?

Thanks, Luc

+2  A: 
module Foo
  A = ::A
  B = ::B
end

Foo::A.new.bar

Note that the :: prefix on a constant starts searchign the global namespace first. Like a leading / on a pathname. This allows you differentiate the global class A from the modularized constant Foo::A.

Squeegy
A: 

Yes, in your module create the class and have it inherit from your outside classes. For example,

class A
...
end

module MyModule
 class NewA < A
 end
end

The class MyModule::NewA will have all the attributes and methods of class A.
Then again, modules in ruby are never locked, so there is nothing stopping you just writing the class definition straight into the module.

bennybdbc
+1  A: 

Use the const_missing hook. If the constant can't be found in the current module, try to resolve in the global namespace:

class A; end
class B; end

module M
    def self.const_missing(c)
        Object.const_get(c)
    end
end

M::A.new
M::B.new
banister
yes, that's great. Just to be sure, when you are talking about global namespace, how will it check in subfolder like app/classes ? Is this on in the global namespace by default ?
Luc
'global namespace' just means that the class definition is not nested within any other class or module. By the way, this code does not 'include' the classes into the module, it just makes those classes accessible from that module (but really this is all ruby can do anyway since ruby has no concept of 'nested' classes or modules - it's all just about the scope of the constant that references the class/module)
banister
Thanks, this is exactly what I needed :)
Luc
+2  A: 

@Squeegy's answer already tells you what to do, but I think it is equally important to understand why that works. And it's actually pretty simple: classes in Ruby are nothing special. They are just objects like any other object that get assigned to variables just like any other variable. More precisely: they are instances of the Class class and they usually get assigned to constants (i.e. variables whose name starts with an uppercase letter).

So, just like you can alias any other object to multiple variables:

a = ''
b = a
a << 'Hello'
c = b
b << ', World!'
puts c # => Hello, World!

You can also alias classes to multiple variables:

class Foo; end
bar = Foo
p bar.new # => #<Foo:0x1d9f220>

If you want to move the classes into the namespace instead of just aliasing them, you also need to set the original variables to some other object like nil, in addition to @Squeegy's answer:

::A = nil
::B = nil
Jörg W Mittag
Great information!
never_had_a_name
A: 

If you do want to put them in a module I don't see the point of first including them in the global namespace and then aliasing them inside the module. I think what you want to do (although I doubt it is a good thing to do) is something like this:

file classes/g1.rb

class A1
  def self.a
    "a1"
  end
end

class B1
  def self.b
    "b1"
  end
end

file classes/g2.rb

class A2
  def self.a
    "a2"
  end
end

class B2
  def self.b
    "b2"
  end
end

file imp.rb

module MyModule
  ["g1.rb", "g2.rb"].each do |file|
    self.class_eval open("classes/#{file}"){ |f| f.read }
  end
end

puts defined? MyModule
puts defined? A1
puts defined? MyModule::A1

puts MyModule::A1.a
puts MyModule::B2.b

outputs

constant
nil
constant
a1
b2

I can think of a few disadvantages of this approach (harder to debug for one thing, and probably a bit slower to load although I am only guessing).

Why don't you just do something like this:

Dir["classes/*.rb"].each do |file|
  contents = open(file) { |f| f.read }
  open(file, "w") do |f| 
    f.puts "module MyModule\n"
    contents.each { |line| f.write "  #{line}" }
    f.puts "\nend"
  end
end

That'll fix your classes to be in your module since in ruby you can reopen a module at any time. Then just include them like you do normally.

ormuriauga
In fact I do not really like the way to write a class with f.write.
Luc