How do I use hash keys as methods on a class?

后端 未结 5 1933
孤街浪徒
孤街浪徒 2020-12-05 05:16

I have a class and a hash. How can I get the members of the hash to dynamically become methods on the class with the key as the method name?

class User
  def         


        
相关标签:
5条回答
  • 2020-12-05 06:00

    You can "borrow" ActiveResource for this. It even handles nested hashes and assignment:

    require 'active_resource'
    class User < ActiveResource::Base
      self.site = ''  # must be a string
    end
    

    Usage:

    u = User.new "sn" => "Doe", "givenName" => "John", 'job'=>{'description'=>'Engineer'}
    u.sn  # => "Doe"
    u.sn = 'Deere'
    u.job.description  # => "Engineer"
    # deletion
    u.attributes.delete('givenName')
    

    Note that u.job is a User::Job - this class is auto-created. There's a gotcha when assigning to a nested value. You can't just assign a hash, but must wrap it in the appropriate class:

    u.job = User::Job.new 'foo' => 'bar'
    u.job.foo  # => 'bar
    

    Unfortunately, when you want to add a nested hash that doesn't have a corresponding class, it's uglier because you have to force ARes to create the class from the hash:

    # assign the hash first
    u.car = {'make' => 'Ford'}
    # force refresh - this can be put into a method
    u = User.new Hash.from_xml(u.to_xml).values.first
    
    0 讨论(0)
  • 2020-12-05 06:10

    The solution by sepp2k is the way to go. However, if your @attributes never change after the initialization and you need speed, then you could do it in this way:

    class User
      def initialize
        @attributes = {"sn" => "Doe", "givenName" => "John"}
        @attributes.each do |k,v|
          self.class.send :define_method, k do v end
        end
      end
    end
    
    User.new.givenName # => "John"
    

    This generates all the methods in advance...

    0 讨论(0)
  • 2020-12-05 06:10

    Actually severin have a better idea, just because usage of method_missing is a bad practice, not all the time, but most of it.

    One problem with that code provided by severin: it returns value that have been passed to initializer, so you cannot change it. I suggest you a little different approach:

    class User < Hash
      def initialize(attrs)
        attrs.each do |k, v|
          self[k] = v
        end
      end
    
      def []=(k, v)
        unless respond_to?(k)
          self.class.send :define_method, k do
            self[k]
          end
        end
    
        super
      end
    end
    

    Lets check it:

    u = User.new(:name => 'John')
    p u.name
    u[:name] = 'Maria'
    p u.name
    

    And also you can do it with Struct:

    attrs = {:name => 'John', :age => 22, :position => 'developer'}
    keys = attrs.keys
    
    user = Struct.new(*keys).new(*keys.map { |k| attrs[k] })
    

    Lets test it:

    p user
    p user.name
    user[:name] = 'Maria'
    p user.name
    user.name = 'Vlad'
    p user[:name]
    

    Or even OpenStruct, but be careful it will not create method if it already have it in instance methods, you can look for that by using OpenStruct.instance_methods (because of type is used, I'm now using second approach):

    attrs = {:name => 'John', :age => 22, :position => 'developer'}
    user = OpenStruct.new(attrs)
    

    Yep, so easy:

    user.name
    user[:name] # will give you an error, because OpenStruct isn't a Enumerable or Hash
    
    0 讨论(0)
  • 2020-12-05 06:15

    Just use OpenStruct:

    require 'ostruct'
    class User < OpenStruct
    end
    
    u = User.new :sn => 222
    u.sn
    
    0 讨论(0)
  • 2020-12-05 06:19
    def method_missing(name, *args, &blk)
      if args.empty? && blk.nil? && @attributes.has_key?(name)
        @attributes[name]
      else
        super
      end
    end
    

    Explanation: If you call a method, which does not exist, method_missing is invoked with the name of the method as the first parameter, followed by the arguments given to the method and the block if one was given.

    In the above we say that if a method, which was not defined, is called without arguments and without a block and the hash has an entry with the method name as key, it will return the value of that entry. Otherwise it will just proceed as usual.

    0 讨论(0)
提交回复
热议问题