Organizing Code


Exercise: File Handler Module

If you'd like some practice creating files and modules, put all the handle_file functions into their own module named FileHandler, for example.

Show Answer

Hide Answer

defmodule Servy.FileHandler do
  def handle_file({:ok, content}, conv) do
    %{ conv | status: 200, resp_body: content }

  def handle_file({:error, :enoent}, conv) do
    %{ conv | status: 404, resp_body: "File not found!" }

  def handle_file({:error, reason}, conv) do
    %{ conv | status: 500, resp_body: "File error: #{reason}" }

You'll need to then import this new module in the existing Handler module.

Show Answer

Hide Answer

import Servy.FileHandler, only: [handle_file: 2]

Import Options

By default, when you use import it imports all the module's functions and macros into the current namespace. As we did in the video, you can use the only option to explicitly import specific functions:

import Servy.Plugins, only: [rewrite_path: 1, log: 1, track: 1]

import Servy.Parser, only: [parse: 1]

Using only is optional, but it's recommended so as to avoid importing all the functions into the current namespace and potentially ending up with name collisions. Conversely, there's a rarely used except option to import all the functions except those that are specified.

For completeness sake, you might bump into two other variations worth noting:

import SomeModule, only: :functions

import SomeModule, only: :macros

Using the :functions atom imports only functions whereas using the :macros atom only imports macros.

Alternate Way to Get Absolute Paths

In the previous video we expanded the path to the pages directory relative to the directory of the current file (__DIR__) like so:

@pages_path Path.expand("../../pages", __DIR__)

Now that we're running the application using iex -S mix, you can optionally expand the path using a slightly different approach, as follows:

@pages_path Path.expand("pages", File.cwd!)

We've removed the ../../ part of the first argument and called the File.cwd! function as the second argument. File.cwd! returns the current working directory.

But wait, you say, won't that vary depending on where we run the application? Yes, but mix always runs from the root project directory which is the top-level servy directory in our case. So calling File.cwd! always returns the top-level servy directory. And relative to that directory, the pages directory is just one level down.

Kind of a neat trick!

As an aside, this is the first time we've seen a function name ending with !. Generally speaking, this is a naming convention that conveys that the function will raise an exception if it fails. In particular, calling File.cwd! is the same as calling File.cwd but it raises an exception if for some reason there's a problem.

The Kernel Module

Functions and macros defined in Elixir's Kernel module are automatically imported into every module for convenience. It's worth scanning through these functions and macros.

Code So Far

The code for this video is in the organizing-code directory found within the video-code directory of the code bundle.