Reputation: 6213
I am trying to access the property of an object in an array, but I keep getting an error saying that the array value is nil:
@notifications.each do |note|
@users << User.find(note.notifier_id)
end
@unreads = []
for i in [email protected]
@unreads[i] = 0
@current_user.notifications.each do |n|
if n.notifier_id == @users[i].id && n.seen == false
@unreads[i] += 1
end
end
end
I get the error: NoMethodError (undefined method 'id' for nil:NilClass):
, coming from @users[i].id
If I don't run that for loop and just print out the value of @users
, I get the expected output, which includes valid id
values.
How do I properly access a User
object from an array?
Upvotes: 1
Views: 101
Reputation: 1367
There are two problems in your code.
The first issue will always be a problem. As @sawa answered, you have an off-by-one error in the range you're using in your for
loop. Use three dots:
for i in [email protected]
instead of two dots:
for i in [email protected]
The Ruby 2.3.0 documentation for the Range
class reads:
Ranges constructed using
..
run from the beginning to the end inclusively. Those created using...
exclude the end value.
nil
Result in QueryIt's possible that User.find(note.notifier_id)
will return nil
. Unlike problem #1, this may not always happen. I'm not assuming you're using Rails in my suggestions below, since you didn't mention using it.
One way to deal with this is to ensure that User.find
always returns an object that responds to #id
. This would be an instance of User
in most cases, and possibly a null object when the query can't find a User
record. You can see an example of the null object pattern here.
Another way to deal with this is to check to see if @users[i]
is present before sending it the #id
message.
@notifications.each do |note|
@users << User.find(note.notifier_id)
end
@unreads = []
for i in [email protected]
@unreads[i] = 0
@current_user.notifications.each do |n|
if @users[i] && n.notifier_id == @users[i].id && n.seen == false
@unreads[i] += 1
end
end
end
If @users[i]
is nil
, the code won't ever execute @users[i].id
. Adding the extra check to the conditional makes the code harder to read, however.
You could also discard nil
values from your @users
array with #compact
before counting the number of unread items. I'm using each_with_index
below, since it's a bit more idiomatic than for
.
@notifications.each do |note|
@users << User.find(note.notifier_id)
end
@unreads = []
@users.compact.each_with_index do |user, i|
@unreads[i] = 0
@current_user.notifications.each do |n|
if n.notifier_id == @users[i].id && n.seen == false
@unreads[i] += 1
end
end
end
Upvotes: 3
Reputation: 373
from [email protected]
, there are @user.count + 1
elements, but you can access by index from 0..(@users.count-1)
.
it is better for using each_with_index
to loop in @users
@users.each_with_index do |user, i|
@unreads[i] = @current_user.
notifications.
where(notifier_id: user.id, seen: false).
count
end
Shorter version
@users = User.where(id: @notifications.map(&:notifier_id))
@unreads = @users.map { |user| @current_user.notifications.where(notifier_id: user.id, seen: false).count }
Upvotes: 0
Reputation: 168081
Your way of accessing is correct, but you are trying to access @users
at an index that does not exist. Try:
for i in [email protected]
Upvotes: 2