Ruby "good practice" using constants?
-
@dan rathbun said:
@thomthom said:
I then use
PLUGIN
as a shortcut within that namespace.But you CAN still use
self
so I do not the point.Because I use
PLUGIN
within sub-modules when I need to refer back to the root namespace for the current plugin.<span class="syntaxdefault"><br />module TT</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">Plugins</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">Example<br /><br /> PLUGIN </span><span class="syntaxkeyword">= </span><span class="syntaxdefault">self<br /><br /> def self</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">hello_world<br /> puts </span><span class="syntaxstring">';)'<br /> </span><span class="syntaxdefault">end<br /><br /> module FooBar<br /> def self</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">magic_button<br /> PLUGIN</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">hello_world<br /> end<br /> end<br /><br />end<br /></span>
Since I use that pattern in my plugins I can port modules from one plugin to another with less rewrite - another benefit.
-
@brewsky said:
I am wondering of it could be a good idea / good practice to use a constant as a placeholder for my plugin's main library object.
But.. a class or module identifier IS a constant (it begins with a capital character.)
@brewsky said:
I considered this because a constant can be accessed from anywhere in the module, without having to resort to globals, or having to forward the object in every new class instance(as I do now)
(1) There is never any reason where you should resort to global references, for proprietary object references. (It's poor practice, clutters the global
ObjectSpace
and they can clash with other globals. Shared global constants are OK, such asMF_CHECKED
, ... these are designed to be used by everyone.)(2) Let's say you create a BIM library, that you allow me to use.
It's qualified constant reference isBrewsky::BIM::Lib
I can use that or, I can create a local reference that points at it. (After all, a
Module
instance object is like any other instance object in Ruby, it can have any number of references pointing at it, at any time. Also the number of references pointing at an instance object, at any specific time, can vary. Example singleton instances of a specific member of theInteger
class. There is only one integer 0, but many different references to it, from many different classes or modules.)So imagine I do not wish to type out the fully qualified name all the time:
module Dan module NiftyPlugin LIB = Brewsky;;BIM;;Lib model = Sketchup.active_model LIB.create_wall(model,[0,0,0],(8.0).inch,(9.0).feet,(24.0).feet) end end
But the reference does NOT have to be a local constant.
It can can an instance reference:
@lib = Brewsky::BIM::Lib
It can be a class/module var reference:
@@lib = Brewsky::BIM::Lib
.. or even a local var reference (within a method or block.)
lib = Brewsky::BIM::Lib
So the type of reference depends upon how you will use it, what scope you want it to have, and for how long you wish to use it (ie, temporary or persistent.)
Can you give us a code shell showing the namespace nesting, and how you wish to share the reference to your classes ??
-
@thomthom said:
Because I use
PLUGIN
within sub-modules when I need to refer back to the root namespace for the current plugin.<span class="syntaxdefault"><br />module TT</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">Plugins</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">Example<br /><br /> PLUGIN </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> self<br /><br /> def self</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">hello_world<br /> puts </span><span class="syntaxstring">';)'<br /></span><span class="syntaxdefault"> end<br /><br /> module FooBar<br /> def self</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">magic_button<br /> PLUGIN</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">hello_world<br /> end<br /> end<br /><br />end<br /></span>
Sub-modules do not inherit the local constants of outer modules.
@thomthom said:
Since I use that pattern in my plugins I can port modules from one plugin to another with less rewrite - another benefit.
This is what
include
andextend
are for. Shared library modules are best written as "mixin" modules.Didn't we just have this discussion in another topic recently ??
-
@dan rathbun said:
Sub-modules do not inherit the local constants of outer modules.
My snippet works. Try it.
@dan rathbun said:
This is what
include
andextend
are for. Shared library modules are best written as "mixin" modules.Does that duplicate the methods? Or do they all refer to the source module?
-
This on the other hand doesn't work:
-
I did the first all in one line, at the console, and it did not work:
` module Dan; OUTER = self; module Plugin; PLUGIN = self; class Special; CLASS = self; end; end; end
$ Dan::Plugin::Special::OUTER
%(#008000)[Error: #<NameError: (eval): uninitialized constant Dan::Plugin::Special::OUTER>
(eval)]` -
@thomthom said:
@dan rathbun said:
This is what
include
andextend
are for. Shared library modules are best written as "mixin" modules.Does that duplicate the methods?
NO.
@thomthom said:
Or do they all refer to the source module?
YES... it's like a proxy lookup.
So if you define two sub-modules that
include
a library mixin module of constants, and sub-module two changes a value of a constant, it changes IN the mixin module, and any other sub-module that hasinclude
d the mixin module, will see the changes. -
@thomthom said:
My snippet works. Try it.
Reason, because it's a Ruby interpreter block scope issue (Easter egg like thing.)
The interpreter reads the humanized script, and translates this:
module TT;;Plugins;;Example PLUGIN = self module FooBar def self.magic_button puts PLUGIN.name end end end
.. to (something similar on the C-side):
TT;;Plugins;;Example = Module.new { PLUGIN = self FooBar = Module.new { def self.magic_button puts PLUGIN.name end } }
So the
Foobar
definition block has access to the object references in the wrapping scope.IMHO: Although it works.. explaining why is complicated and will likely confuse newbies, and it not really following good organizational form, (which is what we want to teach newbies.)
-
@dan rathbun said:
So if you define two sub-modules that
include
a library mixin module of constants, and sub-module two changes a value of a constant, it changes IN the mixin module, and any other sub-module that hasinclude
d the mixin module, will see the changes.That's interesting. So one can then add all common methods and constants to the mixin... and no extra overhead.
-
@thomthom said:
That's interesting. So one can then add all common methods and constants to the mixin... and no extra overhead.
Yea! That is the whole point ... and it also works with the module vars in the mixin module.
However sometimes evaluation may actually occur with in the Mixin module, rather than the "mixee" module. SO .. test.
-
@dan rathbun said:
However sometimes evaluation may actually occur with in the Mixin module, rather than the "mixee" module. SO .. test.
What evaluation?
-
of methods... so example you'd have to pass in references from the mixee modules. (Don't expect the mixin method to have access to objects in the mixee module. ... From what I remember.)
However (as always,) mixing into classes is different. If the mixin instance method has a ref to a @var, the object used is the one in the mixee class. Which is what you would expect.
It's just that including modules into are a bit more "funky" then mixing modules into a class.
-
But we are getting off-topic here ... let's get back on constants.
-
@dan rathbun said:
of methods... so example you'd have to pass in references from the mixee modules. (Don't expect the mixin method to have access to objects in the mixee module. ... From what I remember.)
I'm not quite following you here..
Got a sample code to illustrate? -
Thanks guys!
I have to do some reading to be able to follow the discussion
I kinda lost it somewhere along the way...TT's first post came close to what I want to do.
This is more what I mean:module TT;;Plugins PLUGIN = QuadFaceTools.new end
@dan rathbun said:
Can you give us a code shell showing the namespace nesting, and how you wish to share the reference to your classes ??
This is an example of what I'm doing now.
I make some sort of "root-plugin" object(class-instance, not a module) that holds all other plugin objects/data.
And pass this on to all nested objects to be able to access the embedded data.module Brewsky;;BimTools class BimTools attr_accessor ;project_list, ;web_dialog def initialize btProject = BtProject.new(self) end end class BtProject attr_reader ;model, ;guid, ;name, ;description def initialize(bt) @bt = bt end def update_dialog(value) dialog = @bt.web_dialog # do something with "dialog" using "value" end end BimTools.new end
-
Sometimes I pass in the outer namespace via the constructor (as you show in your example,) but usually it is into Observer instances. I normally give the "handle" the name
@parent
,@outer
or similar.Access to data objects in the parent namespace, (if they are few,) can be done with more clarity, by also passing them in via the constructor call.
See my simple plugin example: [Code] AnimateSelection Example
In this example, the references are passed in as individual arguments.
But if there are many, it may make more sense to collect the objects into aHash
or aStruct
, and pass that in. -
I guess the way I'm passing the object into every sub-object is "structurally" the best way to go.
But in this way I'm constantly making new pointers to always the same old base-object.
And because there is only one instance, would it not be clearer to use some sort of "almost-global" object, such as a module-constant, and have access to it anywhere in the program?Like TT does?
And after thinking on this, maybe TT's way of just making a module as a base-object for the plugin is a better approach than my BimTools-class. Because is't only used once, the module-approach seems more fitting...Something like:
module Brewsky class BimTools attr_accessor ;project_list, ;web_dialog def initialize btProject = BtProject.new end end class BtProject attr_reader ;model, ;guid, ;name, ;description def update_dialog(value) dialog = PLUGIN.web_dialog # do something with "dialog" using "value" end end PLUGIN = BimTools.new end
-
Here is a SketchUp specific example of using a Constant Library mixin module so an author's various plugin modules (and/or submodules,) can share the references to the author's menu and submenu objects.
-
@dan rathbun said:
So for example, your nested module
Brewsky::BimTools::Manager
is actually an instance object, and the preceeding identifier is the reference to the instance.If you remember this... then it can be easier to understand how using an anonymous singleton proxy class instance inside your
Module
class instance, makes sense.Thank you very much for this very helpful post!
I completely missed it untill nowMy plugin is in need of "some" improvement...
-
@brewsky said:
And after thinking on this, maybe TT's way of just making a module as a base-object for the plugin is a better approach than my BimTools-class. Because is't only used once, the module-approach seems more fitting...
Oh hell yes.
If you need only one copy of a code object, then generally it should be a module.
If you need multiple copies of a code object (usually because the code must adapt to many other instance objects,) then you make it a class, and instantiate instances that are syncronized to a particular instance object.
Often coders try to avoid using a module, because they think it's a static kind of object, and they believe it is harder to use than a class instance.
What they miss is, that a module definition, is really an instance of classModule
. So for example, your nested moduleBrewsky::BimTools::Manager
is actually an instance object, and the preceeding identifier is the reference to the instance.If you remember this... then it can be easier to understand how using an anonymous singleton proxy class instance inside your
Module
class instance, makes sense.Imagine the
Module
class is "the hen".It lays an egg, which is your nested
Manager
module instance, that could be created thus:
` Brewsky::BimTools::Manager = Module.new {plugin managerial code here
}
But the Ruby interpreter calls the
new()` constructor for you on the C-side of things. (Ie, the defintion block syntax for scripts, was invented for human happiness and readability; Ruby itself does not really need it.)But having methods in modules communicate (call each other,) works a bit different than in a class definition. Instance method definitions in a module, are meant for Library Mixin modules. (Read up on the
include
andextend
methods.) They become different kinds of methods, depending on whether they are mixed into a class or module, and whetherinclude
andextend
is used to do the mixing.)So at first blush, the coder thinks they must define all methods in a module as module functions that must be called with
self.method_name()
, ... they find this cumbersome, and they switch back to using a class defintion, and using only one instance of it. (A sort of psuedo-singleton class.)BUT.. the egg can have a membrane inside it's shell. This membrane analogy is the anonymous singleton proxy class instance created with the following syntax:
module Brewsky module BimTools end end module Brewsky;;BimTools;;Manager # module variables and constants declared here MGR_VERSION ||= '1.2.3' @@bimmenu ||= nil class << self # self evaluates to the module instance # Everything in here acts like a class # instance BECAUSE IT ACTUALLY IS ! # In here we can access the module @@vars directly. # In here we define instance methods, not module methods. # But if they are public, they can be called like module # functions, from anywhere outside the module. private def get_version() MGR_VERSION end # In here we can call any other method in here, # without module qualification. public def version() get_version() end end # proxy class # Out here we can call any of the methods inside # the proxy class directly, without qualification. unless @@bimmenu @@bimmenu = UI.menu('Plugins').add_submenu('BIMTools') @@bimmenu.add_item('Version') { UI.messagebox("BIMTools ver #{version()}") } end end # module Brewsky;;BimTools;;Manager # 99.9% of the time, there is no good reason to # have any executing code outside your namespace.
EDIT(2012-12-16): changed post title to "Why use a module instead of a class ?"
Advertisement