# # Elusion -- Build complex data objects on-the-fly # # Copyright (C) 2005 Christian Neukirchen # # This work is licensed under the same terms as Ruby itself. # class Elusion # # Create a new Elusion and using the (optionally) given block. # # If you pass the Array _special_names_, these names will be made # accessible even if they are already defined in Object. You can # use this to use names like +send+ or +id+ in an Elusion. # # Example: # project = Elusion.new([:id]) { |e| # e.name "Elusion" # e.id 42 # e.description "A convenient way to build complex objects on-the-fly " + # "with a focus on driving templating engines." # } # project.name #=> "Elusion" # def initialize(special_names=nil, &block) if special_names (class << self; self; end).instance_eval { special_names.each { |sn| undef_method sn rescue nil } } end @data = {} __define__(&block) end # # Evaluate the block in the Elusion. # # Example: # e = Elusion.new { |e| # e.foo "bar" # } # e.foo #=> "bar" # e.bar #~> NameError # # e.__define__ { |e| # e.bar "foo" # } # e.bar #=> "foo" # def __define__(&block) @state = :write block.call self if block @state = :read self end # # If in *write* mode (usually when inside a block passed to Elusion), # define _name_ to return _value_. If _name_ already exists, make # an Array of the values. If a block is given, create a sub-Elusion # of the block. # # If in *read* mode, if no _block_ given, return the value attached to # _name_. If a _block_ is passed, the value attached to _name_ will # be used to decide what to do (DWIM): # # * If the value responds to +each+, +each+ will be called on the # block (replaces loops). # * If the value is +true+, the block will be called with +self+ # (replaces true?). # * If the value is +false+ or +nil+, the block will not be called # (replaces true?). # * Else, the value will be yielded to the block (replaces +with+). # # In parentheses you can see what you would need if you used # Kashmir/Elusion without DWIM blocks. # def method_missing(name, value=nil, &block) if @state == :write if block value = self.class.new &block end if @data.has_key? name if @data[name].is_a? Array @data[name] << value else @data[name] = [@data[name]] << value end else @data[name] = value end else raise ArgumentError, "wrong number of arguments" if value if @data.has_key? name unless block @data[name] else if @data[name].respond_to? :each # Direct iteration with each @data[name].each &block elsif @data[name] == true # Direct call on true block.call @data elsif not @data[name] # Nothing on false/nil # else # Direct call block.call @data[name] end end else raise NameError, "undefined slot #{name} for #{self}" end end end end