Phoenix, Ecto and time zones

This is another part of my series on learning Elixir.

In the previous episode, I calculated the distance of flight. I will use it later for gathering statistics. Another metric, I would like to have is a time of flight. Usually, flight departure and arrival times are presented in local time. So it gets a bit tricky to get the time length when the journey spans across few time zones.

Elixir’s standard DateTime library doesn’t work with time zones very well. The Internet suggests I should use Timex.

But first I needed to make some changes into my database because up until now, I had only flight date. A few weeks ago I wrote a post, how to update your schema with migrations and this time I followed the same steps. Still, I haven’t figure out how to do it in a bit more automated manner.

defmodule Flightlog.Repo.Migrations.CreateFlight do
  use Ecto.Migration

  def change do
    alter table(:flights) do
      modify :arrival_date, Timex.Ecto.DateTimeWithTimezone
      modify :departure_date, Timex.Ecto.DateTimeWithTimezone
    end
    
  end
end

As you can see, I used specific Timex types. This works only with Postgre, and if you want to use timezones it needs one additional step. You’ll have to add custom type to your database:

CREATE TYPE datetimetz AS (
    dt timestamptz,
    tz varchar
);

You can read more about using Timex with Ecto on this documentation page.

I also updated my flight.ex model. It looks like that right now:

defmodule Flightlog.Flight do
  use Flightlog.Web, :model

  schema "flights" do
    field :departure_date, Timex.Ecto.DateTimeWithTimezone
    field :arrival_date, Timex.Ecto.DateTimeWithTimezone
    field :flight_number, :string
    field :plane_type, :string
    field :from, :string
    field :to, :string

    timestamps()
  end

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """
  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:departure_date, :arrival_date, :flight_number, :plane_type, :from, :to])
    |> validate_required([:departure_date, :arrival_date, :flight_number, :plane_type, :from, :to])
  end
end

After that, I walked along the path that’s proven to work in the flight distance part. I added a new function in my math.ex library, making use of Timex diff function:

    def flightTime(earlier, later) do
        hours = Timex.diff(later, earlier, :hours)
        minutes = rem(Timex.diff(later, earlier, :minutes), 60)
        "#{hours}:#{minutes}"
    end

And I’m calling it from the view in another function, so it’s easily accessible from the template:

  def time(time1, time2) do
    Flightlog.Math.flightTime(time1, time2)
  end

And that was it. Although it took me few hours because I was struggling a bit with Timex types. I didn’t read carefully the documentation and for example missed the step with creating new Postgre type. Good lesson here, to look into docs carefully :)

The effect:

 

Screen Shot 2017-05-07 at 23.26.43.png

As you can see, I made some approach at formatting dates. Unfortunately, I didn’t manage to show them in local time. They’re in UTC in here. This will be next step most likely.

That’s all for today. Next week we’ll try to test this new module. In the meantime, check previous episodesAnd if you’re interested in machine learning, look into my weekly link drop.

Extending your Ecto model

Last week I used Ecto models to quickly created the database and I was very surprised how all got generated for me. But the model I build was very simplistic, and now I need to extend it. I started working on first serious functionality for the project, and I’ll have to do some changes. For start, I’m just testing by adding one field.

I generated new migrations file in /priv/repo/migrations folder and a bit by trial and error I end up with file like that:

defmodule Flightlog.Repo.Migrations.CreateFlight do
  use Ecto.Migration

  def change do
    alter table(:flights) do
      add :plane_type, :string
    end

  end
end

And after running mix ecto.migrate, I actually got some results:

Michals-MBP:flightlog michal$ mix ecto.migrate
01:01:37.663 [info]  == Running Flightlog.Repo.Migrations.CreateFlight.change/0 forward
01:01:37.663 [info]  alter table flights
01:01:37.666 [info]  == Migrated in 0.0s

This worked for adding fields to the database but didn’t automagically update all the access layers. There’s probably some way to it, but this time I did it manually.

First I updated the views, for example added following into show.html.eex:

https://gist.github.com/mlusiak/0979cb46187ee75cd724190e09aa96c1

This solved the visuals but still didn’t work. The crucial were changes in the model:

  defmodule Flightlog.Flight do
  use Flightlog.Web, :model

  schema "flights" do
    field :date, Ecto.DateTime
    field :flight_number, :string
    field :plane_type, :string
    field :from, :string
    field :to, :string

    timestamps()
  end

  @doc """
  Builds a changeset based on the `struct` and `params`.
  """
  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:date, :flight_number, :plane_type, :from, :to])
    |> validate_required([:date, :flight_number, :plane_type, :from, :to])
  end
end

I added new field both in schema part and in changeset. Cast is responsible for things being updated. I didn’t have to add it to be validates as required.

So this was actually a bit tedious, but I’m probably missing something here. Hopefully I’ll figure it out by the time I’ll need to make bigger changes.

I also found this post, that’s deal with similar problem.

That’s all for today. Tune in next week for another part. Also, check previous episodesAnd if you’re interested in machine learning, look into my weekly link drop.

Connecting to database with Ecto

In the last episode of my Elixir adventures, I messed with own-made Views and Controller to display hardcoded flight information for my FlightLog. This week I’ll try to show data based on the content of the database.

Phoenix doesn’t have built-in data access capabilities. But there’s awesome Ecto project, that’s sort of beefed up ORM. It reminds me Entity Framework (or good parts of it) in .NET or Rails. It supports multiple databases, although the default is Postgres. If you generated your Phoenix project by default and didn’t exclude Ecto, you should have everything you need to start. If no, refer to this guide

To add data access to FlightLog, I started the way, I would usually do in a web project. I installed the database (I went with Postgres), created some table, inserted some sample data. It was proven later, that it wasn’t a necessary step.

To build your first model, go to your project root and type for example:

$ mix phoenix.gen.html Flight flights date:datetime flight_number:string from:string to:string
* creating priv/repo/migrations/20150409213440_create_flight.exs
* creating web/models/flight.ex
* creating test/models/flight_test.exs
* creating web/controllers/flight_controller.ex
* creating web/templates/flight/edit.html.eex
* creating web/templates/flight/form.html.eex
* creating web/templates/flight/index.html.eex
* creating web/templates/flight/new.html.eex
* creating web/templates/flight/show.html.eex
* creating web/views/flight_view.ex
* creating test/controllers/flight_controller_test.exs

Add the resource to your browser scope in web/router.ex:

    resources "/flights", FlightController

and then update your repository by running migrations:

    $ mix ecto.migrate

As you can see, this gave me a lot of stuff – a migration, a controller, a controller test, a model, a model test, a view, and a number of templates. It also instructs as to add new Controller to the router. Let’s do that.

I also removed recent additions, because they served the same purpose. I initially though I’ll just edit them, but Ecto surprised me by doing everything for me. Updated route file looks like that:

defmodule Flightlog.Router do
  use Flightlog.Web, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", Flightlog do
    pipe_through :browser # Use the default browser stack

    resources "/flights", FlightController
  end

  # Other scopes may use custom stacks.
  # scope "/api", Flightlog do
  #   pipe_through :api
  # end
end


After that I run mix ecto.migrate which executed migration file:

defmodule Flightlog.Repo.Migrations.CreateFlight do
  use Ecto.Migration

  def change do
    create table(:flights) do
      add :date, :datetime
      add :flight_number, :string
      add :from, :string
      add :to, :string

      timestamps()
    end

  end
end


This created tables for me as defined in phoenix.gen.html. I noticed that database has some extra fields for basic journaling like inserted_at and updated_at. I logged into the database and added some data just to have something to show and fired up my project again:

mix phoenix.server

When you browse to the site, you’ll notice that not only I have a list of flights, but also buttons for actions like Edit, Delete or Add a new item. All generated for me and even with decent styling. That was a very nice surprise.

Screen Shot 2017-04-06 at 13.00.42.png

So first look at Ecto is very positively surprising. It created all the boilerplate code for me, but it is also a code that’s understandable and easy to edit. There seem not to be any underlying magic. If I need to change default behaviour I feel I wouldn’t have much problem doing that. We’ll see in future if practice will support those claims.

I haven’t covered any of the internals how Ecto works, so if you’re interested I suggest you peek into this guide and official documentation.

That’s all for today. Tune in next week for another part. Also, check previous episodesAnd if you’re interested in machine learning, look into my weekly link drop.