All about coding

Ruby and Rails technical content written by Lucian Ghinda

How to Use Pattern Matching to Locate Elements in a Hash Array

Markdown version

Having the following structure of a Hash that includes an Array of Hashes, for example, and you want the email of a moderator:

system = {
  users: [
    { username: 'alice', role: 'admin', email: 'alice@example.com' },
    { username: 'bob', role: 'user', email: 'bob@example.com' },
    { username: 'charlie', role: 'moderator', email: 'charlie@example.com' }
  ]
}

moderator = system[:users].find { |u| u[:role] == 'moderator' }
email = moderator[:email] 

puts email # charlie@example.com

Now here is doing the same thing in Ruby using pattern matching:

system = {
  users: [
    { username: 'alice', role: 'admin', email: 'alice@example.com' },
    { username: 'bob', role: 'user', email: 'bob@example.com' },
    { username: 'charlie', role: 'moderator', email: 'charlie@example.com' }
  ]
}

system => {users: [*, { role: 'moderator', email: }, *]}
puts email # charlie@example.com

Let’s put the two lines one above the other:

# Using Enumerator#find
moderator = system[:users].find { |u| u[:role] == 'moderator' }
email = moderator[:email] 

# Using Pattern Matching
system => {users: [*, { role: 'moderator', email: }, *]}

There is another difference between them: in case of Pattern Matching it will throw a NoMatchingPatternError when not found, while the two lines that use Enumerator#find will throw a NoMethodError:

moderator = system[:users].find { |u| u[:role] == 'THIS_ROLE_DOES_NOT_EXISTS' }
email = moderator[:email] 
# undefined method '[]' for nil (NoMethodError)

system => {users: [*, { role: 'THIS_ROLE_DOES_NOT_EXISTS', email: }, *]}
# {users: [{username: "alice", role: "admin", email: "alice@example.com"}, 
# {username: "bob", role: "user", email: "bob@example.com"}, 
# {username: "charlie", role: "moderator", email: "charlie@example.com"}]}: 
# [{username: "alice", role: "admin", email: "alice@example.com"}, 
# {username: "bob", role: "user", email: "bob@example.com"}, 
# {username: "charlie", role: "moderator", email: "charlie@example.com"}] 
# does not match to find pattern (NoMatchingPatternError)does not match 
# to find pattern (NoMatchingPatternError)

How does pattern matching work in this example

If email does not exist either as a variable or method, matching it will create a local variable with the value from the matched row’s key.

system = {
  users: [
    { username: 'charlie', role: 'moderator', email: 'charlie@example.com' }
  ]
}
system => {users: [*, { role: 'moderator', email: }, *]}
puts binding.local_variables
# => [:system, :email]

If a variable of method labeled email does exists, it will just try to match it against its value:

system = {
  users: [
    { username: 'charlie', role: 'moderator', email: 'charlie@example.com' }
  ]
}

email = 'charlie@example.com'
system => {users: [*, { role: 'moderator', email: }, *]}
puts email # charlie@example.com

If the variable already has a value, it will be overriden if the matching is succesful:

system = {
  users: [
    { username: 'charlie', role: 'moderator', email: 'charlie@example.com' }
  ]
}
email = 'bob@example.com'
system => {users: [*, { role: 'moderator', email: }, *]}
puts email # charlie@example.com

You can force if you want to search for the content of that variable with the ^ operator:

system = {
  users: [
    { username: 'charlie', role: 'moderator', email: 'charlie@example.com' }
  ]
}
email = 'bob@example.com'
system => {users: [*, { role: 'moderator', email: ^email}, *]}
# {users: [{username: "charlie", role: "moderator", email: "charlie@example.com"}]}: [{username: "charlie", role: "moderator", email: "charlie@example.com"}] does not match to find pattern (NoMatchingPatternError)

👐 Want to improve your developer testing skills? Visit goodenoughtesting.com/articles to discover resources on testing for developers.

👉 Join my Short Ruby Newsletter for weekly Ruby updates and visit rubyandrails.info, a directory of Ruby learning content.

🤝 Connect with me on Linkedin, Bluesky, Ruby.social, , and Twitter, where I mostly post about Ruby and Ruby on Rails.

🎥 Follow my YouTube channel for short videos about Ruby and Rails.