Как сделать attr_accessor_with_default в рубине?

Некоторый код, который у меня был с использованием attr_accessor_with_default в модели рельсов, теперь дает мне предупреждение об устаревании, в котором я говорю "Использовать Ruby вместо этого!"

Итак, думая, что, возможно, в ruby 1.9.2 появился новый бит, который сделал дескриптор дескриптора attr_accessor по умолчанию, я искал его, но я этого не вижу. Я видел несколько методов для переопределения attr_accessor для обработки значений по умолчанию.

Это то, что они имеют в виду, когда они говорят мне "Использовать Ruby"? Или я должен сейчас писать полные геттеры/сеттеры? Или есть какой-то новый способ, который я не могу найти?

Ответ 1

attr_accessor :pancakes

def after_initialize
     return unless new_record?
     self.pancakes = 11
end

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

Ответ 2

Эта страница apidock предлагает просто сделать это в методе initialize.

class Something
  attr_accessor :pancakes

  def initialize 
    @pancakes = true
    super
  end
end

Не забудьте вызвать super, особенно при использовании ActiveRecord или аналогичного.

Ответ 3

Поскольку вы, вероятно, хорошо знаете свои данные, вполне допустимо предположить, что nil не является допустимым значением.

Это означает, что вы можете покончить с after_initialize, поскольку это будет выполнено для каждого создаваемого вами объекта. Как отметили несколько человек, это (потенциально) катастрофическое для производительности. Кроме того, вставка метода, как в примере, в любом случае устарела в Rails 3.1.

Чтобы использовать Ruby вместо этого, я бы использовал такой подход:

attr_writer :pancakes

def pancakes
  return 12 if @pancakes.nil?
  @pancakes
end

Итак, немного опустите магию Ruby и напишите свой собственный геттер. В конце концов, это делает именно то, что вы пытаетесь выполнить, и это хорошо и достаточно просто, чтобы кто-нибудь мог обернуть свою голову.

Ответ 4

Нет ничего волшебного в 1.9.2 для инициализации переменных экземпляра, которые вы настроили с помощью attr_accessor. Но есть after_initialize обратный вызов:

Обратный вызов after_initialize будет вызываться всякий раз, когда создается объект Active Record, либо путем прямого использования нового, либо когда запись загружается из базы данных. Может быть полезно избежать необходимости прямого переопределения метода Active Record initialize.

Итак:

attr_accessor :pancakes
after_initialize :init

protected
def init
    @pancakes = 11
end

Это безопаснее, чем что-то вроде этого:

def pancakes
    @pancakes ||= 11
end

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

Ответ 5

Мне интересно, будет ли работать с Rails-реализацией:

http://apidock.com/rails/Module/attr_accessor_with_default

def attr_accessor_with_default(sym, default = nil, &block)
  raise 'Default value or block required' unless !default.nil? || block
  define_method(sym, block_given? ? block : Proc.new { default })
  module_eval(      def #{sym}=(value)                        # def age=(value)        class << self; attr_reader :#{sym} end  #   class << self; attr_reader :age end        @#{sym} = value                         #   @age = value      end                                       # end, __FILE__, __LINE__ + 1)
end

Ответ 6

Это ооооолдовый вопрос, но общая проблема все еще возникает - и я оказался здесь.

Другие ответы разнообразны и интересны, но я обнаружил проблемы со всеми из них при инициализации массивов (особенно, поскольку я хотел иметь возможность использовать их на уровне класса, прежде чем инициализация была вызвана в экземпляре). У меня был успех:

attr_writer :pancakes

def pancakes
  @pancakes ||= []
end

Если вы используете = вместо || =, вы обнаружите, что < оператор не может добавить первый элемент в массив. (Создается анонимный массив, ему присваивается значение, но он никогда не привязывался к @pancakes.)

Например:

obj.pancakes
#=> []
obj.pancakes << 'foo'
#=> ['foo']
obj.pancakes
#=> [] 
#???#!%$#@%FRAK!!!

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

Этот шаблон должен быть изменен для bool, например, если вы хотите по умолчанию использовать false:

attr_writer :pancakes

def pancakes
  @pancakes.nil? ? @pancakes = false : @pancakes
end

Хотя вы можете утверждать, что назначение не является строго необходимым при работе с bool.