Already Initialized Constant?

I’ve never noticed this before but during testing of one of my plugins I decided to show the ruby console right before the loading of the plugin. My code is the following:

THIS_DIR = File.dirname(__FILE__)
	# Fix for ruby 2.0
	if THIS_DIR.respond_to?(:force_encoding)
		THIS_DIR = THIS_DIR.dup.force_encoding("UTF-8")
	end

My error message is:

c:/users/administrator/appdata/roaming/sketchup/sketchup 2017/sketchup/plugins/medeek_foundation_ext/medeek_foundation_load.rbs:39: warning: already initialized constant Medeek_Engineering_Inc_Extensions::MedeekFoundationPlugin::THIS_DIR
c:/users/administrator/appdata/roaming/sketchup/sketchup 2017/sketchup/plugins/medeek_foundation_ext/medeek_foundation_load.rbs:36: warning: previous definition of THIS_DIR was here

I really have no idea what the problem is here or what I’m doing wrong. I guess I’ve never seen this before because I usually don’t show the console in the plugin load .rbz.

You are doing nothing wrong.
Ruby expects you to define a Constant only once.
When you reassign it there’s a warning message - as you see - but it’s still changed.
There are ways to redefine a Constant in code which suppress the error, but in your case why not recast it to only define it once, thus:

this_dir = File.dirname(__FILE__)
if this_dir.respond_to?(:force_encoding)
  THIS_DIR = this_dir.force_encoding("UTF-8")
else
  THIS_DIR = this_dir
end
2 Likes

You can also do …

this_dir = File.dirname(__FILE__)
if this_dir.respond_to?(:force_encoding)
  THIS_DIR ||= this_dir.force_encoding("UTF-8")
else
  THIS_DIR ||= this_dir
end

… OR …

unless defined?(THIS_DIR)
  this_dir = File.dirname(__FILE__)
  if this_dir.respond_to?(:force_encoding)
    THIS_DIR = this_dir.force_encoding("UTF-8")
  else
    THIS_DIR = this_dir
  end
end
1 Like

I’m not sure I fully understand what the double pipe or equals is doing in this case, never seen that operator before. I really do learn something every day as a developer.

Ruby’s Or Equals operator is covered in this topic

https://ruby-doc.org/core-2.4.0/doc/syntax/assignment_rdoc.html#label-Abbreviated+Assignment

1 Like

Just an other source…
ruby-style-guide#self-assignment
…and after that
ruby-style-guide#double-pipe-for-uninit

2 Likes

In this specific case this will suffice:

THIS_DIR  = File.dirname(__FILE__).force_encoding("UTF-8")

This works all the way back to SketchUp 2014, which is quite ancient by now. Trying to support older SU versions will make the code quite a bit bulkier and uglier as it doesn’t support a number of newer methods and idioms. EDIT: Not to mention SU API, like HtmlDialogs :open_mouth: .

In the general case I’d use a intermediate variable and set the constant once. This is just 3 lines of code and has a single level of indentation which makes it more readable.

this_dir = File.dirname(__FILE__)
this_dir.force_encoding("UTF-8") if this_dir.respond_to?(:force_encoding)
THIS_DIR = this_dir
1 Like

I agree the three line version is more compact and more readable, I will switch out my current code… yet again.

… but it won’t suppress the warnings.

The warnings are there because the Ruby core devs are heading towards true constants.
Already they have implemented no dynamic constant assignment within methods.

The average user is only going to load your code once, so they’ll not likely run into the warning.

It is you (the dev) who reloads your code during development that sees the warning.
There are several ways to handle this so constants are loaded once.

The simplest is to have all your constants in one file that is loaded the first time.
Then during dev when you are tweaking things you need only reload the file that was tweaked.

The other way is to use the same pattern we use to prevent multiple menu items …

… ie, the first file to load:

module Medeek
  module NiftyFoundation

    if !@loaded
      # define constants here
    end

    # define methods and classes, or load other files here, etc.

  end
end

… ie, the last file to load:

module Medeek
  module NiftyFoundation

    if !@loaded
      # define commands, menus and toolbars
      @loaded = true
    end

  end
end

I prefer not to clutter the code with checks if the constant is already defined. Instead the debug/test reload scrip can just hide the warnings. It’s not very important if this is “proper” Ruby as it’s never used in production, just for testing. This also has the benefit that it allows you to redefine the constants without restarting SketchUp when testing, at least for as long as Ruby supports it.

# Reload extension.
#
# @param clear_console [Boolean] Whether console should be cleared.
# @param undo [Boolean] Whether last oration should be undone.
#
# @return [void]
def self.reload(clear_console = true, undo = false)
  # Hide warnings for already defined constants.
  verbose = $VERBOSE
  $VERBOSE = nil
  Dir.glob(File.join(PLUGIN_ROOT, "**/*.{rb,rbe}")).each { |f| load(f) }
  $VERBOSE = verbose

  # HACK: Use a timer to make call to method itself register to console.
  # Otherwise the user cannot use up arrow to repeat command.
  UI.start_timer(0) { SKETCHUP_CONSOLE.clear } if clear_console

  Sketchup.undo if undo

  nil
end

oh yea, a 3d way. Ruby is so multi-paradigm.

  def quietly(*args)
    verbose, $VERBOSE = $VERBOSE, nil
    yield args
    $VERBOSE = verbose
  end

… and then in use:

  quietly do
    # load my files
  end