Cbas
Cbas

Reputation: 6213

get object from array

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

Answers (3)

Michael Stalker
Michael Stalker

Reputation: 1367

There are two problems in your code.

1. Off-by-one Error

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.

2. Potential nil Result in Query

It'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

Long Nguyen
Long Nguyen

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

sawa
sawa

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

Related Questions