# Allows you to build a Hash in a fashion very similar to Builder. Example: # Fork of https://gist.github.com/360506 by BrentD with some enhancements # # HashBuilder.build! do |h| # h.name "Nilesh" # h.skill "Ruby" # h.skill "Rails" # multiple calls of the same method will collect the values in an array # h.location "Udaipur, India" do # If a block is given, first argument will be set as value for :name # h.location do # h.longitude 24.57 # h.latitude 73.69 # end # end # # produces: # # {:name=>"Nilesh", :skill=>["Ruby", "Rails"], :location=>{:name=>"Udaipur, India", :location=>{:longitude=>24.57, :latitude=>73.69}}} class HashBuilder instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|^object_id$)/ } def initialize @hash = {} @target = @hash end def self.build! builder = HashBuilder.new yield builder builder.to_h end def to_h @hash end def inspect to_h.inspect end def attr!(key, value=nil) if block_given? parent = @target @target = {} @target[:name] = value if value yield # the block may not be able to override "name" because Class#name exists but takes no argument parent[key] = @target @target = parent else if @target.keys.include?(key) @target[key] = [@target[key]].flatten + [value] else @target[key] = value end end @hash end def method_missing(key, *args, &block) attr!(key, args.first, &block) # If block_given?, first argument will be taken as the value of :name. Others will be discarded. end end class Hash unless method_defined?(:build!) def build!(&block) ::HashBuilder.build!(&block) end end end