Playing With Fire

Exploring the web one Elixir at a time

Using Ewebmachine to create a link shortener (part 2)

In the last part of this series Using Ewebmachine to create a link shortener (part 1) we covered setting up the basic application and services.

In this part, we will add in some listing functionality, that will output in a number of formats: HTML, XML, JSON.

So, first things first, lets set about getting list of the available URL mappings.

In the Shortener application, create a new resource file:

$ vim lib/shortener_latest_resource

In this file, build the ShortenerLatestResource as follows:

defmodule ShortenerLatestResource do
  use Ewebmachine

  resource ['latest'] do

    content_types_provided do
      [
        {'text/html', :to_html},
        {'text/plain', :to_text},
        {'application/json', :to_json}
      ]
    end

    to_html do
      {:ok, template} = File.read('templates/latest_resource.html.mustache')

      host = base_url(_req)
      {:ok, latest_links} = ShortenUrlSrv.get_latest(20)
      ctx = [links: for {short, long} <- latest_links do [short_link: "#{host}#{short}", long_link: long] end ]
      Mustache.render(template, ctx)
    end

    to_text do
      {:ok, latest_links} = ShortenUrlSrv.get_latest(20)
      Enum.map(latest_links, fn ({code, link}) -> "#{base_url(_req)}#{code} #{link}\n" end )
    end

    to_json do
      {:ok, latest_links} = ShortenUrlSrv.get_latest(20)
      linklist = Enum.map(latest_links, fn ({code, link}) ->
        shortlink = "#{base_url(_req)}#{code}"
    {:struct, [{<<"short_link">>, <<"#{shortlink}">>}, {<<"long_link">>, <<"#{link}">>}]}
      end)
      "#{:mochijson2.encode({:struct, [{:latest, linklist}]})}\n"
    end

    defp base_url(req) do
      host = :wrq.get_req_header("host", req)
      "http://#{host}/"
    end
  end
end

As you might be able to tell, there are couple of things missing that we need to provide in order to get this to work.

The first is the template: latest_resource.html.mustache, and then the ShortenerUrlSrv.get_latest/1 function.

Lets address the template first.

Create the templates/latest_resource.html.mustache and add the following to it:

&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;Latest Links&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div class="navbar navbar-inverse"&gt;
      &lt;div class="navbar-inner"&gt;
        &lt;a class="brand" href="/"&gt;Shortener&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="container"&gt;
      &lt;p&gt;The latest shortened links are:
        &lt;ul&gt;
        {{#links}}
          &lt;li&gt;&lt;a href="{{ short_link }}"&gt;{{ short_link }}&lt;/a&gt; =&gt; &lt;a href="{{ long_link }}"&gt;{{ long_link }}&lt;/a&gt;&lt;/li&gt;
        {{/links}}
        &lt;/ul&gt;
      &lt;/p&gt;
    &lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;

As you might tell, if you read both Basic Templating with Elixir-Mustache and Using a template file with Elixir and Ewebmachine<a href=”/“ title=>, we are using some of the lessons from there.

Next, we need to add the functionality to get the data our of our datastore.

In the lib/shortener_url_srv.ex file, under

  def put_url(url) do
    GenServer.call(@server, {:put_url, url})
  end

Add the following function:

  def get_latest(count) do
    GenServer.call(@server, {:get_latest, count})
  end

In order to service this, we also need to add in the following handle:

  def handle_call({:get_latest, count}, _from, %St{next: n} = state) do
    start = n - 1
    last = max(n - count, 0)
    ids = for item <- last .. start, do: b36_encode(item)

    result = Enum.map(ids, &(do_record_lookup(&1)))
    {:reply, {:ok, result}, state}
  end

  defp do_record_lookup(id) do
    record = case :ets.lookup(@tab, id) do
      [] -> raise 'The id doesnt exist'
      [record] -> record
    end
    record
  end

The last thing that we need to do is add the resource to the list in the Supervisor:

Open lib/shortener_supervisor.ex and change from

  supervisor(Ewebmachine.Sup,[[modules: [ShortenerShortenResource, ShortenerFetchResource],port: 18080]]),

to

  supervisor(Ewebmachine.Sup,[[modules: [ShortenerLatestResource, ShortenerShortenResource, ShortenerFetchResource],port: 18080]]),

With all this in place, fire it up:

  $ iex -S mix

We can now put in some data points:

  iex > ShortenUrlSrv.put_url("http://www.distortedthinking.agency")
  iex > ShortenUrlSrv.put_url("http://www.pragprog.com")

Open a new terminal:

To check the plain/text mimetype, type:

  $ curl -i --header 'accept: text/plain' http://localhost:18080/latest

and you should now see the text output of the handler.

To check the application/json mimetype, type:

  $ curl -i --header 'accept: application/json' http://localhost:18080/latest

There should now be json output to the screen.

Finally, in your browser go to http://localhost:18080/latest and you should see the entries in a list on the page.