Ошибка неопределенного метода [] при попытке доступа к ключу

#ruby-on-rails

#ruby-on-rails

Вопрос:

У меня есть этот массив:

 [{:student=>{:id=>1, :firstName=>"firstName", :lastName=>"lastName",:age=>9}, :pretest=>nil,
:diagnostic=>nil, :result=>nil, :completedAt=>nil},  
{:student=>{:id=>2
    :firstName=>"John", :lastName=>"Doe",:age=>9}, :pretest=>{:id=>22, :score=>{:attempted=>15,
    :correct=>13, :total=>15}, :title=>"Test", :age=>6, :diagnostic=>{:id=>23, :score=>{:attempted=>40, :correct=>17,
    :total=>40}, result: "OK", :completedAt=>Thu, 01 Oct 2020 16:09:34 UTC  00:00}]
  

Я пытаюсь получить доступ к заголовку предварительного теста, когда он существует. Я делаю

 student_data = report[:entries]
student_data.each { |x| puts x[:pretest][:title]}
  

но я получаю неопределенный метод [] для ошибки nil: NilClass .
Когда я делаю

 student_data = report[:entries]
student_data.each { |x| puts x[:pretest]}
  

Я вижу {:id=>22, :score=>{:attempted=>15, :correct=>13, :total=>15}, :title=>"Test", :age=>6} .
Итак, почему student_data.each { |x| puts x[:pretest]} работает, но student_data.each { |x| puts x[:pretest][:title]} выдает ошибку?

Ответ №1:

Возникает ошибка, поскольку первый хэш в массиве имеет :pretest=>nil .

В Ruby 2.3 введен хэш #dig, который можно использовать для безопасной навигации по хэшам:

 student_data.each { |x| puts x.dig(:pretest, :title) }
  

В более старых версиях ruby существуют различные неуклюжие способы безопасной навигации по хэшу:

 hash = {}
# Using fetch with a default value
hash.fetch(:foo, {})[:bar]
# Tail condition
hash[:foo][:bar] if hash[:foo]
# Or with try? from ActiveSupport
hash[:pretest].try?(:[], :foo)
  

Ответ №2:

Глядя на ваш код, вы правы, предварительный тест существует для одного из учеников, но не для всех:

 [{:student=>{:id=>1, :firstName=>"firstName", :lastName=>"lastName",:age=>9}, :pretest=>nil,
:diagnostic=>nil, :result=>nil, :completedAt=>nil},  
{:student=>{:id=>2
    :firstName=>"John", :lastName=>"Doe",:age=>9}, :pretest=>{:id=>22, :score=>{:attempted=>15,
    :correct=>13, :total=>15}, :title=>"Test", :age=>6, :diagnostic=>{:id=>23, :score=>{:attempted=>40, :correct=>17,
    :total=>40}, result: "OK", :completedAt=>Thu, 01 Oct 2020 16:09:34 UTC  00:00}]
  

Очевидно, что для идентификатора студента = 1 предварительное тестирование равно нулю. Таким образом, попытка перебрать учеников завершится неудачей, поскольку она прервется при первом ученике. Вы можете избежать этого, сказав ruby «попробовать» этот хэш-ключ и вернуть значение по умолчанию, если nil . Вы можете использовать dig или попробовать:

 student_data.each { |x| puts x.dig(:pretest, :title) }
  

Или более уродливая версия (но облегчает понимание IMO)

 student_data.each { |x| puts x[:pretest].try(:[],:title) }
  

Комментарии:

1. Привет, нет, я не создал первого ученика по ошибке. Учащийся будет иметь нулевое значение, если он не завершит свой тест. В моем случае у меня есть два ученика, 1 и 2, и я хочу увидеть название тестов тех учеников, у которых есть завершенный тест.

2. Я обновил ответ на то, что может вам помочь

Ответ №3:

На самом деле я попробовал что-то другое, что также работало, в основном переходя к следующей записи, если предыдущая была нулевой:

 def print_existing_titles(student_data)
 student_data.any? do |data|
 next if data[:pretest].nil?

 puts data[:pretest][:title]
end