Snowcrash
Snowcrash

Reputation: 86097

Why do I get odd behaviour with second, third objects in an array?

Here's the code:

class Person
  attr_accessor :id, :name

  def initialize(init = {})
    init.each do |k, v|
      send("#{k}=", v)
    end
  end
end

people = [ 
  Person.new(:id => 1, :name => "Adam"), 
  Person.new(:id => 2), 
  nil,
]

people.map! do |person|
  person ||= Person.new(:id => 3, :name => "Some default")
  person.name ||= 'Eve'
  person
end

binding.pry

and here's what I get in pry:

[1] pry(main)> people
=> [#<Person:0x007fc2b0afba98 @id=1, @name="Adam">,
 #<Person:0x007fc2b0afb930 @id=2, @name="Eve">,
 #<Person:0x007fc2b0afb7f0 @id=3, @name="Some default">]
[2] pry(main)> people.first
=> #<Person:0x007fc2b0afba98 @id=1, @name="Adam">
[3] pry(main)> people.second
NoMethodError: undefined method `second' for #<Array:0x007fc2b0afb890>
from (pry):3:in `<main>'

I was expecting to be able to access people.second and people.second.id. What's up?

Upvotes: 0

Views: 1423

Answers (3)

Stuart Nelson
Stuart Nelson

Reputation: 4202

Just use people[1], which is the standard way to access elements in an array.

Requiring "active_support/core_ext" adds a lot of stuff you probably don't need for what amounts to a very small amount of syntactic sugar.

Upvotes: 1

Sergio Tulentsev
Sergio Tulentsev

Reputation: 230336

This method does not exist in plain ruby. It's from rails. To use it, you have to include Active Support.

require 'active_support/core_ext'

a = [4, 5, 10]

a.first # => 4
a.second # => 5
a.third # => 10

But you really shouldn't accustom yourself with accessing array elements in this way. first/last helpers exist for a reason: very often you need just the first or last element.

user = User.where(name: 'Sergio').first # user might not exist
last_transaction = user.transactions.last

If you plan on accessing second, third, fourth (and further) elements, there are better options. Iterating with .each, for example.

user.transactions.each do |tran|
  # ...
end

I personally prefer indexer over such helpers* at all times (even when they are available).

  1. Indexer form is shorter (users[1] vs. users.second)
  2. They are easier. Consider changing

    users[1] to users[2]
    

    vs.

    users.second to users.third
    

* I mean the helpers that Active Support brings in. I do prefer first over users[0].

Upvotes: 8

Alex D
Alex D

Reputation: 30445

As the other answer states, this method is from Active Support. But if you don't want to pull in Active Support just for a single method... well, this is Ruby, isn't it?

class Array
  def second
    self[1]
  end
end

Or, more generally:

module Enumerable
  def second
    first = true
    each { |x| first ? (first = false) : return x }
  end
end

You could actually include both of those definitions, and for Arrays, the more specific (and probably faster) method will be used. For other types of Enumerables, the more general method will be used.

Upvotes: 0

Related Questions