noizZze

Ruby, Rails, Elixir and mobile development blog

Chat rooms app in Elixir in 15 minutes

This is the showcase post that touches a tiny part of what Elixir and the accompanying tooling can help you build in very little time.

The goal

In this post we are going to build a very simple chat app with multiple rooms. Anyone with the link will be able to connect and say something. We won't be storing the history of messages for the case of simplicity.

If you are lost, or just want to skip over some hoops, the source of the app we build here is available at Github.

Preparations

This walkthrough will require Elixir 1.0.2+ as a dependency for Phoenix Framework. Please install it before you start.

Get the latest Phoenix Framework.

$ git clone https://github.com/phoenixframework/phoenix.git && cd phoenix && git checkout v0.6.2 && mix do deps.get, compile
    

Create a new phoenix app. This creates a new app that is configured to use Phoenix Framework. You don't need the phoenix folder that was checked out of the repo in the previous step.

$ mix phoenix.new chatex ~/tmp/chatex
    

Compile and launch your app skeleton.

$ cd ~/tmp/chatex
    $ mix do deps.get, compile
    $ mix phoenix.start
    

You should see the root page at http://localhost:4000/ now.

Layouts and assets

When we created a Phoenix app, the mix task initialized directory structure for us:

  • config folder is for your config files
  • lib is for standalone library code
  • Static files go into the priv directory (much like public in Rails)
  • test folder has familiar to Rails developers structure for tests
  • web folder contains your web application code (much list app in Rails)

Throw in jquery-2.1.1.min.js and bootstrap.min.js (3.3.1) into priv/static/js and bootstrap.min.css into priv/static/css.

Update the layout web/templates/layout/application.html.eex to include our new assets and clean up a bit. Here's what I got:

<!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="description" content="">
        <meta name="author" content="">
    
        <title>Chatex</title>
        <link rel="stylesheet" href="/css/bootstrap.min.css">
      </head>
    
      <body>
        <div class="container">
          <%= @inner %>
        </div>
    
        <script type="text/javascript" src="/js/jquery-2.1.1.min.js"></script>
        <script type="text/javascript" src="/js/bootstrap.min.js"></script>
      </body>
    </html>
    

If you are familiar with Rails layouts, you should be at home with the notion of inserting generated pages inside the broader layout file. Here we have <%= @inner %> marking the place where the generated page content goes.

Routing

Open the web/router.ex file. What you see shouldn't be terribly hard to comprehend. You can read more on routes in officinal Routing guide. Let's just define three routes.

scope "/", Chatex do
      pipe_through :browser
      get "/",    RoomsController, :show
      get "/:id", RoomsController, :show
      post "/message/:room_id", MessagesController, :create, as: :new_message
    end
    

Controllers

Let's add Chatex.RoomsController:

defmodule Chatex.RoomsController do
      use Phoenix.Controller
    
      @default_room "Lobby"
    
      @doc "shows a specific room with room_id specified"
      def show(conn, %{ "id" => room_id }) do
        conn |> render_room room_id
      end
    
      @doc "shows a default room -- Lobby"
      def show(conn, _params) do
        conn |> render_room @default_room
      end
    
    
      # renders the room with given ID
      defp render_room(conn, room_id) do
        conn |> render :show, room_id: room_id
      end
    
    end
    

We add two show clauses -- one for the default room and one for the room with ID.

You can read more about controllers in the official controllers guide.

With that out of the way, let's move to views. We will get back to controllers when sending actual messages.

Views

Views are slightly different from what we've seen in Rails. They consist of two parts -- presenter module and templates. Read more on views in the official views guide and templates guide.

For now we'll create an empty view module web/views/rooms_view.ex:

defmodule Chatex.RoomsView do
      use Chatex.View
    end
    

And then create the template for the show action:

<div class="row">
      <div class="col-sm-4">
        <h2>Rooms</h2>
        <ul id="rooms">
          <li><a href="<%= Chatex.Router.Helpers.lobby_path(:show) %>">Lobby</a></li>
          <li><a href="<%= Chatex.Router.Helpers.room_path(:show, "Help") %>">Help</a></li>
        </ul>
      </div>
      <div class="col-sm-8">
        <h1><%= @room_id %></h1>
        <div id="chatbox"></div>
    
        <div id="chatline">
          <form class="form" action="<%= Chatex.Router.Helpers.new_message_path(:create, @room_id) %>">
            <input type="hidden" name="room_id" id="room_id" value="<%= @room_id %>">
            <div class="form-group">
              <div class="row">
                <div class="col-sm-4">
                  <input type="text" name="user" id="user" placholder="User name" class="form-control"/>
                </div>
                <div class="col-sm-7">
                  <input type="text" name="body" id="body" placholder="Message" class="form-control"/>
                </div>
                <div class="col-sm-1">
                  <input type="submit" class="btn btn-default" value="Say" />
                </div>
              </div>
            </div>
          </form>
        </div>
      </div>
    </div>
    

If you restart your app at this point, you should be able to change pages by clicking on the Lobby and Help in the sidebar.

Channels

We will need some interactivity in the app. Phoenix Framework comes with built-in WebSockets and PubSup support, which are super fun to work with.

First, we define the channel in the routes and mount it on the /ws path:

defmodule Chatex.Router do
      use Phoenix.Router
      use Phoenix.Router.Socket, mount: "/ws"
    
      channel "messages", Chatex.MessagesChannel
    
      # ...
    end
    

Second, let's create the web/channel/messages_channel.ex that will be talking to the WebSockets clients:

defmodule Chatex.MessagesChannel do
      use Phoenix.Channel
    
      @doc "Called when the user connects to the room."
      def join(socket, _room_id, _message) do
        { :ok, socket }
      end
    
      @doc "Called when the user disconnects."
      def leave(socket, _message) do
        socket
      end
    
    end
    

This is a bare bones channel that does literally nothing. It accepts connections to any rooms and does nothing when a user leaves.

On the client side, we need to create a JavaScript file priv/static/js/room.js and include it in the layout file along with /js/phoenix.js after jQuery and Bootstrap.

$(function() {
      // new message form submission handler
      var form = $("form");
      form.on("submit", function(e) {
        e.preventDefault();
    
        var url  = form.attr('action'),
            body = $("#body"),
            user = $("#user");
    
        $.post(url, { message: { user: user.val(), body: body.val() } }, function(data) {
          body.val('').focus();
        });
      });
    
      // connection to the channel
      var socket = new Phoenix.Socket("/ws");
      socket.join("messages", $("#room_id").val(), {}, function(channel) {
        channel.on("new:message", function(data) {
          var div = $("<div class='alert alert-info'></div>").text(data.user + " said: " + data.body);
          $("#chatbox").append(div);
        });
      });
    });
    

Here we listen for form submissions and then sending message[user] and message[body] to the MessagesController#create. We also connect to the room topic via WebSocket and listen for new messages to display.

Now let's create MessagesController that will broadcast our message to the members of the same room.

defmodule Chatex.MessagesController do
      use Phoenix.Controller
    
      plug :action
    
      @doc "Broadcasts a message to the members of the #room_id."
      def create(conn, %{ "room_id" => room_id, "message" => %{ "user" => user, "body" => body } }) do
        Phoenix.Channel.broadcast "messages", room_id, "new:message", %{ user: user, body: body }
        conn |> text "ok" 
      end
    
    end
    

The controller handles the POST request to create a message in a certain room by broadcasting it to all users in the "messages" channel listening to topic with the ID of the room.

Try restarting the app, opening several browsers windows (some in the same room, some in different) and test how messages are delivered.

Testing

In this demo app I completely ignored the testing aspect of development for the sake of time and space. Testing deserves a separate series of posts. It's vast territory.

Exercises to consider

  • Try using phoenix_haml instead of Eex
  • Think about how could you list the history of 10 last entered rooms in the sidebar instead of static links
  • Do you think you can create a separate channel that will send :new_room messages whenever someone enters a new room and then update the sidebar list of rooms dynamically?
  • How about treating rooms as hashtags (#lobby), and broadcast messages to listeners of all hashtags found in a message ("#lobby and #help users, hello!")?
  • Can you sanitize room id so that it allows only alphanumerics and dashes?

Conclusion

We haven't even scratched the surface of what's possible in Elixir and Phoenix Framework, but I hope you've got an idea of how simple it is to build interactive web apps with them.

The general idea is that it's not a request-response infrastructure, like Rails. Your application gears are always spinning. You can have long running processes with progress reported, lengthy calculations, real-time games, dynamic interfaces and much more. All of this sits on the base of reliable multi-core-ready foundation with supervisors, processes, self-recovery and native tools for distribution, scaling and monitoring.

Have fun! Any comments or questions are welcome.

Bare bones files watch

Sometimes I just need to follow the changes to files and act upon that. There are plenty of "watchers" out there, like Guard and Watchr, but if I really don't want to drag the bag of gems, here's what I do:

#!/bin/zsh
    
    typeset -a pids
    
    tidy()  
    {
        for pid in $pids
        do
            kill $pid
    
        done
    }
    
    trap tidy INT
    
    (fswatch -e '#' lib test web | xargs -I anything -R 1 -L 1 -t mix test)&
    pids=($!)
    
    (fswatch -0 src/coffee | xargs -0 -n 1 -I _ sh -c "cat src/coffee/* | coffee --compile --stdio > priv/static/js/application.js")&
    pids+=$!
    
    wait
    

The original is by Dave Thomas and was posted in an Elixir user group thread.

We define a function that will terminate our background tasks on INT signal, then start two watchers and add their PIDs to the list. Finally, we wait.

Now to the fswatch command syntax. (Mac OS X users can install this tool with brew install fswatch.) The tool is pretty handy. It watches for changes in the given file / directory and sends notifications. In the first example, we just watch for notifications in code directories and run mix test for each noticed change. In the second, we watch for changes in src/coffee and then run the CoffeeScript compiler along with concatenation of the files.

Easy, bare bones and plenty of opportunities. Explore.

YAML you may have never known

HTML and multiline strings

Storing HTML in YAML fields in a pretty way has never been as easy.

info: >  
      <h1>Page title</h1>
    
      <p>Some paragraph text.</p>
    

NOTE: You'll still need to tell Rails it's safe HTML.

Arrays and hashes

This is useful when you keep the list of options. For example, for the <select> tags.

options:  
      "yes": "Yes"
      "no":  "No"
      maybe: Maybe
    

NOTE: YAML interprets "yes" as true and "no" as false case-insensitively, that's why we need to put them in quotes

And this is how you'd use them in Rails:

= select_tag :field, options_for_select(I18n.t('options').invert)
    

NOTE: We need to invert the Hash so that keys and values land where Rails expects them.

If you don't care about key-value mapping, options can be stored as:

options: [ "Yes", "No", "Maybe" ]  
    

or

options:  
      - "Yes"
      - "No"
      - Maybe
    

NOTE: Quotes again.

Using this version in Rails is as easy, no inversion though:

= select_tag :field, options_for_select(I18n.t('options'))
    

My current Elixir toolkit

All my free time I'm spending on a my own project that I decided to write in Elixir, Rails and CoffeeScript. There is the service backend that runs heavily multi-threaded and so Elixir / Erlang was a natural choice.

I figure some of my friends might be interested in the tools I use currently, so here's the short rundown:

Release management - exrm

Fantastic tool for building releases ready for the deployment. Prepares a versioned nicely packed archive with all dependencies and start / stop / restart / remote_console scripts.

JSON handling - jsex

Sweet little library for encoding, decoding and jumping through all sorts of hoops with JSON.

Websockets - sockjs-erlang

Although there's great variety of web servers in Erlang Kingdom, I chose this one for my own needs for its simplicity. It sits on top of Cowboy and provides a very nice interface, so you focus on the task not on the Websocket handling chores.

UUID - uuid

Tiny library for all sorts of UUID generation.

HTTP client - httpoison

As with web servers, and basically everything else, there are plenty of options, but the one I stuck with is Httpoison. Being based on HTTPotion, it's an excellent piece of software that makes running HTTP requests async or not a breeze.

These are basically the essentials I spent time finding. Hope it saves a bit of time for some of you.

Completes and totals, or how to count

In every third project I see SQL like this:

SELECT
      (SELECT COUNT(*)
       FROM table 
       WHERE group_id = X AND completed = 1) as completed,
    
      (SELECT COUNT(*)
       FROM table
       WHERE group_id = X) as total
    
    FROM table
    WHERE group_id = X
    GROUP BY group_id
    

As you might understand the intention is to count all records in the group and those with completed flag set. This solution gives me creeps.

Here's how I would write the same:

SELECT
      COUNT(NULLIF(completed, 0)) as completed,
      COUNT(*) as total
    FROM table
    WHERE group_id = X
    

Now pick your DB book and go read what NULLIF function is, and do me a favor. No more crazy subselects, ok?

Appcelerator Titanium: Scaling and cropping images

At the time of writing Titanium is at: 3.3.0 GA

In the project I'm supporting we juggle with camera shots like crazy. One of the recent requests from the client was to show square thumbnails in the list of taken pictures. While the requirement itself is pretty reasonable, Titanium doesn't make our life easier.

It's easy to hack resizing with ImageView that is given the image and set to the known width / height. You then save it to the blob and off you go.

The harder it was to make it actually crop the image with unknown dimensions (thanks to device resolution diversity) to 96x96 thumbnail. Here's how I did it.

I've started with nesting image views inside other views, but that led nowhere. Finally I came across one old module -- ti.imagefactory -- stuck somewhere in the August of 2013 in its development. Even though it's quite old, it still has got everything we need -- resizing and cropping.

Here's how the end result looks:

// Target image dimensions
    var w = 96, h = 96;
    
    // Loading the module
    var IF = require('ti.imagefactory');
    
    // Getting file name like "UUID-<width>x<height>" and
    // matching it to grab the width / height
    var id       = photo.imageId,
        match    = /^.*-(\d+)x(\d+)$/.exec(id);
    
    // Reading the file from the storage
    var file     = FileStorage.getFile(id),
        img      = file.read(),
        finished = null;
    
    // We are storing image dimensions in the file name
    // since Ti doesn't recognize loaded Blobs as images
    // all too well.
    if (match != null) {
      var iw = parseInt(match[1]),
          ih = parseInt(match[2]),
          tw = iw < ih ? w : iw * h / ih,
          th = iw < ih ? ih * w / iw : h,
          resized = IF.imageAsResized(img, { width: tw, height: th });
    
      finished = IF.imageAsCropped(resized, { width: w, height: h });      
    } else {
      finished = img;
    }
    

Here, if we don't have image sizes in the file name or they are "0" for some reason, we just let the image stretch itself. Ugly, but yet better than nothing. Normally, we don't have this issue. It's just a safety net.

Another note, that imageAsCropped isn't given the x/y offset and so it will automatically gravitate towards the center of the image. Sweet.

I saw a ton of discussions on the subject and figured I'd share. Hope you enjoyed this bit.

Thoughts on present iOS development

Now the time for iOS has come and I'm putting together these notes basing on my recent experience with XCode 6 Beta, Beta 6 and Beta 7.

On to the great sides:

  • XCode 6. Great Interface Builder experience so far. Although it's terribly slow at times, it's been very handy. Live rendering of custom views is both super useful and confusing at times. I don't know what hardware do they use for demos at WWDC, but on my 2010 Macbook Pro it's not even close to real-time (takes 20-30 seconds to build and update the view). Still cool and faster than rebuilding the project for that little tweak.

  • Swift is brilliant. I thought moving from ObjC will bring pain and even wanted to postpone until I'm more or less at home with it. Glad that I've done otherwise with the latest project. Aside of a couple little hacks, experience has been smooth and pleasant so far.

  • UI preview feature. Handy to check the constraints and have a rough idea of what the view will look on various devices, but with its weak sides -- not totally correct rendering of table view cells, latency and some crazy behavior with messed up constraints that doesn't match to that of a real device.

Now to what's not so great:

  • Installation issues. First launch after the fresh install is confusing. Filed the issue.

  • Docs can be better. Although the built-in doc browser has been seriously improved, guides are still in read it all in one book format, and are very hard to navigate and fish out necessary info (unless you are reading it from cover to cover).

  • Unwind segues not recognized when working with Swift and need ugly ObjC workaround.

  • Swift rules change often (especially for unboxing). I moved during the week from Beta to Beta 6 to Beta 7, and with each move there was a bunch of warnings about unboxing rules. Glad it evolves, but man, it's annoying at times (especially when you have to update template code that was generated for you and you don't have a clue what it should be without digging in).

  • Interfacing from Swift with some legacy libs can be an adventure. You'll need bridging header files and hand-keeping them in sync when you update libs.

  • Debugger doesn't show any vars and fields when working with Swift. Error messages are hilarious and don't help at all. One day it says something about some Metal library it couldn't compile, the other -- about an unknown variable that is actually defined two lines above the breakpoint. All in all, you can't debug Swift code reliably and have to get back to Stone Age NSLog techniques and good deal of guesswork.

What are we still missing:

  • Automatic laying out screen space on soft keyboard appearance / disappearance. It's 2014, but we still need to handle this manually. Android has the feature either to pan the screen or to resize it, but Apple figured that's something we can do ourselves. In every project, on every screen with fields.

  • Fitting of content within multiline labels doesn't work. Single-line UILabel views are capable of shrinking text to fit text fragments that are too long for the allotted screen space. When you go multi-line, you lose that. Oh boy, there's nothing terribly complex with that, and why is it not there yet. (I hear it is in iOS 8. Backport would be nice, guys.)

Bottom line is that toolkit has gone long way to become really helpful and usable. There are still lots of weak spots and at times experience is frustrating (rebuilding complete project for 2 minutes after a one line change is one of them), but at least designing and building stuff for iOS and Mac OS X is now a pleasurable experience.

If you are still using Appcelerator Titanium, consider going native with your next project. Both Android and iOS platforms give all you need to work on your projects efficiently now.

We are living through exciting times, ladies and gents!

What I like about present Android development

Android Studio. Number one most important thing in whole Android development story is Android Studio built on top of IntelliJ IDEA engine. Excellent off the box Gradle support, intuitive source control, intelligent code completion, responsiveness of the UI, debugging -- all top notch. They even help you complete repetitive strings in JavaDoc.

Responsiveness. Very simple and super-flexible means to layout interfaces for any platform, and means to pick the right resources (strings, values, drawables etc) for each. If I am to give just one suggestion here, it will be -- forget the visual UI designer. Give manual XML editing a try. It is very easy and pays of million times with precision and clarity in interfaces.

Building blocks. Extensive collection of modules and classes makes development a real pleasure. Take a look at AsyncTask and IntentService. These are the real pleasure to work with. Database management tooks, and speech recognition / synthesis are super handy too.

Documentation. Detailed tutorials and reference pages. In this aspect Google is light years ahead of Apple. The way Android Tutorials are designed and written is absolutely fantastic. You get just the necessary zoom level into the technology. If you need more, you drill down, if you need less -- you get up the hierarchy. In contrast, Apple has traditional all-in-one-book PDF-like papers, which makes it a complete waste of time if you need just a tad for solving the present task.

Fragments. With the introduction of fragments the development for Android went to a totally new level. They are those "sub-activities" you can embed in your views, replace, and transit to and from. You can juggle with them and use to compose interfaces for different platforms and form factors (think master-detail laid out sequentially for handhelds and as a sidebar+main area for the tablets.)

There's plenty more. I've highlighted something that stuck in memory after the recent week-long project.

Constraints aware soft keyboard compensation on iOS

A while ago Apple introduced constraints-based layouts for Mac OS X and iOS user interface designs. It's a huge and logical step forward from pixel-perfect fully manual laying out of things. The line of devices grows and creating all possible variations of interfaces has become a seriously tedious task.

One piece still missing from the puzzle is the set of tools for proper compensation for the soft keyboard. (It's when the keyboard slides up from the bottom of the screen.) There's a good chance that on a non-scrollable page your text fields at the bottom of it will become hidden by the keyboard. At this point you are on your own.

Recently, I've got exactly same problem and fished for solutions. All I could find were several pieces of code with frame-based calculations. That's when you figure the height of the keyboard that's about to slide out and animate the portion of the screen up by the same amount. It all looks like this:

- (void)registerForKeyboardNotifications {
        [[NSNotificationCenter defaultCenter] addObserver:self
            selector:@selector(keyboardWillShow:)
            name:UIKeyboardWillShowNotification object:nil];
    
       [[NSNotificationCenter defaultCenter] addObserver:self
             selector:@selector(keyboardWillHide:)
             name:UIKeyboardWillHideNotification object:nil];
    }
    
    - (void)keyboardWillShow:(NSNotification*)aNotification {
        NSDictionary* info = [aNotification userInfo];
        CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
    
        CGRect aRect = self.view.frame;
        aRect.size.height -= kbSize.height;
    
        [UIView animateWithDuration: 0.3 animations: ^{
            self.view.frame = aRect
        })
    }
    
    - (void)keyboardWillHide:(NSNotification*)aNotification {
        NSDictionary* info = [aNotification userInfo];
        CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
    
        CGRect aRect = self.view.frame;
        aRect.size.height += kbSize.height;
    
        [UIView animateWithDuration: 0.3 animations: ^{
            self.view.frame = aRect
        })
    }
    

This approach work great until there's a view you want to also resize or move depending on what you do to your primary target. Say, you have a table above your data entry section that you want to shrink as the data entry section goes up when the keyboard slides out.

idea

And that's just the simplest example, believe me...

With modern constraints based layout, you tell the table view bottom edge to follow the entry section top edge and the entry section bottom edge to stick to the bottom of the container. Now to modify the frame-based functions we need to save the bottom edge constraint of the entry section into the outlet (here inputsSpaceConstraint). And then we can safely modify the keyboardWillShow and keyboardWillHide methods as follows.

- (void)keyboardWillShow:(NSNotification*)aNotification {
        NSDictionary* info = [aNotification userInfo];
        CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
    
        [self.view layoutIfNeeded];
        [UIView animateWithDuration: 0.3 animations: ^{
            self.inputsSpaceConstraint.constant = -kbSize.height;
            [self.view layoutIfNeeded];
        })
    }
    
    - (void)keyboardWillHide:(NSNotification*)aNotification {
        [self.view layoutIfNeeded];
        [UIView animateWithDuration: 0.3 animations: ^{
            self.inputsSpaceConstraint.constant = 0;
            [self.view layoutIfNeeded];
        })
    }
    

The idea here is to use the constant field of the constraint to shift the view by the given amount (the height of the keyboard), and then, when we hide the keyboard, return it back to zero (or whatever you are using).

One specific thing to notice is that you absolutely need to call layoutIfNeeded before and in the process of animation to see the animation itself, not just before-and-after. That's how Apple suggests it to be.