Prev Next

Pattern Matching Maps in Elixir

Exercise: Rewriting

URLs are commonly rewritten to make them prettier or to improve a page’s SEO. Just for practice, add a function to the pipeline that rewrites requests for /bears?id=1 to /bears/1, /bears?id=2 to /bears/2, and so on. Here’s an example request:

request = """
GET /bears?id=1 HTTP/1.1
Host: example.com
User-Agent: ExampleBrowser/1.0
Accept: */*
 
"""

Solution:

def rewrite_path(%{path: "/bears?id=" <> id} = conv) do
  %{ conv | path: "/bears/#{id}" }
end

Exercise: Emojify Worthy Responses

Just for fun, suppose you want your web server to decorate all responses that have a 200 status with emojis before and after the actual content. Write an emojify function that emojifies those responses and plug that function into the pipeline.

Solution:

def emojify(%{status: 200} = conv) do
  emojies = String.duplicate("🎉", 5)
  body = emojies <> "\n" <> conv.resp_body <> "\n" <> emojies
 
  %{ conv | resp_body: body }
end
 
def emojify(conv), do: conv

You’ll also need to change how the “Content-Length” of the response is computed. It needs to be the number of bytes (not characters) in the response. Use byte_size/1 for this purpose:

"""
Content-Length: #{byte_size(conv.resp_body)}
"""

Exercise: Handle DELETE Differently

Change the DELETE route you created in a previous exercise to pattern match on the conv map, rather than taking three arguments.

Solution:

def route(%{method: "DELETE", path: "/bears/" <> _id} = conv) do
  %{ conv | status: 403, resp_body: "Bears must never be deleted!" }
end

Thought Experiment

Can you think of other useful functions to add to the pipeline, either to modify the request before it’s routed or modify the response before it’s sent back? You may need to consider additional fields in the conversation map that are affected by the functions.

Alternative Approach Using Regular Expressions

In the first exercise above, you rewrote requests for /bears?id=1. Now suppose you want to do the same for lions, tigers, and so on, but rather than writing separate rewrite_path function clauses for each wildthing, you want to handle them all in one generic function.

Solution using regular expressions:

def rewrite_path(%{path: path} = conv) do
  regex = ~r{\/(?<thing>\w+)\?id=(?<id>\d+)}
  captures = Regex.named_captures(regex, path)
  rewrite_path_captures(conv, captures)
end
 
def rewrite_path_captures(conv, %{"thing" => thing, "id" => id}) do
  %{ conv | path: "/#{thing}/#{id}" }
end
 
def rewrite_path_captures(conv, nil), do: conv

This function uses regular expressions to capture thing and id values and rewrite the path accordingly. If no captures are found, the path remains unchanged.

Open-Ended Exercise: Use the Logger

In the video, we tracked a 404 by printing a warning to the console. To get a tad fancier, you can use the built-in Logger module, which supports various levels of logging and a wide range of configuration options.

To use Logger in your code, first require it:

require Logger

Then, you can call logging functions, such as:

Logger.info "It's lunchtime somewhere."
Logger.warn "Do we have a problem, Houston?"
Logger.error "Danger Will Robinson!"

Experiment with different logging levels, but don’t forget to return to the main course!

Code So Far

The code for this section is in the rewriting directory found within the video-code directory of the code bundle

Prev Next