Rails3: клонирование уже проверенного объекта предотвращает аннулирование клона - это странно или нормально? - PullRequest
2 голосов
/ 27 сентября 2010

В тестовом коде Rails (3.0) я клонировал объект, так что я могу использовать его для проверки правильности без изменения оригинала. Если я вызвал assert (original.valid?) до клонирования, то клон проходит тест validates_presence_of даже после того, как я установил значение member_id равным nil.

Два теста ниже иллюстрируют это. В первом тесте клон создается за до проверки оригинала ("контакта"). Клон корректно не проходит проверку при отсутствии member_id. Утверждение С. успешно.

Во втором тесте клон создается после проверки оригинала. Даже если для clone.member_id установлено значение nil, проходит проверку. Другими словами, утверждение 2С не выполняется. Разница между тестами only - это порядок двух строк:

  cloned = contact.clone
  assert(contact.valid?,"A")

Что здесь происходит? Это нормальное поведение Ruby: клонирование, которое я просто не понимаю?

test "clone problem 1" do
  contact = Contact.new(:member_id => 1)
  cloned = contact.clone
  assert(contact.valid?,"A")
  cloned.member_id = nil
  assert(!cloned.valid?,"C")
end

test "clone problem 2" do
  contact = Contact.new(:member_id => 1)
  assert(contact.valid?,"2A")
  cloned = contact.clone
  cloned.member_id = nil
  assert(!cloned.valid?,"2C")
end

1 Ответ

3 голосов
/ 27 сентября 2010

Вы будете удивлены - это не может работать!

Хорошо, причину можно найти в коде Rails. Первая проверка запускает код:

# Validations module

# Returns the Errors object that holds all information about 
# attribute error messages.
def errors
  @errors ||= Errors.new(self)
end

Поскольку это первый запуск, он создаст новый экземпляр класса Errors. Просто, не правда ли? Но есть недочёт - параметр self. В вашем случае это «контактный» объект.

Позже, когда вы снова вызовете это для клонированного объекта, экземпляр @errors больше не будет создан - так как он не равен нулю. И вот оно! Вместо прохождения «клонированного» «я» используется старое «я».

Позже в проверочном коде класс Errors запускает код, считывающий значение из @base, которое является self из инициализации. Видишь? Значения для теста читаются из оригинальной модели, а не из клона! Таким образом, проверка «клонированного» объекта выполняется на значениях из оригинала.

Хорошо, пока что «почему нет», а теперь несколько слов о «как».

Решение простое - просто установите @errors равным nil после клонирования и перед проверкой. Поскольку это довольно личное, простое назначение не работает. Но это работает:

cloned.instance_eval do
  @errors = nil
end

И несколько советов для интересного чтения: http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/

Это довольно полное объяснение того, как работает валидация в Rails 3.

...