Ruby namespace best practice.
Posted: Fri Jan 04, 2013 7:33 pm
So there's been quite a bit of debate about the Ruby implementation- particularly around the issue of ensuring that libraries are available, and avoiding clashes between different plugins and modules.
Where libraries are kept, and the bundling of Ruby with exports we can't do much about, apart from to talk to the developers about our worries. But for purely Ruby-coded new classes. modules and methods, there are many things that we can do as programmers to minimise the chance of problems.
The trouble is that the Ruby namespace is global to everything sharing the same Ruby interpreter - whether its several schematic open at once in the editor, multiple plugin instances, or naturally all of the modules within the same schematic. Whenever a class, method or module gets defined in more than one place, the two definitions will clash - possibly re-defining methods, changing variable values etc.
So downloading a nice shiny new module from the forum could create headaches if the developer made some new classes that happened to share names with those in another module elsewhere in your design.
I have seen some good examples of programmers dealing with exactly this issue elsewhere, so I though I'd pass on a quick summary of the 'best practices' that I've come across...
1) Take good care of the name-space
By far the most common time that a class name gets used directly is when creating a new object. Once created, you'll normally have a variable that you use to reference the object. So it's not a big deal if class names are a bit verbose. Classes and modules can also be nested, to isolate them from the rest of the namespace...
Now you cannot access any class methods of class "Thingy" unless you prefix them with the module name "Trog".
The small amount of extra typing in the code is well worth it - now my class "Thingy" won't clash with Fred's class "Fred::Thingy". If we all tag our classes like this, then there will be less clashes - and using your name or "Tag" for the module name means that it simple to find the right people to try and resolve any outstanding problems.
In effect, each developer uses their own personal name space.
2) Global variables and constants
Again careful naming is the key here - but ask yourself if you really need things to be quite so global. Like classes, you can wrap constants and class variables into a Module namespace...
The class variable and contant are still effectively 'global', since the module itself is global - we just need to invoke the namespace or the variable accessor...
3) Standard Ruby and API classes
Never do anything that would change the behaviour of any of these when used in any of the myriad of ways that they can already be used.
Very occasionally it can really save your neck if you add a new method to an existing class. But even this should be a last resort - usually whatever the problem is can be got around by using module/class methods, or by making a new class that is sub-classed to the one you're trying to target.
As we've seen with FS3.01, the developers are adding new classes and methods from time to time - so even a brand new method that you added could have its feet trodden on in the future!
4) Ruby running order
Rubies get parsed at startup in the order of the module nesting within your schematic, and by the order that you put them into the schematic.
This can create problems where you are trying to call a new class or module from within another RubyEdit instance - because you might make the method call before the class definition has been parsed.
The safest way around this is to only call new classes from within the "event" section of your Ruby code - or methods that you know will only be called once "event" is running. If you call a new class within the "init" or "bare code" sections of a RubyEdit, all bets are off, and it will depend entirely on the parser whether those calls will resolve or not.
If you do need to initialise anything that requires a new class, you can easily do it by calling the intialisation from "event" depending on the setting of a flag....
You could also cut and paste the class definition into every module that needs it, just to be on the safe side. That would mean a lot of extra code to parse at load time though, rewriting the class definition over and over but without actually changing it.
You can get around this by checking whether the class already exists, and only parsing it if it hasn't been run yet...
...and there are similar ways to check if a class already has a particular method etc.
So, there are many things that we can do to make our code as robust as possible. When within the FS editing space, we just have to be careful about any new modules that we write or download. In time, the best way will become clear, we can all share it, and the "gurus" can point out potential problems in folks code if they ask.
PS) Don't try to copy and paste any of the code examples - I added the pipe characters '|' because the 'code' BBCode strips whitespace, and I can't stand to see code without nice neat indenting!!
And many thanks to: TIG, ThomThom, Fredo6, Dan and many others who develop Ruby plugins over at the Sketchup forums - their Developer section is well worth browse as an example of how a forum community can get together to minimise these sorts of hassles.
Where libraries are kept, and the bundling of Ruby with exports we can't do much about, apart from to talk to the developers about our worries. But for purely Ruby-coded new classes. modules and methods, there are many things that we can do as programmers to minimise the chance of problems.
The trouble is that the Ruby namespace is global to everything sharing the same Ruby interpreter - whether its several schematic open at once in the editor, multiple plugin instances, or naturally all of the modules within the same schematic. Whenever a class, method or module gets defined in more than one place, the two definitions will clash - possibly re-defining methods, changing variable values etc.
So downloading a nice shiny new module from the forum could create headaches if the developer made some new classes that happened to share names with those in another module elsewhere in your design.
I have seen some good examples of programmers dealing with exactly this issue elsewhere, so I though I'd pass on a quick summary of the 'best practices' that I've come across...
1) Take good care of the name-space
By far the most common time that a class name gets used directly is when creating a new object. Once created, you'll normally have a variable that you use to reference the object. So it's not a big deal if class names are a bit verbose. Classes and modules can also be nested, to isolate them from the rest of the namespace...
Code: Select all
module Trog
| class Thingy
| def initialize(name)
| @name = name
| end
| end #class
end # moduleNow you cannot access any class methods of class "Thingy" unless you prefix them with the module name "Trog".
Code: Select all
@a_thingy = Trog::Thingy.new("Testing")The small amount of extra typing in the code is well worth it - now my class "Thingy" won't clash with Fred's class "Fred::Thingy". If we all tag our classes like this, then there will be less clashes - and using your name or "Tag" for the module name means that it simple to find the right people to try and resolve any outstanding problems.
In effect, each developer uses their own personal name space.
2) Global variables and constants
Again careful naming is the key here - but ask yourself if you really need things to be quite so global. Like classes, you can wrap constants and class variables into a Module namespace...
Code: Select all
module Trog
| @@class_var = "Hello"
| CLASS_CONST = "I'm Trog"
| def Trog.class_var # Accessor method for @@class_var
| @@class_var
| end
end # moduleThe class variable and contant are still effectively 'global', since the module itself is global - we just need to invoke the namespace or the variable accessor...
Code: Select all
a = Trog::CLASS_CONST
a = Trog.class_var3) Standard Ruby and API classes
Never do anything that would change the behaviour of any of these when used in any of the myriad of ways that they can already be used.
Very occasionally it can really save your neck if you add a new method to an existing class. But even this should be a last resort - usually whatever the problem is can be got around by using module/class methods, or by making a new class that is sub-classed to the one you're trying to target.
As we've seen with FS3.01, the developers are adding new classes and methods from time to time - so even a brand new method that you added could have its feet trodden on in the future!
4) Ruby running order
Rubies get parsed at startup in the order of the module nesting within your schematic, and by the order that you put them into the schematic.
This can create problems where you are trying to call a new class or module from within another RubyEdit instance - because you might make the method call before the class definition has been parsed.
The safest way around this is to only call new classes from within the "event" section of your Ruby code - or methods that you know will only be called once "event" is running. If you call a new class within the "init" or "bare code" sections of a RubyEdit, all bets are off, and it will depend entirely on the parser whether those calls will resolve or not.
If you do need to initialise anything that requires a new class, you can easily do it by calling the intialisation from "event" depending on the setting of a flag....
Code: Select all
def make_new
| #blah blah...
| @start_done = true
end
def event i,v,t
| make_new unless @start_done
| #blah blah...
endYou could also cut and paste the class definition into every module that needs it, just to be on the safe side. That would mean a lot of extra code to parse at load time though, rewriting the class definition over and over but without actually changing it.
You can get around this by checking whether the class already exists, and only parsing it if it hasn't been run yet...
Code: Select all
unless Module.const_defined?(:NewClass)
| class NewClass
| def.......
| end # class
end # test if defined...and there are similar ways to check if a class already has a particular method etc.
So, there are many things that we can do to make our code as robust as possible. When within the FS editing space, we just have to be careful about any new modules that we write or download. In time, the best way will become clear, we can all share it, and the "gurus" can point out potential problems in folks code if they ask.
PS) Don't try to copy and paste any of the code examples - I added the pipe characters '|' because the 'code' BBCode strips whitespace, and I can't stand to see code without nice neat indenting!!
And many thanks to: TIG, ThomThom, Fredo6, Dan and many others who develop Ruby plugins over at the Sketchup forums - their Developer section is well worth browse as an example of how a forum community can get together to minimise these sorts of hassles.