Realtime Stats

Readers: 0

Likes: 69

The Making Of |> Why, What and How?

Reading this blog post by José Valim, introducing their new company Dashbit and the blog they built, inspired me to share the story of Real World Phoenix. It has grown quite organically and as I am writing this post I'll be actually implementing some features I have been wanting to build, so I'll just explain what I'm building as I go.

Why?

Let's start with the why? The first time I used the term Real World Phoenix was actually a blog post I had written for the guild(a blog where me and my colleaugues at Kabisa write about all things tech). It was a post written beginning of April 2019, just before ElixirConfEU and shortly after Chris McCord made his LiveView repository public. The excitement of the whole community really triggered me into creating something with LiveView and throwing it out into the world, hence the term Real World Phoenix.

Since then the concept sticked and it has evolved as something I can use to share practical things about using Phoenix in real world projects. I also see it as a sort of counter movement against the tendency I see with engineers to just use rails when a project asks for something straightforward and quick to setup. I really believe Phoenix can be just as fast and easy to use, once we get past the initial learning curve(which is really small in my opinion). And the added benefit being that it can be much more scalable when things do start to explode... Yes, I know, Rails can also scale. But hey, I just really like Phoenix and Elixir (you are probably reading this because you do too, right?).

After that first post I wrote a number of other posts on the guild and eventually acquired the domain realworldphoenix.com so I could have an even more focussed outlet for all things Phoenix and Elixir.

What?

So what is this blog all about. As I said it started out as a place to share practical tips on using Phoenix, but when I started to build the blog, a new and exciting idea started to pop into my mind... What if I could make a blog that has interactive content in such a way that enhances the reading experience and could also be used to gather insight from readers while they are reading... That really got me excited!

Do you like the interactive features of realworldphoenix.com?

It can be as simple as I show above here, but I see a lot of potential in gathering information from readers about what topics they would like to see more. Possibly even collecting votes to see what areas people are interested in and what topics readers would like to see more in-depth content on. So you'll be reading a lot more on this blog and you too can be partly influencing what you get to read here!

How?

Now to get to the meat of the post. How in the world am I building this blogging system? After reading the blog post on Dashbit I really liked the fact that they aren't just going with an off-the-shelf solution. Also, a system where you don't necessarily need a database can be quite handy. When I started writing this post today I also didn't have a need for a database, so I too had a db-less blog. But as I am writing this now, that actually already changed... Although I am not serving the written content from the database, I am now starting to store likes in the database persistently. I could of course use ets and even possibly dets for added disk persistence, but I am guessing that I am going to be happy that I have that extra power and convenience of postgres down the road. Basically it makes it easier for me to eventually also create user accounts on this blog if that turns out to be handy, for instance.

Blog post rendering

I recently wrote in detail about how I am rendering markdown on this blog, so I won't repeat that here. What I do want to add is that I actually use the same kind of workflow as Dashbit is using on writing and reviewing content. Using git together with pull requests. My blog posts are actually just templates residing in the template directory of Phoenix. When I start writing a new post I add it to that folder. So this one went into lib/real_world_phoenix_web/templates/post/blog/2020-02-25/the_making_of.html.md.

Then I add an entry to my router to point to my PostController show/2 action like this:

scope "/", RealWorldPhoenixWeb do
...
  get "/blog/:date/:title", PostController, :show
...
end

And my PostController renders the blog post based on the url being hit:

def show(conn, %{"title" => title, "date" => date}) do
  render(conn, "blog/#{date}/#{title}.html")
end

Once I merge a post and deploy, all of these blog posts are immediately available. I am not using an index page with posts yet, but just redirect the index/2 action to the latest post like this:

def index(conn, _params) do
  latest_post = Posts.get_latest_published_post()
  redirect(conn, to: "/blog/#{latest_post.date}/#{latest_post.slug}")
end

The last thing I do is make a list of post links based on the dates of the post, so that I can just deploy future content when needed and they stay hidden until the post date is here. That is pretty handy, because I can write content ahead of time and it'll become available without me needing to publish anything at a certain date or time.

Here is the full Posts module. Do you notice the new and shiny Enum.sort_by/3 unsing the Date shorthand! Very nice and easy to use.

defmodule RealWorldPhoenix.Posts do
  @moduledoc false
  alias RealWorldPhoenixWeb.PostView

  def get_all_posts do
    {_, _, templates} = PostView.__templates__()

    templates
    |> map_fields()
    |> reject_future_posts()
    |> Enum.sort_by(& &1.date, {:desc, Date})
  end

  def get_latest_published_post do
    get_all_posts()
    |> List.first()
  end

  defp map_fields(paths) when is_list(paths) do
    paths
    |> Enum.map(&Path.split/1)
    |> Enum.map(&Enum.take(&1, -2))
    |> reject_partials_and_index
    |> Enum.map(&convert_fields/1)
  end

  defp reject_future_posts(list) do
    list
    |> Enum.reject(&(Date.compare(&1.date, Date.utc_today()) == :gt))
  end

  defp reject_partials_and_index(list) do
    list
    |> Enum.reject(fn file -> hd(file) == "index.html" end)
    |> Enum.reject(fn [_date, file] -> String.starts_with?(file, "_") end)
  end

  defp convert_fields([date, file]) do
    %{
      date: get_date(date),
      slug: get_slug(file),
      title: get_human_friendly_title(file)
    }
  end

  defp get_slug(filename) do
    Path.basename(filename, ".html")
  end

  defp get_human_friendly_title(file) do
    get_slug(file)
    |> String.split("_")
    |> Enum.map(&String.capitalize/1)
    |> Enum.join(" ")
  end

  defp get_date(datestring) do
    Date.from_iso8601!(datestring)
  end
end

That should wrap this up. I will definitely be tweaking this setup as I go along, but I feel I have a pretty workable blog situation setup now and I can easily start writing more content about Phoenix and Elixir!

Hope you learned something new from this post.

Until next time!