Инициализируйте класс Ruby от произвольного хеша, но только ключи с соответствием средствам доступа

http://www.php.net/manual/en/migration5.oop.php

В PHP 5 существует новая Объектная модель. Обработка PHP объектов была полностью переписана, допуская лучшую производительность и больше функций. В предыдущих версиях PHP объекты были обработаны как типы примитивов (например, целые числа и строки). Недостаток этого метода состоял в том, что семантически целый объект был скопирован, когда переменная была присвоена или передана в качестве параметра методу. В новом подходе на объекты ссылается дескриптор, а не значением (можно думать о дескрипторе как об идентификаторе объекта).

5
задан David Moles 14 May 2015 в 19:07
поделиться

6 ответов

Это то, что я использую (я называю эту идиому hash-init).

 def initialize(object_attribute_hash = {})
  object_attribute_hash.map { |(k, v)| send("#{k}=", v) }
 end

Если вы используете Ruby 1.9, вы можете сделать это еще чище (send разрешает частные методы):

 def initialize(object_attribute_hash = {})
  object_attribute_hash.map { |(k, v)| public_send("#{k}=", v) }
 end

Это вызовет ошибку NoMethodError, если вы попытаетесь назначить foo, а метод «foo =» не существует. Если вы хотите сделать это чисто (назначить атрибуты для каких писателей), вам следует выполнить проверку

 def initialize(object_attribute_hash = {})
  object_attribute_hash.map do |(k, v)| 
    writer_m = "#{k}="
    send(writer_m, v) if respond_to?(writer_m) }
  end
 end

, однако это может привести к ситуациям, когда вы вводите объекту неправильные ключи (скажем, из формы), и вместо того, чтобы громко терпеть неудачу, он просто проглотить их - впереди мучительная отладка. Так что в моей книге NoMethodError - лучший вариант (это означает нарушение контракта).

Если вам просто нужен список всех писателей (нет возможности сделать это для читателей), вы делаете

 some_object.methods.grep(/\w=$/)

, что означает «получить массив имен методов и grep для записей, которые заканчиваются одним знаком равенства после символа слова ".

9
ответ дан 18 December 2019 в 11:58
поделиться

You could override attr_reader, attr_writer and attr_accessor to provide some kind of tracking mechanism for your class so you can have better reflection capability such as this.

For example:

class Class
  alias_method :attr_reader_without_tracking, :attr_reader
  def attr_reader(*names)
    attr_readers.concat(names)
    attr_reader_without_tracking(*names)
  end

  def attr_readers
    @attr_readers ||= [ ]
  end

  alias_method :attr_writer_without_tracking, :attr_writer
  def attr_writer(*names)
    attr_writers.concat(names)
    attr_writer_without_tracking(*names)
  end

  def attr_writers
    @attr_writers ||= [ ]
  end

  alias_method :attr_accessor_without_tracking, :attr_accessor
  def attr_accessor(*names)
    attr_readers.concat(names)
    attr_writers.concat(names)
    attr_accessor_without_tracking(*names)
  end
end

These can be demonstrated fairly simply:

class Foo
  attr_reader :foo, :bar
  attr_writer :baz
  attr_accessor :foobar
end

puts "Readers: " + Foo.attr_readers.join(', ')
# => Readers: foo, bar, foobar
puts "Writers: " + Foo.attr_writers.join(', ')
# => Writers: baz, foobar
4
ответ дан 18 December 2019 в 11:58
поделиться

Попробуйте что-нибудь вроде этого:

class Test
  attr_accessor :foo, :bar

  def initialize(opts = {})
    opts.each do |opt, val|
      send("#{opt}=", val) if respond_to? "#{opt}="
    end
  end
end

test = Test.new(:foo => "a", :bar => "b", :baz => "c")

p test.foo # => nil
p test.bar # => nil
p test.baz # => undefined method `baz' for #<Test:0x1001729f0 @bar="b", @foo="a"> (NoMethodError)

Это в основном то, что делает Rails, когда вы передаете хеш params в new. Он проигнорирует все параметры, о которых он не знает, и позволит вам установить вещи, которые не обязательно определены attr_accessor, но все же имеют соответствующий установщик.

Единственным недостатком является то, что для этого действительно требуется, чтобы у вас был определен установщик (а не только аксессор), который может быть не тем, что вы ищете.

2
ответ дан 18 December 2019 в 11:58
поделиться

Accessors are just ordinary methods that happen to access some piece of data. Here's code that will do roughly what you want. It checks if there's a method named for the hash key and sets an accompanying instance variable if so:

def initialize(opts)
  opts.each do |opt,val|
    instance_variable_set("@#{opt}", val.to_s) if respond_to? opt
  end
end

Note that this will get tripped up if a key has the same name as a method but that method isn't a simple instance variable access (e.g., {:object_id => 42}). But not all accessors will necessarily be defined by attr_accessor either, so there's not really a better way to tell. I also changed it to use instance_variable_set, which is so much more efficient and secure it's ridiculous.

0
ответ дан 18 December 2019 в 11:58
поделиться

Нет встроенного способа получить такой список. Функции attr _ * по сути просто добавляют методы, создают переменную экземпляра и ничего больше. Вы можете написать для них обертки, чтобы они делали то, что хотите, но это может быть излишним. В зависимости от ваших конкретных обстоятельств вы можете использовать Object # instance_variable_defined? и Module # public_method_defined? .

Также избегайте использования eval, когда это возможно:

def initialize(opts)
  opts.delete_if{|opt,val| not the_list_of_readers.include?(opt)}.each do |opt,val|
    instance_variable_set "@#{opt}", val
  end
end
0
ответ дан 18 December 2019 в 11:58
поделиться

You can look to see what methods are defined (with Object#methods), and from those identify the setters (the last character of those is =), but there's no 100% sure way to know that those methods weren't implemented in a non-obvious way that involves different instance variables.

Nevertheless Foo.new.methods.grep(/=$/) will give you a printable list of property setters. Or, since you have a hash already, you can try:

def initialize(opts)
  opts.each do |opt,val|
    instance_variable_set("@#{opt}", val.to_s) if respond_to? "#{opt}="
  end
end
0
ответ дан 18 December 2019 в 11:58
поделиться
Другие вопросы по тегам:

Похожие вопросы: