Chapter 5. Elixir application structure

This chapter covers

  • Organizing Elixir code in a project
  • Using the Mix utility to manage your Elixir projects
  • Using hex.pm to make use of third-party tools and libraries

So far, you’ve been writing and using Elixir code in an IEx session or, in chapter 4, in a single file. The majority of your Elixir and Phoenix projects won’t be organized like this, however. As your application grows in complexity, it’s important to impose some sort of organizational structure to keep things manageable.

In this chapter, we’ll cover the structure of a typical Elixir application. Along the way, you’ll use the Mix utility to automate a lot of tasks that would take some effort if done manually. Finally, you’ll use the Hex package manager to bring third-party tools and libraries into your application. All of this will help you get your application set up properly before you tackle using a real database in chapter 6.

5.1. Moving from a single file to an application

Your auction application in chapter 4 consisted of a fake repo, a public interface layer to access the data in the repo, and an Item struct that defined the data structure of your auction items. All that code—three modules—existed in a single file. Once you start adding more functionality, that kind of project structure will become unmaintainable.

You could break up the modules into separate files. This is a great idea on the surface, but as you increasingly need to use code from one file in a different file, you’ll end up with a web of interconnected files that all depend on one another.

Fortunately, there’s a standard directory structure for Elixir projects. As you can see in figure 5.1, there are usually three top-level directories for every Elixir application:

  • Configuration code goes in the config subdirectory.
  • The bulk of the modules and business logic go into the lib subdirectory.
  • Tests go into the test subdirectory.
Figure 5.1. The standard directory and file structure of an Elixir project

Along with the standard directories, you’ll typically find a file named mix.exs. This file can be considered the brain or mother ship of your application. You’ll look at this file in the next section.

Although simple applications are typically structured in this way, you’ll be creating multiple applications—one for the business logic you’ve been working on and one for your Phoenix application. But you’ll also create another skeleton application to tie them together. This kind of pattern is called an umbrella application. The top-level umbrella application will contain the subapplications that contain the logic.

5.1.1. Using Mix to create a new application

You may be thinking, “Oh, great. Every time I want to start a new Elixir application, I have to remember this standard structure.” If you are indeed thinking that, I’ve got some great news for you! You don’t have to remember it at all! All you have to remember is mix. Like IEx, the Mix utility is installed when you install Elixir. And also like IEx, it has an excellent help system. If all you remember is the command mix, you can figure out how to get the rest.

The following listing jumps directly into using the Mix tool (in the terminal). It will tell you all the different things it can do.

Listing 5.1. Letting mix tell you what it can do
> mix
** (Mix) "mix" with no arguments must be executed in a directory with a
     mix.exs file

Usage: mix [task]

Examples:                 1

    mix             - Invokes the default task (current: "mix run")
    mix new PATH    - Creates a new Elixir project at the given path
    mix help        - Lists all available tasks
    mix help TASK   - Prints documentation for a given task

  • 1 Mix provides usage examples to help you figure out exactly what you want to do.

The second example in the preceding listing tells you how to create a new Elixir project. That sounds helpful. But what if you want to know more? You can see in the following listing that mix even tells you how to get more help. If you enter mix help new it will give you all the help documentation for that specific task.

Listing 5.2. Using mix help new to learn about creating a new Elixir project
> mix help new

                                    mix new

Creates a new Elixir project. It expects the path of the project as argument.

    mix new PATH [--sup] [--module MODULE] [--app APP] [--umbrella]      1

A project at the given PATH will be created. The application name and module
name will be retrieved from the path, unless --module or --app is given.

A --sup option can be given to generate an OTP application skeleton including a
supervision tree. Normally an app is generated without a supervisor and without
the app callback.

An --umbrella option can be given to generate an umbrella project.

An --app option can be given in order to name the OTP application for the
project.

A --module option can be given in order to name the modules in the generated
code skeleton.

## Examples

    mix new hello_world                                                  2

Is equivalent to:

    mix new hello_world --module HelloWorld

To generate an app with a supervision tree and an application callback:

    mix new hello_world --sup

To generate an umbrella application with sub applications:

    mix new hello_world --umbrella
    cd hello_world/apps
    mix new child_app

  • 1 Arguments presented in brackets [like this] are considered optional.
  • 2 This is the simplest usage example for mix new.

You can see that there are a number of options you can specify when creating a new Elixir application. If you wanted to create a new Elixir application, you’d definitely want to use this tool as the first step. mix new will not only generate the standard Elixir application directory structure but also give you some files that are starting points for your application.

Before you create an application for your auction backend, let’s use the tool on a throwaway project just to see how it works. Because the first option to mix new is the path of the project as well as the application name, you need to avoid most special characters. Like in variable names, you avoid using dashes, but you can use underscores. If you’re like me and prefer dashes to underscores in your directory names, you can specify an alternative name for the application itself (with --app) that uses underscores instead.

Suppose you want to create a Facebook replacement named FriendFace.[1] In a temporary directory somewhere, run the following command.

1

Thanks to the show “The IT Crowd” for the name inspiration.

Listing 5.3. Using mix new
> mix new friend-face --app friend_face
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/friend_face.ex
* creating test
* creating test/test_helper.exs
* creating test/friend_face_test.exs          1

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd friend-face
    mix test                                  2

Run "mix help" for more commands.

  • 1 The generator creates a test file for you.
  • 2 It even suggests you run it right away to make sure it’s hooked up correctly.

You can see that not only did the mix new command create your directory structure, but it also gave you a README, a .gitignore file for ignoring files in your Git repo if you were to create one, a config file, a skeleton module for your friend_face application, and even a test file. You can peek into each of these files and see that they’re not just empty files. They’re files that have actual uses as they are, and they’re very helpful as you’re getting started. If you follow the instructions at the end of listing 5.3 and run mix test in your project directory, you’ll even discover that it already has a passing test.

After you’re done exploring this test application, feel free to delete the directory and all the files it generated for you. You won’t need them.

5.1.2. Generating an auction umbrella application

Now that you’ve seen the basics of how mix works, let’s create an application for your auction site. You’ll use the mix new command like before, but this time you’ll do it in a nontemporary directory that you use for your projects. The first thing you need to generate is the umbrella itself. You’ll then create an auction application inside that umbrella.

Creating an umbrella application is very simple: pass the --umbrella flag to mix new. Let’s call this umbrella structure auction_umbrella to make it recognizably an umbrella structure. I got the following output in my terminal when I ran that command.

Listing 5.4. Generating an umbrella application
> mix new --umbrella auction_umbrella
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating apps
* creating config
* creating config/config.exs
Your umbrella project was created successfully.
Inside your project, you will find an apps/ directory
where you can create and host many apps:

    cd auction_umbrella
    cd apps                     1
    mix new my_app              2

Commands like "mix compile" and "mix test" when executed
in the umbrella project root will automatically run
for each application in the apps/ directory.

  • 1 Unlike running “mix new” without --umbrella, no tests are created (because you don’t yet have an app).
  • 2 It gives you instructions on how to create the first app inside the umbrella.

This kind of structure allows you to create sub-applications under the umbrella application. You don’t really need to modify anything it generated at the moment, but the output does offer a good clue as to what you should do next. All the sub-applications of an umbrella application are stored in the apps directory (auction_umbrella/apps in this case). You can cd into that subdirectory and run mix new app_name to generate a sub-application.

You’ll name your application Auction. Catchy, huh? cd into auction_umbrella/apps and type mix new auction --sup in your terminal. What does the --sup do? It creates an application skeleton along with the files to easily create a supervision tree. This won’t make much difference now, but it will as you go on (you’ll use that supervisor in chapter 7—it will do things like make sure the database connections are maintained). You should see output like the following.

Listing 5.5. Generating the Auction application
> mix new auction --sup           1
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/auction.ex
* creating lib/auction/application.ex
* creating test
* creating test/test_helper.exs
* creating test/auction_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd auction
    mix test

Run "mix help" for more commands.

  • 1 Make sure you run this inside the auction_umbrella/apps directory.

You may have realized that you now have two mix.exs files in your umbrella application—one at the top level of the umbrella, and one in the new Auction application. That’s perfectly fine—they’ll live well with each other. But the rest of the work you’ll do in this chapter will be strictly inside the auction_web/apps/auction directory.

5.1.3. The magic mix.exs file

For any Elixir application, the mix.exs file is pretty magical. In it, you define things like the current version of your application, your application’s name, the version of Elixir it runs on, any outside dependencies that your application requires in order to run, and any additional applications or supervisors that also need to be started when your application is started.

If you take a peek inside the auction_umbrella/apps/auction/mix.exs file that the mix new task generated for you, you can begin to see just how helpful that Mix task is.

Listing 5.6. The auction_umbrella/apps/auction/mix.exs file (some comments removed)
defmodule Auction.MixProject do
  use Mix.Project

  def project do
    [
      app: :auction,
      version: "0.1.0",
      build_path: "../../_build",
      config_path: "../../config/config.exs",
      deps_path: "../../deps",
      lockfile: "../../mix.lock",
      elixir: "~> 1.7",
      start_permanent: Mix.env() == :prod,
      deps: deps()
    ]
  end

  # Run "mix help compile.app" to learn about applications.              1
  def application do
    [
      extra_applications: [:logger],
      mod: {Auction.Application, []}
    ]
  end

  # Run "mix help deps" to learn about dependencies.                     1
  defp deps do
    [
      # {:dep_from_hexpm, "~> 0.3.0"},                                   2
      # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git",
     tag: "0.1.0"},
      # {:sibling_app_in_umbrella, in_umbrella: true},
    ]
  end
end

  • 1 To read up on the details of these sections, run these commands.
  • 2 These lines are commented-out, but they provide examples of how you can declare the dependencies for your app.

Let’s further break down each of these functions.

What’s the difference between .exs and .ex files?

You may have noticed that some of the files you’re working with have the extension .ex, and some have the extension .exs. What’s the difference? When do you use each?

  • .ex files are for compiled code. When you’d like to execute the code in an .ex file, Elixir first needs to compile the code.
  • .exs files are script files and are interpreted when they’re executed (meaning they don’t have to be precompiled).

Most of the time, you’ll be writing .ex files, as you want all the benefits of compiled code (compiler optimizations, speed, and so on). .exs files, because they’re interpreted, are slower to run (they have to go through parsing, tokenization, and so on). They are, however, a flexible choice when you don’t require compilation. For example, the mix.exs file in an Elixir project and all test code files are .exs files.

The project function

The project function defines the top-level details of your Elixir application:

  • The app name as an atom (auction) and the current version of the application (0.1.0)
  • The Elixir versions your app will run on (~> 1.7[2])

    2

    The ~> means the following version number can be incremented by the last dot value in the version. In this case, you could run versions 1.7 through 1.?, but not versions before 1.7 or versions 2.0 and later.

  • Configuration for the application, so that if it goes down either by failure or successful shutdown, other applications that your application started as dependencies will also be shut down (this is set as true in the production environment by the return of the comparison of the current Mix environment to the :prod atom.)
  • A list of dependencies. This is a list of tuples containing external package names and version numbers, but for simplicity’s sake, it’s set up by default to rely on a private function, defined later, named deps. We’ll cover the deps function shortly.

The following listing shows the project function.

Listing 5.7. The project function
def project do
  [
    app: :auction,
    version: "0.1.0",

    build_path: "../../_build",                1
    config_path: "../../config/config.exs",    1
    deps_path: "../../deps",                   1
    lockfile: "../../mix.lock",                1
    elixir: "~> 1.7",
    start_permanent: Mix.env() == :prod,
    deps: deps()
  ]
end

  • 1 Configures the project with relative paths back to the umbrella application root

These are only some of the options that can be set here for your application. Elixir itself has a few more (which you can read about in its excellent documentation for Mix.Project: https://hexdocs.pm/mix/Mix.Project.html). Other dependencies of your application may also have options that will need to be set here.

The application function

The application function (see listing 5.8) is pretty simple in terms of what’s generated, but the functionality it provides is big. In simple terms, it’s what tells the compiler that you’d like to generate an .app file for your application. According to the documentation (which you can read by typing # Run "mix help compile.app", as in the comment about the function declaration in listing 5.6), “An .app file is a file containing Erlang terms that defines your application. Mix automatically generates this file based on your mix.exs configuration.”

Listing 5.8. The application function
def application do
  [
    extra_applications: [:logger],
    mod: {Auction.Application, []}         1
  ]
end

  • 1 You can specify lots of other options here, but they’re unnecessary for your starter application.

An Elixir application compiles down to Erlang code (figure 5.2), which will run on the BEAM virtual machine, and the application function provides additional instructions to the compiler about how to compile Erlang code. The most-used options concern additional, external applications that need to be started along with your application. Any application name you provide to extra_applications (in :atom form) is guaranteed to start before your application, so that it will be ready to accept commands when your application needs it. By default, Elixir’s built-in Logger application is started up to provide logging functionality.

Figure 5.2. All of your files, plus all the dependencies, are compiled into files that can run on the BEAM VM.

Third-party applications and dependencies can tell the Elixir compiler that they need to be started along with your application. If they do, they’ll be started automatically without you having to tell Elixir to do so. If an application needs to be included in extra_applications, the README for the library will let you know. If it doesn’t mention the requirement, you can bet that it will either be started automatically or that it doesn’t need to be started at all.

Finally, the mod key is an application callback. Any module you specify in this list (along with a list of arguments, which is currently empty) will be called when the main application starts. The callback expects Auction.Application.start/2 to be defined. When you generated the application with the --sup flag, it created that module for you and placed the reference to it here in the mod key.

The deps function

The final function in your generated mix.exs file is deps. This is where you list all the external applications, packages, and libraries your application depends on. You can see in the following listing that you currently rely on no external packages.

Listing 5.9. The empty deps function
defp deps do
  [
    # {:dep_from_hexpm, "~> 0.3.0"},
    # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag:
     "0.1.0"},
    # {:sibling_app_in_umbrella, in_umbrella: true},      1
  ]
end

  • 1 The in_umbrella: true option is for dependencies defined within the current umbrella app.

Dependencies are specified with the package name plus the version numbers accepted in a tuple (like {:package_name, "~> 1.0"}). For the version requirements, a few different options can be specified. For more details, check the Version documentation: https://hexdocs.pm/elixir/Version.html. You’ll be using deps extensively in the coming sections.

Other options

A handful of other options and functions you can use in your Mix file aren’t generated in a skeleton application, and you won’t use them in this book. For more information on these options, check out the Mix.Project documentation: https://hexdocs.pm/mix/Mix.Project.html.

5.2. Organizing, compiling, and running your new application

You’ve used mix new auction to generate a skeleton application structure and configuration for your Auction application, and now you need to move the code you created in chapter 4 from a single file containing multiple modules into separate files for each module.

Note

I apologize for the little bit of busywork in this section—this is the only time in this book where you’ll cut and paste code like this. In the last chapter, I wanted to make it clear that you could define as many modules as you wanted in a single file and that they would all compile. It was also the easiest way to use multiple modules without a full-on Mix application. But that means that you now need to break that file into three separate modules.

One of the cool things about Elixir is that you can name the files whatever you want in whatever structure you want. This allows you to structure your application as you see fit. The flip side is that unless you decide on some rules regarding how you’re going to structure your application files, it can quickly get out of control.

5.2.1. Breaking apart the three modules

If you’ll recall, you defined three modules in auction.ex in chapter 4:

  • Auction
  • Auction.Item
  • Auction.FakeRepo

You’ll therefore create three different files—one for each module. Usually the application and library code should go in the lib directory that mix new generated for you, so you’ll start there.

The mix new task created an auction.ex file in the lib directory of your application. Paste all the code that made up the Auction module into that file. You’ll notice that mix new auction generated a hello function in that file—it’s safe to overwrite all the contents of the generated file. The following listing shows that module.

Listing 5.10. The new contents of lib/auction.ex is your Auction module
defmodule Auction do                1
  alias Auction.{FakeRepo, Item}

  @repo FakeRepo

  def list_items do
    @repo.all(Item)
  end

  def get_item(id) do
    @repo.get!(Item, id)
  end

  def get_item_by(attrs) do
    @repo.get_by(Item, attrs)
  end
end

  • 1 This module is unchanged from chapter 4—it’s just in a new location in your umbrella app.

For the Auction.Item module, you need to create a new file. You could create this file in the top level of the lib directory, but it’s standard practice to match your directory structure to the namespacing of your module. Item is namespaced under Auction in your module name (Auction.Item), so a good rule of thumb is to create an auction subdirectory and have item.ex live in there. Figure 5.3 shows the file structure based on namespacing.

Figure 5.3. Auction.Item file location

Paste the entirety of the Auction.Item module code into a file named lib/auction/item.ex (or whatever other name you may have chosen). The following listing shows the contents of that file.

Listing 5.11. Your Auction.Item module in the lib/auction/item.ex file
defmodule Auction.Item do
  defstruct [:id, :title, :description, :ends_at]          1
end

  • 1 Defines the data structure but doesn’t provide default values

Finally, you have the Auction.FakeRepo code. Like Auction.Item, you match the module namespacing and your directory structure. Paste the Auction.FakeRepo module code a file named lib/auction/fake_repo.ex.

Listing 5.12. Auction.FakeRepo code in the lib/auction/fake_repo.ex file
defmodule Auction.FakeRepo do
  alias Auction.Item

  @items [                         1
      %Item{
        id: 1,
        title: "My first item",
        description: "A tasty item sure to please",
        ends_at: ~N[2020-01-01 00:00:00]
      },
      %Item{
        id: 2,
        title: "WarGames Bluray",
        description: "The best computer movie of all time, now on Bluray!",
        ends_at: ~N[2018-10-15 13:39:35]
      },
      %Item{
        id: 3,
        title: "U2 - Achtung Baby on CD",
        description: "The sound of 4 men chopping down The Joshua Tree",
        ends_at: ~N[2018-11-05 03:12:29]
      }
    ]

  def all(Item), do: @items

  def get!(Item, id) do
      Enum.find(@items, fn(item) -> item.id === id end)
    end

  def get_by(Item, map) do
    Enum.find(@items, fn(item) ->
      Enum.all?(Map.keys(map), fn(key) ->
        Map.get(item, key) === map[key]
      end)
    end)
  end                          2
end

  • 1 Feel free to customize the Items in your fake database to match your interests.
  • 2 All of these functions were written in chapter 4 and haven’t been changed.
Note

A good rule of thumb for filenames is to convert InitCase module names to lowercase and use an underscore before the previously capitalized letters. PhoenixInAction becomes phoenix_in_action, and “FakeRepo” becomes fake_repo.

5.2.2. Compile and run!

If you’ve followed along so far, you should have a directory structure similar to figure 5.4. But because Elixir is a compiled language, you need to compile your application before it can run. Thankfully, this is very easy.

Figure 5.4. The files in your Auction application

Compiling

Now that you’ve got the code all organized, you may be wondering how to run it. In chapter 4, you compiled the file directly and brought it into an IEx session to play with. Now that you have a full-fledged application, you can use the mix utility. For example, to compile your application, you can issue the command mix compile.

> mix compile
Compiling 4 files (.ex)
Generated auction app

The first time you run this, it will go through your entire application looking for .ex files and compiling them into something that can run on the Erlang VM (using the mix.exs file). You’ll notice in the preceding output that it compiled four files. Because there were no warnings during compilation, it tells you that it successfully generated the auction application.

If you run mix compile again right away, you’ll get no output from the compiler. This is because Elixir’s compiler is smart enough to recognize that no changes have been made since the last time you compiled, so nothing needs to be done. In fact, it’s smart enough to know not only whether any files changed, but also which files have changed. If a file hasn’t changed, it won’t be recompiled (unless it’s affected by a file that did change). This will save you lots of time as you build your app.

5.2.3. Running the Auction application for the first time

You now have a compiled application, so how do you run it? You haven’t created a graphical interface for the application yet—only the code required to play with your fake data through a public interface. Because of that, the only way you can interact with the program at the moment is via IEx. But you’ll start this IEx session a little differently than in the past.

In chapter 4, you started IEx while requiring a specific file (your application code, auction.ex). Now you no longer have a single file. Instead, you have an Elixir Mix application. To start up an IEx session and require an entire Mix application to be brought in, you can use the iex -S mix command inside the directory of a Mix application:

> iex -S mix
Erlang/OTP 21 [erts-10.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-
     threads:1] [hipe] [dtrace]

Interactive Elixir (1.7.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>

If you hadn’t already compiled your application, it would compile here before starting IEx. But because you did compile it, it will start right up and include all the files of the Auction application. The following listing shows an example of testing the public interface you created.

Listing 5.13. Trying out the compiled Auction application
iex(1)> Auction.list_items |> Enum.map(fn(item) -> item.title end)      1
["My first item", "WarGames Bluray", "U2 - Achtung Baby on CD"]

iex(2)> Auction.get_item_by(%{title: "WarGames Bluray"})
%Auction.Item{description: "The best computer movie of all time, now on
     Bluray!",
 ends_at: ~N[2018-10-15 13:39:35], id: 2, title: "WarGames Bluray"}

  • 1 You pipe the output of Auction.list_items/0 (with |>) into the first parameter for Enum.map/2.

It’s all working! You didn’t have to manually require any files or specify which files you were going to use—it all just worked! This is the magic of an Elixir application. I say magic because moving from using individual source code files outside of a Mix application (as in chapter 4, with a mess of file requires and effort needed to get everything working together) to having something that just works right off the bat (as in this chapter) is very refreshing.

5.3. Using Hex to get external dependencies

Now that you’ve gone through the configuration for the Auction application, you can move on to declaring the dependencies. Some very smart Elixir and Erlang developers have produced open source packages that can add functionality to your application, and one such package you’ll eventually add to your application is Ecto. Ecto describes itself as “A database wrapper and language integrated query for Elixir.”

You’ll eventually need to move away from your FakeRepo and into a real repo so you can allow real functionality in your application. Ecto is currently the package to use when creating Elixir applications that need to talk to databases. In fact, you have to explicitly tell Phoenix not to bring in Ecto if you don’t need it when creating a new Phoenix project.

As you’ll recall from the discussion of the mix.exs file (section 5.1.3), your application keeps track of its required dependencies in the deps function. So far, you know you’re going to require a package called Ecto and that it belongs in the mix.exs file.

Hex is Elixir’s package manager. But Hex is more than that—it also comes with nice Mix tasks that make using the package manager easy. For example, there’s a great search tool. Let’s say you know you’re going to need a package that makes rendering React.js components easier in your Phoenix application, but you don’t know any package names. You can either go into a web browser and search the database through Hex’s frontend (at https://hex.pm) or use the Mix task mix hex.search PACKAGE. The following listing shows the output from using the Mix task.

Listing 5.14. Searching Hex for a React.js package
> mix hex.search react                            1
Package              Version             URL
reaxt                1.0.1               https://hex.pm/packages/reaxt
react_phoenix        0.5.0               https://hex.pm/packages/
     react_phoenix
lyn                  0.0.16              https://hex.pm/packages/lyn
phoenix_components   1.0.2               https://hex.pm/packages/
     phoenix_components
react_on_elixir      0.0.4               https://hex.pm/packages/
     react_on_elixir
phoenix_reactor      0.1.0               https://hex.pm/packages/
     phoenix_reactor
reactive             0.0.1               https://hex.pm/packages/reactive
reactivity           0.6.0               https://hex.pm/packages/reactivity
elixir_script_react  1.0.2-react.15.6.1  https://hex.pm/packages/
     elixir_script_react
Phoenix_react        0.1.0               https://hex.pm/packages/
     phoenix_react

  • 1 There may be many more packages listed when you run this command.
Note

If you get an error stating that “The task “hex.search” could not be found,” run the command mix local.hex and try again.

The packages listed here have something to do with the word “react.” It could be in the package name, in the description, or other places, and they’re ordered loosely by popularity.

Sometimes you’ll know what package you need, and you need to know the latest version number. In those cases, I’ve found the Mix task to be the fastest way to retrieve that information. For example, you know you’ll need Ecto in your application, so the only further piece of information you require is the latest version number (unless you already know of a specific one you want to depend on). That means you can search for ecto with the Mix task, as shown in the following listing. Ecto is split into two distinct packages: ecto and ecto_sql. For now, you need the ecto_sql version in order to talk to your database.

Listing 5.15. Searching hex.pm for Ecto
> mix hex.search ecto                                    1
Package  Description                    Version  URL
ecto     A toolkit for data mapping...  3.0.3    https://hex.pm/packages/ecto
ecto_sql SQL-based adapters for Ecto... 3.0.3    https://hex.pm/packages/
     ecto_sql
# SNIP!

  • 1 There will be many more results when you run this command.

You can see that the latest version was 3.0.3 when I ran this command. Unless you have a compelling reason to use an older version of a package, it’s generally a good idea to use the latest. You’ll depend on at least version 3.0.3 in your application.

Note

It’s likely that by the time you read this, the version numbers will have advanced beyond 3.0.3. If that’s the case, use the latest version available.

To specify the package name and version 3.0.3, your deps function would look like the following.

Listing 5.16. Specifying your first dependency
defp deps do
  [
    {:ecto_sql, "3.0.3"}       1
  ]
end

  • 1 Specifies that you want version 3.0.3

The preceding listing shows how to specify a dependency to a specific version requirement. But sometimes there are other ways you’d like to handle the dependency versions.

In general, Hex packages should use semantic versioning (major.minor.patch). In the case of Ecto, you’re looking at the major 3, minor 0, patch 5 release. Major versions can have backward-incompatible changes in them—you’ll normally require some migration in your usage of the package if you change major versions. Minor versions typically contain new functionality but don’t break existing usage. Patch versions typically contain minor bug fixes and the like.

So what if you want to keep your Ecto package up to date but don’t want to worry about manually editing the deps function every time there’s a new release? There are a few options in the version specification syntax that you can use:

  • > 3.0.3—This gives you any package as long as it’s above version 3.0.3, including potentially breaking major releases. This does not include version 3.0.3 itself, so as I write this, Hex wouldn’t be able to find a suitable version of Ecto for the package.
  • >= 3.0.3—This gives you any package equal to or above 3.0.3. This includes any patch, minor, and potentially breaking major releases, so use this carefully.
  • < 4.0—This uses any package found as long as it isn’t version 4.0 or above. This could also mean that version 0.0.4 could be used just as well as version 3.0.3. Use this with caution as well.
  • >= 3.0.3 and < 3.1.0—These gives you any package that’s from the major 3 minor 0 releases, as long as it’s equal to or above 3.0.3.
  • ~> 3.0.3—The preceding option is so common that a special symbol is used to denote this kind of requirement. ~> MAJOR.MINOR.PATCH uses any version that’s beyond the patch version but without incrementing the minor version. ~> MAJOR.MINOR takes any minor or patch version up to the next major version.

Regardless of the version requirements, Hex will attempt to fetch the latest, most recent package version that meets the requirements specified. With that in mind, let’s make sure you have Hex include any bug fixes in the 3.0 minor branch by using the ~> 3.0.3 format. The following listing shows what the deps function should look like.

Listing 5.17. Your newly specified dependency list
defp deps do
  [
    {:ecto_sql, "~> 3.0.3"}     1
  ]
end

  • 1 Accepts any version from 3.0.3 to 3.0.x, as long as x is greater than or equal to 3

Ecto is an important part of your application, because it provides a wrapper around a database, but it doesn’t speak to the database directly. Ecto requires an adapter specific to the database that you’d like to use. Why not use the adapter directly, without Ecto? Ecto provides a large range of utilities and functions that make working with a database much easier and without a lot of the boilerplate code that’s usually necessary. And because you’re writing code that Ecto then translates into database-speak, you can typically move from one database to another without having to change much (if any) of your code.

In this book, you’ll be using the PostgreSQL database, so you’ll use the PostgreSQL adapter. The vast majority of the code you’ll write (if not all of it) will work as written with any of the other Ecto adapters. There may be small exceptions to this (mostly during the setup of the adapter itself), but the fact that databases are so easily interchangeable is one of the great things about using Ecto.

Because you’re using PostgreSQL, you need to modify your deps function in mix.exs to include the dependency (postgrex), as in the following listing.

Listing 5.18. The newly specified dependency list
defp deps do
  [
    {:ecto_sql, "~> 3.0.3"},
    {:postgrex, "~> 0.14.1"}         1
  ]
end

  • 1 postgrex is the Postgres Ecto adapter. Feel free to use the adapter you need for your preferred database.

If you’d rather use a different supported database for your application, be sure to use the correct adapter (see table 5.1).

Table 5.1. Ecto database adapters

Database

Ecto adapter

Dependency

PostgreSQL Ecto.Adapters.Postgres postgrex
MySQL Ecto.Adapters.MySQL mariaex
MSSQL MssqlEcto mssql_ecto
SQLite Sqlite.Ecto2 sqlite_ecto2
Mnesia EctoMnesia.Adapter ecto_mnesia

5.3.1. Pulling in your dependencies

Now that you have the dependencies specified, you need to bring them into your application. You do that in the following listing by using a Mix task—mix deps.get. When you execute that task in your terminal, it will query hex.pm about the latest applicable versions of the dependencies you specified. It also does the job of fetching their dependencies, so you don’t have to worry about manually ensuring that a long line of dependency requirements are met (see figure 5.5).

Listing 5.19. Getting dependencies with mix deps.get
> mix deps.get
Resolving Hex dependencies...
Dependency resolution completed:
New:
  connection 1.0.4
  db_connection 2.0.3
  decimal 1.6.0               1
  ecto 3.0.5                  2
  ecto_sql 3.0.3
  postgrex 0.14.1
  telemetry 0.2.0
* Getting ecto_sql (Hex package)
* Getting postgrex (Hex package)
* Getting connection (Hex package)
* Getting db_connection (Hex package)
* Getting decimal (Hex package)
* Getting ecto (Hex package)
* Getting telemetry (Hex package)

  • 1 You didn’t request decimal—the packages you did request depend on them.
  • 2 ecto_sql relies on ecto, so it is brought in too.
Figure 5.5. Hex manages the sometimes complex relationships between your app’s dependencies.

The output you see is Hex fetching the required packages and putting them into the deps directory of your application. The next time you compile your application (whether explicitly via mix compile or by starting your application with iex -S mix), those dependencies will be compiled into your application for your application code to take advantage of.

We’ll go deeper into using Ecto in the next chapter, but to demonstrate how you can use external dependencies with little fuss, let’s temporarily add the UUID package to your application dependencies. Add the dependency requirement to your mix.exs file as follows.

Listing 5.20. Temporarily adding UUID to your dependencies
defp deps do
  [
    {:ecto_sql, "~> 3.0.3"},
    {:postgrex, "~> 0.14.1"},
    {:uuid, "~> 1.1.8"}         1
  ]
end

  • 1 Your application can have as many dependencies as it requires.

Be sure to run mix deps.get again to fetch the newly added dependency.

Now, use it in your application’s environment via an IEx session. UUID is a package that allows you to easily generate universally unique identifiers, so you’ll test its ability to do so in the following listing by using its uuid4/0 function to generate a version 4 UUID. You could even use it to generate auction titles.

Listing 5.21. Using the UUID package in your auction application
> iex -S mix
# ... likely lots of code compilation output

iex(1)> UUID.uuid4
"40a6eb21-4c85-46ac-a550-e18130187aee"                   1

iex(2)> %Auction.Item{title: UUID.uuid4}
%Auction.Item{description: nil, ends_at: nil, id: nil,
 title: "592b2b0b-5f9e-4c74-91d9-478bd2ca1d9b"}          1

  • 1 These ID strings will be different when you run this.

Because the purpose of UUIDs is to be “universally unique,” your output will be different than mine was, but the formatting will be the same, and it will be a valid UUID string. It works!

You’re now done with your little experiment, and you no longer need the UUID package. How can you get rid of once-required dependencies that are no longer needed? It’s actually very easy. Simply remove it from the deps/0 function in your mix.exs file.

Listing 5.22. Your deps function without UUID
defp deps do
  [
    {:ecto_sql, "~> 3.0.3"},
    {:postgrex, "~> 0.14.1"}        1
  ]
end

  • 1 If you no longer need a dependency, just remove it from deps. UUID was previously here.

Once you’ve done that, you can again run mix deps.get and it will update your mix.lock file, which keeps track of all your dependencies. But if you look in your deps folder for your project, you’ll still see a directory for UUID. If you’d like to get rid of that, you can manually delete it from the directory, or you can use the mix deps.clean uuid command in your terminal. It will determine that it’s no longer needed and remove the package’s code from your hard drive.

Summary

  • When organizing Elixir projects, use multiple files instead of one large file.
  • Use the mix new command to start a new Elixir project. That single task will generate the initial folder structure and files to get you started.
  • Configuration of your application takes place in the mix.exs file.
  • Hex.pm is the Elixir package manager, and it has many helpful third-party modules and applications to help you create your application.
  • Your application’s package dependencies are stated in the mix.exs file.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.200.211