3 ways to use Regexp capture groups with back-references in Ruby
By: Tech - Rubycademy
Tags: Programming, Ruby, Rails, Ruby on Rails, Regular Expressions

3 ways to use Regexp capture groups with back-references in Ruby

Before to start

Please allow me to introduce here the platform that helped me to learn a big part of my knowledge about Ruby. Indeed, Pluralsight is an amazing platform.

With 50+ courses that cover various topics on Ruby and Ruby on Rails, it’s the best way to take your knowledge to the next level!

Try it for free 👇😉

Thank you for your time!

Quick reminder of Regexp and capture groups

In Ruby, the regular expression logic is encapsulated in the Regexp class. There is 3 ways to instantiate a Regexp

So, the next question is: how to match this Regexp against a String ?

The MatchData class

The Regexp class provides a Regexp#match method that takes a String to test as parameter

The Regexp#match method returns an instance of MatchData . The MatchData class encapsulates all the results of a pattern match. I invite you to read the official documentation to learn what you can do with an instance of MatchData . For instance, you can use the MatchData#to_a to iterate through the results.

The Pattern-Matching Operator =~

In order to ease the use of regular expressions, Ruby provides the syntactic sugar =~ . This operator matches a Regexp against a String

Note that all the magic variables are set because the =~ operator instantiate a MatchData if the regexp matches against the String .

Capture Groups

An interesting way to use regular expression in Ruby is to take advantage of the capture groups. With capture groups, we can capture part of a match and reuse it later. To capture a match we enclose the part we want to capture in parenthesis

if /(hello) (world)/ =~ "hello world"

puts "#{$2} #{$1}" # => "world hello"

end

Here, we access the content of the 2 capture groups — pattern between parenthesis — using global variables $n where n is the index position of the capture group within the regexp.

The Named Captures

The named capture feature, when it’s used with the =~ operator, has a powerful mechanism that automatically assigns the result of a matching group into a local variable using the name assigned to the group into the regular expression

The named capture feature is also available with the Regexp#match method. The main difference is that there is no variable created. Indeed, the named captures are available in the returned MatchData instance by using the MatchData#[] method

Capture groups and back-references

Capture groups and back-references are some of the more fun features of regular expressions. You place a sub-expression in parentheses, you access the capture with \1

/(hello) \1/ =~ "hello hello" # => 0

Here, \1 refers to the first declared capture group (hello) . If you declare two capture groups then you can refer to each of these groups by using \1 and \2 .

Capture groups and back-references are easy and fun. But Ruby allows you to go even further. Indeed, Ruby allows you to access capture groups outside of the regular expression by using back-references. Let’s have a look to 3 examples to get more familiar with this concept.

Using back-references in Ruby

Here, you’ll see how Ruby takes advantage of the regular expression specification to elegantly extend the String class.

String#[] method

Here, we can see that to avoid to rewrite html in our regular expression we use the \1 back-reference to the first capture group (html) . This pattern helps us to avoid redundancy within our regexp.

⚠️ This technique is mainly used to avoid redundancy and enhance readability of your Regexp. But when you have to deal with several capture groups, using back-references can quickly make your regexp unreadable. That’s why, a good practice is to annotate the capture groups with their back-reference indexes in order to maintain the readability of your regular expression \1 : (html) , \2 : (head) , etc..

String#gsub method

Here, to_https method converts an http url to an https one. To do so, we use the gsub method with as arguments:

a regexp that represents the substring to replace —it includes http ( (http) )and excludes the following s ( (?!s) )

( )and excludes the following ( ) the replacing string with a back-reference to the first capture group (http)

This demonstrates the care that Ruby has for developers. Indeed, in String#gsub method, the replacing string can include back-references to the capture groups within the regular expression!

WE LOVE RUBY !

By the way, have a look to the official Ruby documentation if you’re not familiar with the concept of negative-lookaheads (?!)

Back-referencing named groups

Here we reproduce the same behavior as the previous example. The only change is that we use named groups instead of capture groups. So, in the replacing substring we use \k followed by the label of the named group between chevrons \k<scheme> to back-reference the named group in the pattern passed as first argument.

Conclusion

Mixing capture groups and back-references is a powerful tool that can enhance the readability of your code. However, a good practice is to annotate the capture groups with back-reference indexes in order to maintain the readability of your regular expression.

Voilà!