Как заставить работать attr_accessor_with_default с коллекцией? - PullRequest
0 голосов
/ 14 августа 2010

Я хочу дать одной из моих моделей средство доступа к атрибутам, которое по умолчанию представляет собой массив из восьми нулей. Это первый синтаксис, который я пробовал:

attr_accessor_with_default:weekly_magnitude_list, [0,0,0,0,0,0,0,0]

Выше не получилось то, что я ожидал, потому что все экземпляры модели в конечном итоге совместно используют один и тот же объект Array. Блог (http://barelyenough.org/blog/2007/09/things-to-be-suspicious-of-attr_accessor_with_default-with-a-collection/), в котором я рассказывал, предлагал другой синтаксис, в основном оборачивая значение по умолчанию в блоке.

attr_accessor_with_default(:weekly_magnitude_list) {[0,0,0,0,0,0,0,0]}

Это не работает (для меня в Rails 3). Каждый раз, когда я вызываю метод доступа, я, похоже, получаю совершенно новый объект Array. Это означает, что я не могу писать.

Кто-нибудь знает правильный способ сделать это?

Для вашего удовольствия я включил вывод простого теста, демонстрирующего это:

class Container
  attr_accessor_with_default :naive_collection, [0,0]
  attr_accessor_with_default(:block_collection) {[0,0]}
end   
> c = Container.new
=> #<Container:0x7f3610f717a8>
> c.naive_collection[0] = "foo"  
=> "foo"   
> Container.new.naive_collection
=> ["foo", 0]
# expected [0,0]

> c.block_collection[0] = "foo"
=> "foo"
> c.block_collection
=> [0, 0]
# expected ["foo", 0]

1 Ответ

1 голос
/ 25 февраля 2011

Я только что наткнулся на этот вопрос, столкнувшись с той же проблемой.

Для справки, документы указывают, что блочная форма динамически оценивается в области действия экземпляра.Чтобы продолжить пример, его полезность на самом деле весьма ограничена, но, по крайней мере, работает так, как вы могли ожидать:

class Container
  attr_accessor_with_default(:block_collection) { name.underscore }
end

> c = Container.new(:name => "TestName")
> c.block_collection  # => "test_name"
> c.block_collection = "something else"  # => "something else"
> c.name => "TestName"

Вот действительно странная часть, хотя ...

class Container
  attr_accessor_with_default :naive_collection, [0, 0]
end

# This works as expected
> c = Container.new
> c.naive_collection = ["foo", "bar"]  # => ["foo", "bar"]
> Container.new.naive_collection  # => [0, 0]
> c.naive_collection[0] = 0  # => [0, "bar"]
> Container.new.naive_collection  # => [0, 0]

# But this doesn't
> c2 = Container.new
> c2.naive_collection  # => [0, 0]
> c2.naive_collection[0] = "problem!"  # => ["problem!", 0]
> Container.new.naive_collection  # => ["problem!", 0]

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

Затем он выполняет это в пределах module_eval:

def #{sym}=(value)
  class << self;
    attr_reader :#{sym}
  end

  @#{sym} = value
end

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

Обновление:

Мне удалось выяснить, что происходитздесь неправильно.

def attr_accessor_with_default(sym, default = Proc.new)
  define_method(sym, block_given? ? default : Proc.new { default })
  module_eval(<<-EVAL, __FILE__, __LINE__ + 1)
    def #{sym}=(value)
      class << self; attr_accessor :#{sym} end
      @#{sym} = value
    end
  EVAL
end

Первоначально значение по умолчанию существует как proc.Как только вы вызываете метод установки, методы метода получения и установки перезаписываются методами attr_accessor, и переменная экземпляра инициализируется.Проблема в том, что процедура по умолчанию в методе получения возвращает значение по умолчанию на уровне класса.Поэтому, когда вы делаете что-то вроде:

> c2.naive_collection[0] = "problem!"  # => ["problem!", 0]

, вы фактически изменяете значение по умолчанию для класса.

Я думаю, что этот метод, вероятно, должен быть реализован как:

class Module
  def attr_accessor_with_default(sym, default = Proc.new)
    module_eval(<<-EVAL, __FILE__, __LINE__ + 1)
      def #{sym}
        class << self; attr_reader :#{sym} end
        @#{sym} = #{ default.dup }
      end

      def #{sym}=(value)
        class << self; attr_accessor :#{sym} end
        @#{sym} = value
      end
    EVAL
  end
end

Я куплю билет и предложу патч.

Обновите снова: https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/6496

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...