OpenStruct vs Struct vs custom data classes


#1

Continuing the discussion from Nested Hashes and Arrays:


We are kind of getting away from the subject a bit, so I’m breaking this off to a “Struct-like” discussion thread.

I had to go look up Struct again because I always avoid using it, and never suggest it. I needed to refresh my memory as to why.

Otherwise, the name of this struct will appear as a constant in class Struct , so it must be unique for all Struct s in the system …

This is why in a shared environment I avoid them. The identifier name for Struct classes is added to the Struct class itself, as a local constant.

This means my Window struct can clash with Nat’s.

It not that it is a limitation, because it an be avoided by omitting a first String argument, but the potential for creating clashing constants by newbs is so great, that I’d only recommend it to more advanced coders.

But remember that Struct is just a factory class that generates data classes. By the time a coder is experienced enough to understand proper use of Struct in a shared environment, they’re talented enough to write their own wrapper or factory classes themselves.


#2

An example of a custom Struct-like factory.

Exercise:

As an exercise in writing a custom class factory similar to Struct in pure Ruby.

  1. Code a factory (class of module) that creates data attribute classes.

  2. Difference it from Struct that will not create constant identifier(s) in a shared namespace.
    Instead, write it so the coder must assign the returned class object to a reference within
    their own namespace(s).

  3. Just like the Struct class, pass symbols attribute names to the factory method that create full accessor attributes. (Both getter and setter methods.)

  4. The factory needs to set up the constructor method (initialize) in the generated classes that will accept constructor values to assign to the attributes. Any attribute values not passed into the constructor must assign a nil value to the attribute.

… Good luck


Click to view, to see Dan's version ...
module Author::SomeLibrary

  # A factory library module that creates data attribute classes.  
  # It can be used by qualification or as a mixin module. This is an
  # exercise in writing a custom class factory similar to Struct in
  # pure Ruby. The main difference with Struct is that it will not
  # create constant identifier(s) in a shared namespace. Instead, the
  # coder must assign the returned class object to a reference within
  # their own namespace(s).
  module DataForge

    extend self

    def new(*args)
      # check if all args are symbols
      nas = args.find {|arg| !arg.is_a?(Symbol) }
      if nas # not a symbol ...
        fail(
          ArgumentError,
          "argument (#{args.index(nas)}) is not a Symbol.",
          caller
        )
      end
      klass = Class.new
      # For each symbol name in the args array, create
      # an attribute with getter and setter method ...
      klass.class_eval {
        attr_accessor( *args )
        define_method(:initialize) { | *params |
          args.each_with_index do | arg, i |
            instance_variable_set( "@#{arg.to_s}".to_sym, params[i] )
          end
        }
      }
      #
      return klass
    end

  end # DataForge module

end # module Author::SomeLibrary

Example of use …


# From within some plugin namespace (module or class) ...
include Author::SomeLibrary

Person = DataForge.new(:name,:gender,:age,:occupation,:iq)
# Person is now a class with the named attributes.

smarty = Person.new("Albert Einstein","Male",76,"Physicist",160)
smarty.iq #=> 160
smarty.age > 50 #=> true