class CSV::Row - RDoc Documentation

# class CSV::Row

# CSV::Row¶ ↑

A CSV::Row instance represents a CSV table row. (see class CSV).

The instance may have:

- Fields: each is an object, not necessarily a String.

- Headers: each serves a key, and also need not be a String.

### Instance Methods¶ ↑

CSV::Row has three groups of instance methods:

- Its own internally defined instance methods.

- Methods included by module `Enumerable`.

- Methods delegated to class `Array`.:

  - `Array#empty?`

  - `Array#length`

  - `Array#size`

## Creating a CSV::Row Instance¶ ↑

Commonly, a new CSV::Row instance is created by parsing CSV source that has headers:

    source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
    table = CSV.parse(source, headers: true)
    table.each {|row| p row }

Output:

    #<CSV::Row "Name":"foo" "Value":"0">
    #<CSV::Row "Name":"bar" "Value":"1">
    #<CSV::Row "Name":"baz" "Value":"2">

You can also create a row directly. See `::new`.

## Headers¶ ↑

Like a CSV::Table, a CSV::Row has headers.

A CSV::Row that was created by parsing CSV source inherits its headers from the table:

    source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
    table = CSV.parse(source, headers: true)
    row = table.first
    row.headers # => ["Name", "Value"]

You can also create a new row with headers; like the keys in a Hash, the headers need not be Strings:

    row = CSV::Row.new([:name, :value], ['foo', 0])
    row.headers # => [:name, :value]

The new row retains its headers even if added to a table that has headers:

    table << row # => #<CSV::Table mode:col_or_row row_count:5>
    row.headers # => [:name, :value]
    row[:name] # => "foo"
    row['Name'] # => nil

## Accessing Fields¶ ↑

You may access a field in a CSV::Row with either its Integer index (Array-style) or its header (Hash-style).

Fetch a field using method `[]`:

    row = CSV::Row.new(['Name', 'Value'], ['foo', 0])
    row[1] # => 0
    row['Value'] # => 0

`Set` a field using method `[]=`:

    row = CSV::Row.new(['Name', 'Value'], ['foo', 0])
    row # => #<CSV::Row "Name":"foo" "Value":0>
    row[0] = 'bar'
    row['Value'] = 1
    row # => #<CSV::Row "Name":"bar" "Value":1>

### Attributes

row[R]

Internal data format used to compare equality.

### Public Class Methods

CSV::Row.new(headers, fields, header_row = false) → csv_row click to toggle source

Returns the new CSV::Row instance constructed from arguments `headers` and `fields`; both should be Arrays; note that the fields need not be Strings:

    row = CSV::Row.new(['Name', 'Value'], ['foo', 0])
    row # => #<CSV::Row "Name":"foo" "Value":0>

If the Array lengths are different, the shorter is `nil`-filled:

    row = CSV::Row.new(['Name', 'Value', 'Date', 'Size'], ['foo', 0])
    row # => #<CSV::Row "Name":"foo" "Value":0 "Date":nil "Size":nil>

Each CSV::Row object is either a field row or a header row; by default, a new row is a field row; for the row created above:

    row.field_row? # => true
    row.header_row? # => false

If the optional argument `header_row` is given as `true`, the created row is a header row:

    row = CSV::Row.new(['Name', 'Value'], ['foo', 0], header_row = true)
    row # => #<CSV::Row "Name":"foo" "Value":0>
    row.field_row? # => false
    row.header_row? # => true

    # File lib/csv/row.rb, line 105
    def initialize(headers, fields, header_row = false)
      @header_row = header_row
      headers.each { |h| h.freeze if h.is_a? String }

      # handle extra headers or fields
      @row = if headers.size >= fields.size
        headers.zip(fields)
      else
        fields.zip(headers).each(&:reverse!)
      end
    end

### Public Instance Methods

row << [header, value] → self click to toggle source
row << hash → self
row << value → self

Adds a field to `self`; returns `self`:

If the argument is a 2-element Array `[header, value]`, a field is added with the given `header` and `value`:

    source = "Name,Name,Name\nFoo,Bar,Baz\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row << ['NAME', 'Bat']
    row # => #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":"Baz" "NAME":"Bat">

If the argument is a Hash, each `key-value` pair is added as a field with header `key` and value `value`.

    source = "Name,Name,Name\nFoo,Bar,Baz\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row << {NAME: 'Bat', name: 'Bam'}
    row # => #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":"Baz" NAME:"Bat" name:"Bam">

Otherwise, the given `value` is added as a field with no header.

    source = "Name,Name,Name\nFoo,Bar,Baz\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row << 'Bag'
    row # => #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":"Baz" nil:"Bag">

    # File lib/csv/row.rb, line 389
    def <<(arg)
      if arg.is_a?(Array) and arg.size == 2  # appending a header and name
        @row << arg
      elsif arg.is_a?(Hash)                  # append header and name pairs
        arg.each { |pair| @row << pair }
      else                                   # append field value
        @row << [nil, arg]
      end

      self  # for chaining
    end

row == other → true or false click to toggle source

Returns `true` if `other` is a /CSV::Row that has the same fields (headers and values) in the same order as `self`; otherwise returns `false`:

    source = "Name,Name,Name\nFoo,Bar,Baz\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    other_row = table[0]
    row == other_row # => true
    other_row = table[1]
    row == other_row # => false

    # File lib/csv/row.rb, line 633
    def ==(other)
      return @row == other.row if other.is_a? CSV::Row
      @row == other
    end

[](header_or_index, minimum_index = 0)
Alias for: field
row[index] = value → value click to toggle source
row[header, offset] = value → value
row[header] = value → value

Assigns the field value for the given `index` or `header`; returns `value`.

Assign field value by Integer index:

    source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row[0] = 'Bat'
    row[1] = 3
    row # => #<CSV::Row "Name":"Bat" "Value":3>

Counts backward from the last column if `index` is negative:

    row[-1] = 4
    row[-2] = 'Bam'
    row # => #<CSV::Row "Name":"Bam" "Value":4>

Extends the row with `nil:nil` if positive `index` is not in the row:

    row[4] = 5
    row # => #<CSV::Row "Name":"bad" "Value":4 nil:nil nil:nil nil:5>

Raises `IndexError` if negative `index` is too small (too far from zero).

Assign field value by header (first found):

    source = "Name,Name,Name\nFoo,Bar,Baz\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row['Name'] = 'Bat'
    row # => #<CSV::Row "Name":"Bat" "Name":"Bar" "Name":"Baz">

Assign field value by header, ignoring `offset` leading fields:

    source = "Name,Name,Name\nFoo,Bar,Baz\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row['Name', 2] = 4
    row # => #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":4>

Append new field by (new) header:

    source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row['New'] = 6
    row# => #<CSV::Row "Name":"foo" "Value":"0" "New":6>

    # File lib/csv/row.rb, line 339
    def []=(*args)
      value = args.pop

      if args.first.is_a? Integer
        if @row[args.first].nil?  # extending past the end with index
          @row[args.first] = [nil, value]
          @row.map! { |pair| pair.nil? ? [nil, nil] : pair }
        else                      # normal index assignment
          @row[args.first][1] = value
        end
      else
        index = index(*args)
        if index.nil?             # appending a field
          self << [args.first, value]
        else                      # normal header assignment
          @row[index][1] = value
        end
      end
    end

deconstruct → array click to toggle source

Returns the new Array suitable for pattern matching containing the values of the row.

    # File lib/csv/row.rb, line 682
    def deconstruct
      fields
    end

deconstruct_keys(keys) → hash click to toggle source

Returns the new Hash suitable for pattern matching containing only the keys specified as an argument.

    # File lib/csv/row.rb, line 667
    def deconstruct_keys(keys)
      if keys.nil?
        to_h
      else
        keys.to_h { |key| [key, self[key]] }
      end
    end

delete(index) → [header, value] or nil click to toggle source
delete(header) → [header, value] or empty_array
delete(header, offset) → [header, value] or empty_array

Removes a specified field from `self`; returns the 2-element Array `[header, value]` if the field exists.

If an Integer argument `index` is given, removes and returns the field at offset `index`, or returns `nil` if the field does not exist:

    source = "Name,Name,Name\nFoo,Bar,Baz\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row.delete(1) # => ["Name", "Bar"]
    row.delete(50) # => nil

Otherwise, if the single argument `header` is given, removes and returns the first-found field with the given header, of returns a new empty Array if the field does not exist:

    source = "Name,Name,Name\nFoo,Bar,Baz\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row.delete('Name') # => ["Name", "Foo"]
    row.delete('NAME') # => []

If argument `header` and Integer argument `offset` are given, removes and returns the first-found field with the given header whose `index` is at least as large as `offset`:

    source = "Name,Name,Name\nFoo,Bar,Baz\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row.delete('Name', 1) # => ["Name", "Bar"]
    row.delete('NAME', 1) # => []

    # File lib/csv/row.rb, line 451
    def delete(header_or_index, minimum_index = 0)
      if header_or_index.is_a? Integer                 # by index
        @row.delete_at(header_or_index)
      elsif i = index(header_or_index, minimum_index)  # by header
        @row.delete_at(i)
      else
        [ ]
      end
    end

delete_if {|header, value| ... } → self click to toggle source

Removes fields from `self` as selected by the block; returns `self`.

Removes each field for which the block returns a truthy value:

    source = "Name,Name,Name\nFoo,Bar,Baz\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row.delete_if {|header, value| value.start_with?('B') } # => true
    row # => #<CSV::Row "Name":"Foo">
    row.delete_if {|header, value| header.start_with?('B') } # => false

If no block is given, returns a new Enumerator:

    row.delete_if # => #<Enumerator: #<CSV::Row "Name":"Foo">:delete_if>

    # File lib/csv/row.rb, line 476
    def delete_if(&block)
      return enum_for(__method__) { size } unless block_given?

      @row.delete_if(&block)

      self  # for chaining
    end

dig(index_or_header, *identifiers) → object click to toggle source

Finds and returns the object in nested object that is specified by `index_or_header` and `specifiers`.

The nested objects may be instances of various classes. See Dig Methods.

Examples:

    source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row.dig(1) # => "0"
    row.dig('Value') # => "0"
    row.dig(5) # => nil

    # File lib/csv/row.rb, line 715
    def dig(index_or_header, *indexes)
      value = field(index_or_header)
      if value.nil?
        nil
      elsif indexes.empty?
        value
      else
        unless value.respond_to?(:dig)
          raise TypeError, "#{value.class} does not have \#dig method"
        end
        value.dig(*indexes)
      end
    end

each {|header, value| ... } → self click to toggle source

Calls the block with each header-value pair; returns `self`:

    source = "Name,Name,Name\nFoo,Bar,Baz\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row.each {|header, value| p [header, value] }

Output:

    ["Name", "Foo"]
    ["Name", "Bar"]
    ["Name", "Baz"]

If no block is given, returns a new Enumerator:

    row.each # => #<Enumerator: #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":"Baz">:each>

    # File lib/csv/row.rb, line 610
    def each(&block)
      return enum_for(__method__) { size } unless block_given?

      @row.each(&block)

      self  # for chaining
    end

Also aliased as: each_pair
each_pair(&block)
Alias for: each
fetch(header) → value click to toggle source
fetch(header, default) → value
fetch(header) {|row| ... } → value

Returns the field value as specified by `header`.

With the single argument `header`, returns the field value for that header (first found):

    source = "Name,Name,Name\nFoo,Bar,Baz\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row.fetch('Name') # => "Foo"

Raises exception `KeyError` if the header does not exist.

With arguments `header` and `default` given, returns the field value for the header (first found) if the header exists, otherwise returns `default`:

    source = "Name,Name,Name\nFoo,Bar,Baz\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row.fetch('Name', '') # => "Foo"
    row.fetch(:nosuch, '') # => ""

With argument `header` and a block given, returns the field value for the header (first found) if the header exists; otherwise calls the block and returns its return value:

    source = "Name,Name,Name\nFoo,Bar,Baz\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row.fetch('Name') {|header| fail 'Cannot happen' } # => "Foo"
    row.fetch(:nosuch) {|header| "Header '#{header} not found'" } # => "Header 'nosuch not found'"

    # File lib/csv/row.rb, line 258
    def fetch(header, *varargs)
      raise ArgumentError, "Too many arguments" if varargs.length > 1
      pair = @row.assoc(header)
      if pair
        pair.last
      else
        if block_given?
          yield header
        elsif varargs.empty?
          raise KeyError, "key not found: #{header}"
        else
          varargs.first
        end
      end
    end

field(index) → value click to toggle source
field(header) → value
field(header, offset) → value

Returns the field value for the given `index` or `header`.

Fetch field value by Integer index:

    source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row.field(0) # => "foo"
    row.field(1) # => "bar"

Counts backward from the last column if `index` is negative:

    row.field(-1) # => "0"
    row.field(-2) # => "foo"

Returns `nil` if `index` is out of range:

    row.field(2) # => nil
    row.field(-3) # => nil

Fetch field value by header (first found):

    source = "Name,Name,Name\nFoo,Bar,Baz\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row.field('Name') # => "Foo"

Fetch field value by header, ignoring `offset` leading fields:

    source = "Name,Name,Name\nFoo,Bar,Baz\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row.field('Name', 2) # => "Baz"

Returns `nil` if the header does not exist.

    # File lib/csv/row.rb, line 203
    def field(header_or_index, minimum_index = 0)
      # locate the pair
      finder = (header_or_index.is_a?(Integer) || header_or_index.is_a?(Range)) ? :[] : :assoc
      pair   = @row[minimum_index..-1].public_send(finder, header_or_index)

      # return the field if we have a pair
      if pair.nil?
        nil
      else
        header_or_index.is_a?(Range) ? pair.map(&:last) : pair.last
      end
    end

Also aliased as: []
field?(value) → true or false click to toggle source

Returns `true` if `value` is a field in this row, `false` otherwise:

    source = "Name,Name,Name\nFoo,Bar,Baz\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row.field?('Bar') # => true
    row.field?('BAR') # => false

    # File lib/csv/row.rb, line 589
    def field?(data)
      fields.include? data
    end

field_row? → true or false click to toggle source

Returns `true` if this is a field row, `false` otherwise.

    # File lib/csv/row.rb, line 148
    def field_row?
      not header_row?
    end

fields(*specifiers) → array_of_fields click to toggle source

Returns field values per the given `specifiers`, which may be any mixture of:

- Integer index.

- Range of Integer indexes.

- 2-element Array containing a header and offset.

- Header.

- Range of headers.

For `specifier` in one of the first four cases above, returns the result of `self.field(specifier)`; see `field`.

Although there may be any number of `specifiers`, the examples here will illustrate one at a time.

When the specifier is an Integer `index`, returns `self.field(index)`L

    source = "Name,Name,Name\nFoo,Bar,Baz\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row.fields(1) # => ["Bar"]

When the specifier is a Range of Integers `range`, returns `self.field(range)`:

    row.fields(1..2) # => ["Bar", "Baz"]

When the specifier is a 2-element Array `array`, returns `self.field(array)`L

    row.fields('Name', 1) # => ["Foo", "Bar"]

When the specifier is a header `header`, returns `self.field(header)`L

    row.fields('Name') # => ["Foo"]

When the specifier is a Range of headers `range`, forms a new Range `new_range` from the indexes of `range.start` and `range.end`, and returns `self.field(new_range)`:

    source = "Name,NAME,name\nFoo,Bar,Baz\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row.fields('Name'..'NAME') # => ["Foo", "Bar"]

Returns all fields if no argument given:

    row.fields # => ["Foo", "Bar", "Baz"]

    # File lib/csv/row.rb, line 530
    def fields(*headers_and_or_indices)
      if headers_and_or_indices.empty?  # return all fields--no arguments
        @row.map(&:last)
      else                              # or work like values_at()
        all = []
        headers_and_or_indices.each do |h_or_i|
          if h_or_i.is_a? Range
            index_begin = h_or_i.begin.is_a?(Integer) ? h_or_i.begin :
                                                        index(h_or_i.begin)
            index_end   = h_or_i.end.is_a?(Integer)   ? h_or_i.end :
                                                        index(h_or_i.end)
            new_range   = h_or_i.exclude_end? ? (index_begin...index_end) :
                                                (index_begin..index_end)
            all.concat(fields.values_at(new_range))
          else
            all << field(*Array(h_or_i))
          end
        end
        return all
      end
    end

Also aliased as: values_at
has_key?(header) → true or false click to toggle source

Returns `true` if there is a field with the given `header`, `false` otherwise.

    # File lib/csv/row.rb, line 279
    def has_key?(header)
      !!@row.assoc(header)
    end

Also aliased as: include?, key?, member?, header?
header?(header)
Alias for: has_key?
header_row? → true or false click to toggle source

Returns `true` if this is a header row, `false` otherwise.

    # File lib/csv/row.rb, line 140
    def header_row?
      @header_row
    end

headers → array_of_headers click to toggle source

Returns the headers for this row:

    source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
    table = CSV.parse(source, headers: true)
    row = table.first
    row.headers # => ["Name", "Value"]

    # File lib/csv/row.rb, line 160
    def headers
      @row.map(&:first)
    end

include?(header)
Alias for: has_key?
index(header) → index click to toggle source
index(header, offset) → index

Returns the index for the given header, if it exists; otherwise returns `nil`.

With the single argument `header`, returns the index of the first-found field with the given `header`:

    source = "Name,Name,Name\nFoo,Bar,Baz\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row.index('Name') # => 0
    row.index('NAME') # => nil

With arguments `header` and `offset`, returns the index of the first-found field with given `header`, but ignoring the first `offset` fields:

    row.index('Name', 1) # => 1
    row.index('Name', 3) # => nil

    # File lib/csv/row.rb, line 573
    def index(header, minimum_index = 0)
      # find the pair
      index = headers[minimum_index..-1].index(header)
      # return the index at the right offset, if we found one
      index.nil? ? nil : index + minimum_index
    end

initialize_copy(other_row) → self click to toggle source

Calls superclass method.

Calls superclass method

    # File lib/csv/row.rb, line 130
    def initialize_copy(other)
      super_return_value = super
      @row = @row.collect(&:dup)
      super_return_value
    end

inspect → string click to toggle source

Returns an ASCII-compatible String showing:

- `Class` CSV::Row.

- Header-value pairs.

Example:

    source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row.inspect # => "#<CSV::Row \"Name\":\"foo\" \"Value\":\"0\">"

    # File lib/csv/row.rb, line 740
    def inspect
      str = ["#<", self.class.to_s]
      each do |header, field|
        str << " " << (header.is_a?(Symbol) ? header.to_s : header.inspect) <<
               ":" << field.inspect
      end
      str << ">"
      begin
        str.join('')
      rescue  # any encoding error
        str.map do |s|
          e = Encoding::Converter.asciicompat_encoding(s.encoding)
          e ? s.encode(e) : s.force_encoding("ASCII-8BIT")
        end.join('')
      end
    end

key?(header)
Alias for: has_key?
member?(header)
Alias for: has_key?
push(*values) → self click to toggle source

Appends each of the given `values` to `self` as a field; returns `self`:

    source = "Name,Name,Name\nFoo,Bar,Baz\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row.push('Bat', 'Bam')
    row # => #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":"Baz" nil:"Bat" nil:"Bam">

    # File lib/csv/row.rb, line 410
    def push(*args)
      args.each { |arg| self << arg }

      self  # for chaining
    end

to_csv → csv_string click to toggle source

Returns the row as a CSV `String`. Headers are not included:

    source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row.to_csv # => "foo,0\n"

    # File lib/csv/row.rb, line 694
    def to_csv(**options)
      fields.to_csv(**options)
    end

Also aliased as: to_s
to_h → hash click to toggle source

Returns the new Hash formed by adding each header-value pair in `self` as a key-value pair in the Hash.

    source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row.to_h # => {"Name"=>"foo", "Value"=>"0"}

Header order is preserved, but repeated headers are ignored:

    source = "Name,Name,Name\nFoo,Bar,Baz\n"
    table = CSV.parse(source, headers: true)
    row = table[0]
    row.to_h # => {"Name"=>"Foo"}

    # File lib/csv/row.rb, line 653
    def to_h
      hash = {}
      each do |key, _value|
        hash[key] = self[key] unless hash.key?(key)
      end
      hash
    end

Also aliased as: to_hash
to_hash()
Alias for: to_h
to_s(**options)
Alias for: to_csv
values_at(*headers_and_or_indices)
Alias for: fields