DRY Ruby Initialization with Hash Argument

后端 未结 6 2080

I find myself using hash arguments to constructors quite a bit, especially when writing DSLs for configuration or other bits of API that the end user will be exposed to. Wha

6条回答
  •  情深已故
    2020-11-29 17:47

    You don't need the constant, but I don't think you can eliminate symbol-to-string:

    class Example
      attr_reader :name, :age
    
      def initialize args
        args.each do |k,v|
          instance_variable_set("@#{k}", v) unless v.nil?
        end
      end
    end
    #=> nil
    e1 = Example.new :name => 'foo', :age => 33
    #=> #
    e2 = Example.new :name => 'bar'
    #=> #
    e1.name
    #=> "foo"
    e1.age
    #=> 33
    e2.name
    #=> "bar"
    e2.age
    #=> nil
    

    BTW, you might take a look (if you haven't already) at the Struct class generator class, it's somewhat similar to what you are doing, but no hash-type initialization (but I guess it wouldn't be hard to make adequate generator class).

    HasProperties

    Trying to implement hurikhan's idea, this is what I came to:

    module HasProperties
      attr_accessor :props
      
      def has_properties *args
        @props = args
        instance_eval { attr_reader *args }
      end
    
      def self.included base
        base.extend self
      end
    
      def initialize(args)
        args.each {|k,v|
          instance_variable_set "@#{k}", v if self.class.props.member?(k)
        } if args.is_a? Hash
      end
    end
    
    class Example
      include HasProperties
      
      has_properties :foo, :bar
      
      # you'll have to call super if you want custom constructor
      def initialize args
        super
        puts 'init example'
      end
    end
    
    e = Example.new :foo => 'asd', :bar => 23
    p e.foo
    #=> "asd"
    p e.bar
    #=> 23
    

    As I'm not that proficient with metaprogramming, I made the answer community wiki so anyone's free to change the implementation.

    Struct.hash_initialized

    Expanding on Marc-Andre's answer, here is a generic, Struct based method to create hash-initialized classes:

    class Struct
      def self.hash_initialized *params
        klass = Class.new(self.new(*params))
      
        klass.class_eval do
          define_method(:initialize) do |h|
            super(*h.values_at(*params))
          end
        end
        klass
      end
    end
    
    # create class and give it a list of properties
    MyClass = Struct.hash_initialized :name, :age
    
    # initialize an instance with a hash
    m = MyClass.new :name => 'asd', :age => 32
    p m
    #=>#
    

提交回复
热议问题