66

I'm running some Ruby code which evals a Ruby file every time its date changes. In the file, I have constant definitions, like

Tau = 2 * Pi

and, of course, they make the interpreter display the unwanted "already initialized constant" warning every time, so, I'd like to have the following functions:

def_if_not_defined(:Tau, 2 * Pi)
redef_without_warning(:Tau, 2 * Pi)

I could avoid the warning by writing all my constant definitions like this:

Tau = 2 * Pi unless defined?(Tau)

but it is inelegant and a bit wet (not DRY).

Is there a better way to def_if_not_defined? And how to redef_without_warning?

--

Solution thanks to Steve:

class Object
  def def_if_not_defined(const, value)
    mod = self.is_a?(Module) ? self : self.class
    mod.const_set(const, value) unless mod.const_defined?(const)
  end

  def redef_without_warning(const, value)
    mod = self.is_a?(Module) ? self : self.class
    mod.send(:remove_const, const) if mod.const_defined?(const)
    mod.const_set(const, value)
  end
end

A = 1
redef_without_warning :A, 2
fail 'unit test' unless A == 2
module M
  B = 10
  redef_without_warning :B, 20
end
fail 'unit test' unless M::B == 20

--

This question is old. The above code is only necessary for Ruby 1.8. In Ruby 1.9, P3t3rU5's answer produces no warning and is simply better.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Eldritch Conundrum
  • 8,452
  • 6
  • 42
  • 50
  • 5
    Why do you want to redefine a constant? Better to namespace constants by keeping them in your own classes or modules--this way they'll never conflict with other constants. – Jordan Running Jul 30 '10 at 21:11
  • 1
    I want to redefine a constant because I want to use constants naturally as if I wasn't using an automatic source code reloader, so I won't accept any "just don't use a constant" answer. – Eldritch Conundrum Jul 30 '10 at 21:19
  • 2
    What's inelegant and not DRY about `Tau = 2 * Pi unless defined?(Tau)`? – jrdioko Dec 08 '10 at 17:42
  • 'Tau' is written twice. Not a big deal, except when the name is long or gets renamed uncarefully. But I prefer 'redef :Tau, 2*Pi' – Eldritch Conundrum Jan 25 '11 at 21:00
  • 8
    I'm currently writing the second edition of the Ruby on Rails Tutorial, and I found this thread while Googling around to solve a Ruby problem I encountered in the process. It didn't turn out to address the exact issue I was having, but let me say that, since I am also the author of The Tau Manifesto, it made me very happy nonetheless. :-) – mhartl Jan 28 '12 at 21:32
  • @Jordan Simple use case: a library defined a timeout as a constant, but the timeout is too small. – Phillipp Jan 08 '15 at 18:03

4 Answers4

73

The following module may do what you want. If not it may provide some pointers to your solution

module RemovableConstants

  def def_if_not_defined(const, value)
    self.class.const_set(const, value) unless self.class.const_defined?(const)
  end

  def redef_without_warning(const, value)
    self.class.send(:remove_const, const) if self.class.const_defined?(const)
    self.class.const_set(const, value)
  end
end

And as an example of using it

class A
  include RemovableConstants

  def initialize
    def_if_not_defined("Foo", "ABC")
    def_if_not_defined("Bar", "DEF")
  end

  def show_constants
    puts "Foo is #{Foo}"
    puts "Bar is #{Bar}"
  end

  def reload
    redef_without_warning("Foo", "GHI")
    redef_without_warning("Bar", "JKL")
  end

end

a = A.new
a.show_constants
a.reload
a.show_constants

Gives the following output

Foo is ABC
Bar is DEF
Foo is GHI
Bar is JKL

Forgive me if i've broken any ruby taboos here as I am still getting my head around some of the Module:Class:Eigenclass structure within Ruby

Steve Weet
  • 28,126
  • 11
  • 70
  • 86
  • Sure, the key to this answer is simply first calling `Object.send(:remove_const,'Tau') if Object.const_defined?('Tau')`, which undefines the constant, thus preempting the warning. Great approach. – hayesgm Aug 11 '13 at 22:38
  • Yep, or just `send(:remove_const, :CONST) if const_defined?(:CONST)` if you're in class (not instance) scope. – thewoolleyman Jun 18 '14 at 23:32
6

Another approach, using $VERBOSE, to suppress warnings, is discussed here: http://mentalized.net/journal/2010/04/02/suppress_warnings_from_ruby/

Update 2020/5/6: In response to the comment that the link is now dead, I am pasting an example here from my old project, though I can't say whether and in what circumstances it is a good approach:

original_verbose = $VERBOSE
$VERBOSE = nil # suppress warnings
# do stuff that raises warnings you don't care about
$VERBOSE = original_verbose
Paul Lynch
  • 1,297
  • 13
  • 20
  • 2
    Yes. As mentioned in your link, a better implementation of silence_warnings exist in Rails: http://api.rubyonrails.org/classes/Kernel.html#M002564 But that approach is inferior to the accepted answer, because it probably has side effects on other threads. – Eldritch Conundrum Aug 30 '12 at 23:23
  • Also, that link is now dead. – Sixtyfive Apr 30 '20 at 09:11
4

If you want to redefine a value then don't use constants, use a global variable instead ($tau = 2 * Pi), but that's not a good practice too. You should make it an instance variable of a suitable class.

For the other case, Tau = 2 * Pi unless defined?(Tau) is perfectly alright and the most readable, therefore the most elegant solution.

Leventix
  • 3,789
  • 1
  • 32
  • 41
2

Unless the values of the constants are pretty weird (i.e. you have constants set to nil or false), the best choice would be to use the conditional assignment operator: Tau ||= 2*Pi

This will set Tau to 2π if it is nil, false or undefined, and leave it alone otherwise.

Chuck
  • 234,037
  • 30
  • 302
  • 389
  • 1
    Nice idea... Unfortunately, it's not very portable: depending on the ruby version and implementation (ruby/jruby), the affectation to a constant with ||= gave me three different results. Either it works quietly as intended (jruby1.5), either I get an "uninitialized constant" failure (ruby1.8), either I get a warning even if no affectation takes place (jruby1.2). – Eldritch Conundrum Jul 30 '10 at 22:05