Как использовать хеш-ключи как методы для класса?

У меня есть класс и хэш. Как я могу заставить хэшировать динамически методы в классе с ключом как имя метода?

class User
  def initialize
    @attributes = {"sn" => "Doe", "givenName" => "John"}
  end
end

Например, я хотел бы иметь следующий вывод Doe:

u = User.new
puts u.sn

Ответы

Ответ 1

def method_missing(name, *args, &blk)
  if args.empty? && blk.nil? && @attributes.has_key?(name)
    @attributes[name]
  else
    super
  end
end

Объяснение: Если вы вызываете метод, который не существует, метод method_missing вызывается с именем метода в качестве первого параметра, за которым следуют аргументы, заданные методу и блоку, если он был указан.

В вышеизложенном мы говорим, что если метод, который не был определен, вызывается без аргументов и без блока, а хэш имеет запись с именем метода как ключ, он возвращает значение этой записи. В противном случае он будет действовать как обычно.

Ответ 2

Просто используйте OpenStruct:

require 'ostruct'
class User < OpenStruct
end

u = User.new :sn => 222
u.sn

Ответ 3

Решение sepp2k - это путь. Однако, если ваши @attributes никогда не меняются после инициализации и вам нужна скорость, вы можете сделать это следующим образом:

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"

Это генерирует все методы заранее...

Ответ 4

На самом деле severin имеет лучшую идею, просто потому, что использование метода method_missing - это плохая практика, не все время, но большая часть.

Одна проблема с этим кодом, предоставляемая severin: возвращает значение, которое было передано инициализатору, поэтому вы не можете его изменить. Я предлагаю вам немного другой подход:

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

Позволяет проверить:

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

А также вы можете сделать это с помощью Struct:

attrs = {:name => 'John', :age => 22, :position => 'developer'}
keys = attrs.keys

user = Struct.new(*keys).new(*keys.map { |k| attrs[k] })

Позволяет проверить его:

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

Или даже OpenStruct, но будьте осторожны, чтобы он не создавал метод, если он уже имеет его в методах экземпляра, вы можете его найти, используя OpenStruct.instance_methods (из-за того, что тип используется, теперь я использую второй подход):

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

Да, так легко:

user.name
user[:name] # will give you an error, because OpenStruct isn't a Enumerable or Hash

Ответ 5

Вы можете "заимствовать" ActiveResource для этого. Он даже обрабатывает вложенные хэши и назначение:

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

Использование:

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')

Обратите внимание, что u.job - это User:: Job - этот класс автоматически создается. Там есть получение при назначении вложенного значения. Вы не можете просто назначить хэш, но должны обернуть его в соответствующий класс:

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

К сожалению, если вы хотите добавить вложенный хеш, который не имеет соответствующего класса, он уродливее, потому что вам нужно заставить ARes создать класс из хэша:

# 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