Создание объекта Expando в Ruby - PullRequest
4 голосов
/ 16 января 2011

Есть ли лучший способ написать этот класс Expando? То, как это написано, не работает. Я использую Ruby 1.8.7

стартовый код, указанный в https://gist.github.com/300462/3fdf51800768f2c7089a53726384350c890bc7c3

class Expando
    def method_missing(method_id, *arguments)
        if match = method_id.id2name.match(/(\w*)(\s*)(=)(\s*)(\.*)/)
              puts match[1].to_sym # think this was supposed to be commented 
              self.class.class_eval{ attr_accessor match[1].to_sym } 
              instance_variable_set("#{match[1]}", match[5])
        else
              super.method_missing(method_id, *arguments)
        end  
    end    
end

person = Expando.new 
person.name = "Michael"
person.surname = "Erasmus"
person.age = 29 

Ответы [ 2 ]

9 голосов
/ 16 января 2011

Самый простой способ написать это - вообще не писать!:) См. Класс OpenStruct , включенный в стандартную библиотеку:

require 'ostruct'

record = OpenStruct.new
record.name    = "John Smith"
record.age     = 70
record.pension = 300

Если бы я собирался написать его, я бы сделал это так:

# Access properties via methods or Hash notation
class Expando
  def initialize
    @properties = {}
  end
  def method_missing( name, *args )
    name = name.to_s
    if name[-1] == ?=
      @properties[name[0..-2]] = args.first
    else
      @properties[name]
    end
  end
  def []( key )
    @properties[key]
  end
  def []=( key,val )
    @properties[key] = val
  end
end

person = Expando.new
person.name = "Michael"
person['surname'] = "Erasmus"
puts "#{person['name']} #{person.surname}"
#=> Michael Erasmus
2 голосов
/ 16 января 2011

Если вы просто пытаетесь получить рабочую Expando для использования, используйте вместо нее OpenStruct.Но если вы делаете это для образовательной ценности, давайте исправим ошибки.

Аргументы method_missing

Когда вы вызываете person.name = "Michael", это переводится в вызов person.method_missing(:name=, "Michael"), так что вам не нужно извлекать параметр с помощью регулярного выражения.Назначаемое вами значение является отдельным параметром.Следовательно,

if method_id.to_s[-1,1] == "="     #the last character, as a string
   name=method_id.to_s[0...-1]     #everything except the last character
                                   #as a string
   #We'll come back to that class_eval line in a minute
   #We'll come back to the instance_variable_set line in a minute as well.
else
   super.method_missing(method_id, *arguments)
end

instance_variable_set

Имена переменных экземпляра начинаются с символа @.Это не просто синтаксический сахар, это на самом деле часть названия.Поэтому вам нужно использовать следующую строку для установки переменной экземпляра:

instance_variable_set("@#{name}", arguments[0])

(обратите внимание также на то, как мы извлекли значение, которое мы назначаем, из массива arguments)

class_eval

self.class относится к классу Expando в целом.Если вы определите attr_accessor для него, то каждые expando будет иметь аксессор для этого атрибута.Я не думаю, что это то, что вы хотите.

Скорее, вам нужно сделать это внутри блока class << self (это единственный класс или собственный класс self).Это работает внутри собственного класса для self.

Так что мы выполним

class << self; attr_accessor name.to_sym ; end

Однако переменная name на самом деле не доступна внутри, поэтому мы собираемсясначала нужно выделить одноэлементный класс, а затем запустить class_eval.Обычный способ сделать это - использовать собственный метод eigenclass Итак, мы определяем

  def eigenclass
    class << self; self; end
  end

и затем вместо этого вызываем self.eigenclass.class_eval { attr_accessor name.to_sym }

Решение

Объедините все это, и окончательное решение получится до

class Expando
  def eigenclass
    class << self; self; end
  end

  def method_missing(method_id, *arguments)
    if method_id.to_s[-1,1] == "=" 
      name=method_id.to_s[0...-1]
      eigenclass.class_eval{ attr_accessor name.to_sym }
      instance_variable_set("@#{name}", arguments[0])
    else
      super.method_missing(method_id, *arguments)
    end      
  end    
end
...