On Tue, 21 Mar 2006, Dave Baldwin wrote:

> I am creating a DSL using Ruby.  A cut down version is below:
>
> class _Box
> 	attr_accessor		:width
>  		 def initialize (&block)
>               self.width = 20			# some default value
> 		instance_eval(&block) if block_given?
> 	end
> end
>
> # Helper function to avoid the DSL user having to use .new
> def Box(&block)
> 	_Box.new(&block)
> end
>
> I  want to do something like:
>
>    Box {width = 10}
>
> but the only way I can get it to work is
>
>    Box  {self.width = 10}
>
> Having to prefix the ivar with self.  makes the DSL look clunky and wouldn't 
> be acceptable to my users.
>
> Is there anyway to avoid the self.?  Using {@width = 10} will work but 
> prevents some necessary validation from taking place.
>
>
> I have an alternative where width is a method:
>
> class _Box
> 	def width (val = :no_param)
> 		if val != :no_param
> 			@width = val
> 		end
> 		@width
> 	end
> end
>
> so with this I can write
>
>    Box {width 10}
>
> which is acceptable from the DSL view point, but makes the getter operations
> more expensive.  In the real version a getter operation takes about 7
> statements to allow for evaluation of block if the ivar had been set to a
> Proc object and inheritance of values from its parent object (in a visual
> hierarchy).  The setter operation is often only done when the object is
> created but the getter is done very frequently so I want to move down the
> route of separate setters and getters rather than combining them in the one
> method as an optimization.

here is one simple way:

     harp:~ > cat a.rb
     class OpenStruct
       alias_method "__eval__", "instance_eval"
       alias_method "__set__", "instance_variable_set"
       alias_method "__get__", "instance_variable_get"
       instance_methods.each{ |m| undef_method m unless m =~ /^__/ }
       def initialize(&block) __eval__ &block end
       def method_missing m, *a, &b
         m = m.to_s.delete "=?"
         a.size == 0 ? __get__("@#{ m }") : __set__("@#{ m }", a.shift)
       end
     end

     module Initializable
       def initialize &block
         os = OpenStruct.new &block
         attributes.each{|a| instance_variable_set "@#{ a }", os.__send__(a)}
       end
       def to_s
         require "yaml"
         attributes.inject({}){|h,a| h.update a => send(a)}.to_yaml
       end
       def attributes
         self.class::ATTRIBUTES
       end
     end

     class Box
       ATTRIBUTES = %w[ width height ]
       ATTRIBUTES.each{|a| attr a}
       include Initializable
     end

     def Box(*a, &b) Box.new(*a, &b) end

     puts Box {
       width 42
       height 42
     }



     harp:~ > ruby a.rb
     ---
     height: 42
     width: 42


the idea is to initialize another object, the OpenStruct in this case, and then
relay the properties.  this object can have any 'slow' but convenient behaviour
your desire, leaving the Box class to have normal 'fast' attributes.

hth.

-a
-- 
share your knowledge.  it's a way to achieve immortality.
- h.h. the 14th dali lama