user2398029
user2398029

Reputation: 6937

Load Ruby gem into a user-defined namespace

Given a gem that defines top-level classes that clash with some code I have written, is it possible to require the gem in such a way that all its classes are grouped inside a module I can define? For example, if an unsafe_gem defines a class:

class Word
  # ... some code
end

I would need something like:

class Word
  # My word class.
end

module SafeContainer
  # This obviously doesn't work
  # (i.e. the gem still defines ::Word).
  require 'unsafe_gem'
end

So that I can distinguish between:

Word.new # => The class I defined.
SafeContainer::Word.new # => The class defined by the gem.

Some further details: My code (e.g. the 'Word' class) is already wrapped in its own namespace. However, I want to be able to provide the user with the option of enabling a form of "syntactic sugar", which makes some classes directly accessible under the top-level namespace. This, however, creates a name clash with one of the gems I am using, which defines a top-level class. None of the currently proposed solutions work because the gem actually relies on its globally-defined class being there; so undefining the class breaks the gem. Of course, the gem has more than one file, and individually requiring its files into a module seems to be a very brittle solution. Currently, the only workaround I have found is this:

begin
  # Require the faulty gem.
  require 'rbtagger'
rescue 
  # If syntactic sugar is enabled...
  if NAT.edulcorated?
    # Temporarily remove the sugar for the clashing class.
    Object.const_unset(:Word); retry
  else; raise; end
ensure
  # Restore syntactic sugar for the clashing class.
  if NAT.edulcorated?
    Object.const_set(:Word, NAT::Entities::Word)
  end
end

I don't know why, but this makes my toenails curl. Anybody have a better solution?

Upvotes: 10

Views: 4202

Answers (3)

Jamison Dance
Jamison Dance

Reputation: 20184

Another, possibly better answer, comes from this question.

Take advantage of the fact that classes and modules are just objects, like so:

require 'unsafe_gem'
namespaced_word = Word
Word = nil


# now we can use namespaced_word to refer to the Word class from 'unsafe_gem'

#now your own code
class Word
  #awesome code
end

You have to make sure that unsafe_gem only defines one class, and that you require it before you define your own classes and modules so you don't accidentally set your own stuff to nil.

Upvotes: 5

Matthew Rudy
Matthew Rudy

Reputation: 16834

The simple answer is "no"

If we have a file 'word.rb';

class Word
  def say
    puts "I'm a word"
  end
end

and we try and require it, it will always load in the global scope.

If you knew the gem was just a single file, you could, however do the following.

module SafeContainer
  module_eval(File.read("word.rb"))
end

but this is unlikely to work in your case.

Upvotes: 1

Jamison Dance
Jamison Dance

Reputation: 20184

I think your best bet is to wrap your own code in a module. Depending on how much code you have written, this may or may not be a huge pain. However, it is the best way to be sure that your code won't clash with someone else's.

So your Word class becomes

module LoismsProject
  class Word
    #some awesome code
  end
end

That way you can safely require 'unsafe_gem'.

Upvotes: 1

Related Questions