简体   繁体   中英

Undefined method[] error when trying to access a key

I have this array:

[{: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}]

I am trying to access the title of the pretest when it exists. I am doing

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

but I get undefined method [] for nil:NilClass error. When I do

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

I can see {:id=>22, :score=>{:attempted=>15, :correct=>13, :total=>15}, :title=>"Test", :age=>6} . So why does student_data.each { |x| puts x[:pretest]} student_data.each { |x| puts x[:pretest]} work but student_data.each { |x| puts x[:pretest][:title]} student_data.each { |x| puts x[:pretest][:title]} throw an error?

It raises an error since the first hash in the array has :pretest=>nil .

Ruby 2.3 introduced Hash#dig which can be used to safely navigate hashes:

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

In older versions of ruby there are various clunky ways of safely navigating a hash:

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)

Looking at your code, you're right pretest exists for one of the students, but not for all of them:

[{: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}]

Clearly, for student id=1, pretest is nil. So trying to loop through the students will fail since it will break at the first student. You can avoid that by telling ruby to 'try' that hash key and return a default value if nil. You can use dig, or try:

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

Or the uglier version (but makes it easier to understand IMO)

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

I actually tried something different which also worked by basically going to the next entry if the previous one was nil:

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

 puts data[:pretest][:title]
end 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM