Using Mixins containing Toolbar code

I thought I was making progress with Ruby but I have now found that my knowledge regarding the use of mixins is flawed.

This much simplifed code shows a class, TestClass, containing a single variable ‘@state’. This class mixes in a module, TestModule, which has a single method ‘switch_state’. Creating an instance of TestClass and repeatedly calling ‘switch_state’ works as expected - as if the method were native to TestClass.

However, TestModule also creates a Command and adds this to a Toolbar instance. The command block contains the method name ‘switch_state’. If I click on the toolbar icon it reports that the method ‘switch_state’ cannot be found.

module SomeModule
module Testbar

  cmd = UI::Command.new("Test") {
    switch_state
  }
  UI.toolbar("Testbar").add_item cmd
    
  def switch_state
    @state = @state == 0 ? 1 : 0
  end
end # end of module Testbar

class TestClass
  include Testbar
  attr_reader :state
  
  def initialize state
    @state = state
  end

end # end of class
  
t = TestClass.new 0
puts t.state    # => 0
t.switch_state
puts t.state    # => 1
t.switch_state
puts t.state   # => 0

# clicking on toolbar command "Test" produces "undefined local variable or method `switch_state'"

end # end of module SomeModule

Any help that would realign my brain cells would be much appreciated.

This is a confusing technical matter about how modules, mixins and Ruby’s dynamic invocation of methods work. I hope I have it right and explain it adequately below. Likely @DanRathbun will chime in and clarify or correct me if I bungle it!

As it processes your source code, the Ruby interpreter doesn’t actually bind calls to methods. Rather, it codes a lookup of the method names it finds in your code. The lookup is within the namespace hierarchy that is active at that point in processing the source. Since the method could be added later, there is no interpretation-time error if the named method doesn’t exist at that time. Existence is resolved (and may fail) at runtime.

Now, a module can’t have instances, so it doesn’t really have instance methods you can call via the module itself. Rather, methods def’d in the module block are added to a class’s lookup system when the class includes the module. They are only accessible via instances of a class that includes the module.

Other code in the module’s code block is evaluated when the block runs, in the context of that module. It is not re-evaluated when the module is mixed into a class.

So, at the time your code creates the UI.Command and toolbar, it is running in the TestBar module’s namespace, not in TestClass’s namespace. Ruby generates the runtime code to look up and execute a method named “switch_state” in the module’s namespace because that is what was active when the code creating the Command was evaluated. But when you actually invoke the command via the toolbar, no such instance method can be found. It is accessible via a TestClass instance, but not directly via the module. So, at runtime the lookup fails and you get the error message.

Besides the other errors … the above is frivolous because state() does nothing more than return @state, so the above method’s code is really @state = @state.

The def is also wrong in that there cannot be spaces in method names, and if you intended that state is an argument, then you need parenthesis around the argument list for method definitions. Ie …

def initialize(state)

The main error (as Steve will likely say) is that the mixin module’s @state variable is distinct from the class’ @state variable.

(Move the attr_reader :state line into the mixin module.)
EDIT: Ie, moving the statement will cause the @state attribute (and it’s reader method) to be properly created in the mixin module, and therefore available to any class that mixes it in.

See the “Mixins” section from the ol’ “Pick Axe” book …
Pragmatic Programmer Guide: Modules

Basically mixing in using include allows the “mixee” to access the mixin’s instance variables, instance methods and constants. That’s it.

In your test snippet, the local reference cmd would not be accessible. (But as shown below there is not much cause to try use them with a mixin anyway.)


Note that mixin modules affect the “mixee” differently depending upon whether they are used with include or extend.

I agree with this, but further more the mixin module is not the place for a UI object definition like UI commands, toolbars and menu items. We usually put them inside a load guard block at the bottom of the extension’s submodule.

In your snippet "SomeModule" would be the extension submodule. This means your snippet is lacking a top level namespace module (which needs to be globally unique.) This will separate all of your extension submodules from everyone else’s extension submodules.
You could use your name "module PhilipBlenkinsop" or some fictitious business name or trademarkable brandname.

Please do not outdent (skip indents), it creates less readable code.

Anyway … for creating UI elements, since you will likley need to reload your code files during development, put them inside a load guard if block at the bottom of the extension submodule. Viz:

module PhilipBlenkinsop
  module SomeExtension

    # Definition of extension constants holding values
    TOOLBAR_NAME ||= "Testbar"

    # Definition (or persistence loading) of extension variables 

    # Definition of extension submodules and extension specific classes

    # Definition of extension methods

    if !defined(@ui_loaded)
      @ui_loaded = true

      # Definition of UI objects

    end

  end
end

The above scenario of “sections” can be divided into multiple files. (This has been covered in other topics. Please do a search.)

Steve - what I take away from this is that there is a solid wall between a module’s methods so I cannot apply them to variables in an instance of a class. I’m stumped how to get around this. If the Command in the module changed a variable in the module I would happily read that variable from methods in the class instance but I don’t think this is possible.

Dan

Besides the other errors … the above is frivolous because state() does nothing more than return @state , so the above method’s code is really @state = @state .

er, surely not? To quote your favorite (Pickaxe) book)

Rule: Methods are defined with the keyword def followed
by the method name and parameters where parameter are
optionally enclosed in parentheses

Not, perhaps, a preferred style but it does run quite happily I can assure you. Please bear in mind that the code quoted is not the code I am running. It is stripped down as far as I can take it. In practice I have an isolating module around all of my code as you suggest to keep it distinct from other extensions.

If you look back at the extension I wrote over a year ago (Toggler.rbz) all of the code was in modules and so I didn’t have this problem. In this case I need to isolate some code in a class instance but I need to change state of a variable within it from a Toolbar Command.

Still stumped.

To boil things down to the minimum …

I would like to change the value or state of a variable somewhere in my code using a Command in a Toolbar and be able to read that value in a class instance (preferably not using a global variable)

Well as you see here, it causes confusion in reading your code. It is best to always use parenthesis in method definitions, and when calling all methods except global methods (those defined in Kernel, BasicObject, Object, Module and Class.)

Omitting parenthesis and using non-standard indentation causes misread (as above) and we “spin out wheels” trying to help you.

A mixin is not (usually) the way to expose an instance state variable.

Probably better would be just what we call a wrapper method in a outer module. The module holds a @instance reference to the instance, the module defines a wrapper method that calls the instances attribute reader method, and the command calls the module’s wrapper method.

Where the Namespace module is already predefined. (Ie, saving an ident here.)

module Namespace::SomeExtension

  extend self

  CMD ||= {} # Hash to hold UI command references

  @instance = nil unless defined?(@instance)

  class TestClass

    attr_reader :state
  
    def initialize( state )
      @state = state
    end

    def switch_state
      @state = @state == 0 ? 1 : 0
    end

  end # end of class

  def get_state
    @instance ? @instance.state : 0
  end

  def create_instance(state = 0)
    @instance = TestClass.new(state)
  end

  def toggle
    @instance.switch_state if @instance
  end

  if !defined(@ui_loaded)
    @ui_loaded = true

    # Definition of UI objects

    CMD["Toggle"] = UI::Command.new("Toggle Test") {
      toggle()
    }.set_validation_proc {
      get_state() == 0 ? MF_UNCHECKED | MF_CHECKED
    }

    # Perhaps more command objects here ?

    # Define and load the toolbar:
    TOOLBAR = UI.toolbar("Testbar")

    CMD.each { |key,cmd|
      TOOLBAR.add_item(cmd)
    }

  end

end

Many thanks for this Dan. I’ll try and digest it and give it a try tomorrow.

True, but a module is an instance of class Module.

But it can if it uses the extend self trick to extend itself with itself. (See example of above.)

Yes it is possible. This would be the inverse of the example above. Ie, the state would be held in the module, and the instance would call a module method by qualifying the method call with the module name.

So the module method definition would be (something like) …

  def self.get_state
    @state
  end

… and the instance would retrieve the value via a ModuleName.get_state call.

(Note that the extend self trick will also cause public singleton methods to be created, so even in the example above the instance can call the module’s methods with qualification. However the definitions in that example just access the instance’s getter methods. Their definitions would be different if the state was held in the module rather than the instance.)


The main reason why we usually hold state in an instance object, is because of the Mac edition’s plurality of open model instances. The instances are unique to the model that “owns” them (or the model that the instance is associated with.)

If the state in question is the same regardless of all models (ie, likely a general extension option,) then we usually hold the state in the extension module itself. (I myself prefer to have such options in a hash object at the extension module level.)

I threw together some code, and then went AFK.

module SomeModule
  module Testbar

    def create_tool(cmd_text, toolbar)
      cmd = UI::Command.new(cmd_text) { switch_state }
      UI.toolbar(toolbar).tap { |t| t.add_item cmd }.show
      cmd
    end

    def switch_state
      @state = @state == 0 ? 1 : 0
      puts "switch_state @state #{@state}  #{self.state}"
    end
  end # end of module Testbar

  class TestClass
    include Testbar
    attr_reader :state

    def initialize(cmd_text, toolbar, init_state)
      @cmd = create_tool cmd_text, toolbar
      @state = init_state
    end
  end # end of class

end # end of module SomeModule

t = SomeModule::TestClass.new 'Test', 'Testbar', 0
t.switch_state
t.switch_state

Suggestions:

  1. Don’t name local variables or method parameters with names that match methods.
  2. Mixins and super classes are meant for code that is shared. I changed the code to try and show that idea.
  3. Always use parens for method declarations (def statements). Optional for method calls.

You followed up with:

Which I think is the heart of the question, and not mixins.
You could use class variable like “@@state”, in the module. If you want to reference a instance variable, it is not only possible that it has gone out of scope, but there could be more than one instance of your class (shouldn’t occur for Tools). Instead of putting that in the class definition, put it at the module level. Create a module method that returns it.

module SomeModule
module Testbar

@@state = 0

  cmd = UI::Command.new("Test") {
    switch_state
  }
  UI.toolbar("Testbar").add_item cmd

  def switch_state
    @@state = @@state == 0 ? 1 : 0
  end
  def get_state
    @@state
  end
end # end of module Testbar

class TestClass
# . . .

No need to mixin another module that the class already resides in.
You don’t even need an instance state variable called “@state”, unless it is necessary for when there is more than one instance and they can be different.

I didn’t use the getter method “get_state”, but you may need to access it from a different module, like a toolbar module. It is called a “class variable”, but I also assign them in modules, which eliminates the need for global variables. You can access it with:

testbar_state = SomeModule::Testbar.get_state

# It looks like this toolbar doesn't need a getter.
# You could call switch_state with:
SomeModule::Testbar.switch_state

I’m in a middle of studying Ruby, I always wan to do “trial an fail”… to see how it is…
So, my first imagination, as usual quick and dirty code snippet:
(I’m indicating the state with the icon “color” and with console output.)

module AuthorTopLevelNamespace
module AuthorPlugin
  extend self
  @@loaded = false unless defined?(@@loaded)
  
  def start_tool
    Sketchup.active_model.select_tool( @tool = Testtool.new ) unless @@run
  end
  
  def change_state
    @tool.change_state if @@run
  end
  
  unless @@loaded
    @@run = false
    @@state = false
    cmd1 = UI::Command.new("Start"){start_tool}
    cmd1.tooltip = "Start"
    cmd1.set_validation_proc {
      if @@run
        MF_DISABLED | MF_CHECKED
      else
        MF_ENABLED | MF_UNCHECKED
      end
    }
    cmd2 = UI::Command.new("Change"){change_state}
    cmd2.tooltip = "Change"
    cmd2.set_validation_proc {
      if @@state
        MF_ENABLED | MF_UNCHECKED
      else
        MF_DISABLED | MF_CHECKED
      end
    }
    toolbar1 = UI::Toolbar.new('State Test')
    toolbar1.add_item(cmd1)
    toolbar1.add_item(cmd2)
    toolbar1.restore
    @@loaded = true
  end
  
  class Testtool
    include AuthorPlugin

    def activate
      @@run = true
      puts "activated"
    end
    
    def deactivate(view)
      @@run = false
      @@state = false
      puts "deactivated"
    end
    
    def onLButtonDoubleClick(flags, x, y, view)
      puts "onLButtonDoubleClick:"
      @@state = !@@state
      puts "Change from Tool, state: #{@@state}"
    end
    
    def change_state
      @@state = !@@state
      puts "Change from module, state: #{@@state}"
    end
  end
end
end

My thanks to all who’ve offered advice here. There’s quite a lot to get my head around so I’m going to go silent for a while.

Ruby is multi-paradigm. We’ve given you several patterns here to choose from.

Your example does not define module methods that are accessible this way.

Also, if the state will be held by the module rather than the instance, it does not matter whether the module uses an @var or an @@var.