class MatchData

Practical examples and pitfalls for MatchData

MatchData examples: keep captures attached to their match

Practical notes by Ruby-Doc.org

Name the fields a reader needs to understand

A log entry contains a depot and a parcel count. Named captures let the extraction code say which field it is reading instead of relying on a remembered position. The full match and an individual capture are different pieces of data; the example prints both.

Example 1
match = /\A(?<depot>[A-Z]+):(?<count>[0-9]+)\z/.match("NORTH:12")
if match
  p match[0]
  p match[:depot]
  p Integer(match[:count], 10)
end
Expected output
"NORTH:12"
"NORTH"
12

A successful pattern match does not convert the count into a number. Convert and validate it separately before using it in arithmetic. Keeping those steps apart also gives an import screen somewhere to attach a useful error message: did the line fail its format, or did a captured value violate a later rule?

Distinguish an absent optional field from a failed match

A record can match even when an optional capture is absent. That capture then returns nil. This is different from the entire call to match returning nil because the record did not match at all.

Example 2
pattern = /\A(?<name>[a-z]+)(?:-r(?<revision>[0-9]+))?\z/
match = pattern.match("notes")
p match.nil?
p match[:revision]
p pattern.match("notes-?")
Expected output
false
nil
nil

The distinction is useful for a note that may or may not include a revision suffix. An absent suffix could mean use the initial revision. An invalid record should not silently receive that same default, because doing so would conceal the failed parse.

Hold on to the result you intend to use

Store the MatchData object in a local variable when later operations may perform other matches. That keeps the source of a capture visible and avoids making the next line depend on whichever match most recently updated implicit match state.

Position methods and the original string can help explain a rejected field in an editor, but keep their units straight. Character positions in text are not automatically byte offsets suitable for a binary protocol. Test non-ASCII input if offsets leave the parsing helper and are consumed elsewhere. A match object is useful evidence about one match, not a substitute for a complete record-validation policy.

API reference: MatchData API reference

Related: Regexp ยท String

MatchData encapsulates the result of matching a Regexp against string. It is returned by Regexp#match and String#match, and also stored in a global variable returned by Regexp.last_match.

Usage:

url = 'https://docs.ruby-lang.org/en/2.5.0/MatchData.html'
m = url.match(/(\d\.?)+/)   # => #<MatchData "2.5.0" 1:"0">
m.string                    # => "https://docs.ruby-lang.org/en/2.5.0/MatchData.html"
m.regexp                    # => /(\d\.?)+/
# entire matched substring:
m[0]                        # => "2.5.0"

# Working with unnamed captures
m = url.match(%r{([^/]+)/([^/]+)\.html$})
m.captures                  # => ["2.5.0", "MatchData"]
m[1]                        # => "2.5.0"
m.values_at(1, 2)           # => ["2.5.0", "MatchData"]

# Working with named captures
m = url.match(%r{(?<version>[^/]+)/(?<module>[^/]+)\.html$})
m.captures                  # => ["2.5.0", "MatchData"]
m.named_captures            # => {"version"=>"2.5.0", "module"=>"MatchData"}
m[:version]                 # => "2.5.0"
m.values_at(:version, :module)
                            # => ["2.5.0", "MatchData"]
# Numerical indexes are working, too
m[1]                        # => "2.5.0"
m.values_at(1, 2)           # => ["2.5.0", "MatchData"]

Global variables equivalence

Parts of last MatchData (returned by Regexp.last_match) are also aliased as global variables:

See also Global Variables at Regexp.