DEV Community

Noel Worden
Noel Worden

Posted on • Edited on

3

Elixir/Phoenix Project: Create First Schema & Run Tests in Docker

Goal

  • Create Destinations schema under Mapping context

Create Destinations schema under Mapping context

The goal for this next step was to establish the first schema in the project, with the intention to then write the tests, and finally the views. In my previous projects I had always done this by hand, starting with the migration, then the schema, and then the context; and that was the plan for this work as well. But, plans changed, pretty dramatically. Having the time to explore and hopefully improve on my workflows and knowledge base, I spent some time researching before diving into the code. That research -unsurprisingly- led me to the the Phoenix Context docs, which, led me to the phx.gen.html generator. I've known about generators, but I had no idea that a generator existed that did so much.

Initially I was on the fence about using the generator, on one hand it is incredibly convenient for the framework to generate every file necessary to get a schema running, tested, and browser-ready. On the other hand, having all the work done automatically can lead to problems down the road, like debugging, because I'm not as intimately familiar with generated files as I would be with files I typed out by hand. Similar to the common idea that most people learn coding principles much better if they type it all out instead of copy/pasting it from the source. Ultimately I decided to use the generator. I have enough experience with the files being generated that I didn't feel the need to write them all out by hand. I also felt that it would be beneficial to know all the necessary bases were covered, and have that as a pull request to utilize as a reference for future work that I may write without a generator.

The phx.gen.html generator takes four arguments. The first is the context module name, then the schema module name, third is the schema module's plural name (to be used as the table name), and, optionally, the last is the list of fields and corresponding types.

The schema I wanted to created had a good number of fields, my generation looked like this:

mix phx.gen.html Mapping Destination destinations \
longitude:decimal \
latitude:decimal \
name:string \
description:text \
spinner_friendly:boolean \
lake:boolean \
less_than_one_hour:boolean \
one_to_three_hours:boolean \
greater_than_three_hours:boolean \
car_friendly:boolean \
hike_in:boolean \
allows_dogs:boolean \
dogs_off_leash:boolean \
season_spring:boolean \
season_summer:boolean \
season_fall:boolean \
season_winter:boolean \
car_camp:boolean \
backpack_camp:boolean \
fee:boolean \
ice_fishing:boolean
view raw migration01 hosted with ❤ by GitHub

In that generation command, Mapping is the context, Destination is the schema module, destinations the table name, and lastly the list of fields and their types.

  • side note: That command could have been typed out as one long string, like:

    mix phx.gen.html Mapping Destination destinations longitude:decimal latitude:decimal name:string description:text spinner_friendly:boolean lake:boolean less_than_one_hour:boolean one_to_three_hours:boolean greater_than_three_hours:boolean car_friendly:boolean hike_in:boolean allows_dogs:boolean dogs_off_leash:boolean season_spring:boolean season_summer:boolean season_fall:boolean season_winter:boolean car_camp:boolean backpack_camp:boolean fee:boolean ice_fishing:boolean
    view raw migration02 hosted with ❤ by GitHub

    But by using the \ as an escape character, the command can be broken out over several lines to enhance readability.

This one generation command created the context, schema, migration, all tests, and all views. The output showed all the files created and then instructions on two more steps needed to complete the process:

Add the resource to your browser scope in lib/my_app_web/router.ex:
resources "/destinations", DestinationController
Remember to update your repository by running migrations:
$ mix ecto.migrate

Executing the first step from the instructions created all the default the routes for this schema. For this case, the entirety of the default routes works because I intended on utilizing them all. I confirmed the routes generated as expected with this command:

docker-compose exec web mix phx.routes

Which returned:

destination_path GET /destinations AtlasWeb.DestinationController :index
destination_path GET /destinations/:id/edit AtlasWeb.DestinationController :edit
destination_path GET /destinations/new AtlasWeb.DestinationController :new
destination_path GET /destinations/:id AtlasWeb.DestinationController :show
destination_path POST /destinations AtlasWeb.DestinationController :create
destination_path PATCH /destinations/:id AtlasWeb.DestinationController :update
PUT /destinations/:id AtlasWeb.DestinationController :update
destination_path DELETE /destinations/:id AtlasWeb.DestinationController :delete

The second step in the instructions was to execute the migration created in the generation, creating the destinations table in the database.

With the exception of including null:false to a few the fields in the migration, all files created by the generator were good to go. I have some work to do to the generated html files, but that will be covered by a ticket down the road. After executing the migration command, I can navigate to localhost:4000/destinations and I see the index page of that schema, with the ability to create a new record, as well as all the other record manipulating functionality I expect. It's actually pretty amazing how much was accomplished with that phx.gen.html command. There are several mix.phx commands, all of course do something different, and save a lot of typing. They can be found in the Mix Tasks section of the Phoenix documentation, or by typing mix phx in the terminal.

Bonus - Running tests in the Docker container

My initial plan was to separate out this work, but since the tests were included in the files produced by the generator I decided to tackle this and include it in the pull request. I hit some bumps in the road when changing the database configurations in test.exs, to get the tests to run within the container. The process was involved enough that it warranted its own post. The high-level overview is that the hostname of the testing database needs to be "db" to run the tests in a container, but "localhost" for the tests to run in CI. The solution was to use System.get_env/2 in the database configuration logic, and add an env field in the CI testing configuration.

The update to the elixir.yml file took this step:

- name: Run tests
run: mix test

And added an environmental variable of DB_HOSTNAME:

- name: Run tests
run: mix test
env:
DB_HOSTNAME: "localhost"

Then the hostname field of the testing database configuration found at test.exs went from this:

config :atlas, Atlas.Repo,
username: "postgres",
password: "postgres",
database: "my_app_test#{System.get_env("MIX_TEST_PARTITION")}",
hostname: "db",
pool: Ecto.Adapters.SQL.Sandbox

To this:

config :atlas, Atlas.Repo,
username: "postgres",
password: "postgres",
database: "my_app_test#{System.get_env("MIX_TEST_PARTITION")}",
hostname: System.get_env("DB_HOSTNAME", "db"),
pool: Ecto.Adapters.SQL.Sandbox

Where System.get_env/2 is looking for the environmental variable set in the elixir.yml CI file. When that environmental variable is present (when tests are being run in the CI) it sets hostname to the variable's value, "localhost", allowing the tests to run in the CI build. And if the environmental variable is not found, the hostname defaults to "db", allowing for the test to properly run in a container.

Closing Thoughts

With the completion of this work, I can now Create, Read, Update, and Delete (CRUD) records in the destination table. The index view is currently a little rough, because of the size of the table that is being displayed, but that task is for another time. I can now also execute tests in the Docker container with this command:

docker-compose exec web mix test

And the tests also run as part of the CI check.

Having a working schema sets me up well for my next task, which is to utilize the seeds.exs file to populate the new table with sample data.

This is what my repo looks like at the end of this block of work.

References


This post is part of an ongoing series. I encourage any critique, feedback, or suggestions in the comments.

Top comments (0)