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)
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.