views:

176

answers:

4

I'm running some ruby code which evals a .rb file everytime its date changes. In the file, I happen to 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)

Of course, 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?

--

[Edit] 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
+1  A: 

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
+1  A: 

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
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
+1  A: 

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
A: 

Steve, your two functions solve the problem, thank you! The only thing is, they only work at toplevel.

def_if_not_defined(:Name, "EldritchConundrum")
puts Name # ok

module Math
  def_if_not_defined(:Tau, 6.28)
end
puts Math::Tau # NameError
Eldritch Conundrum