My talk at ElixirConf 2014 touched on how the Sauspiel game server - while written in Erlang - uses mix
for dependency management and for compliation. I was asked in #elixir-lang to elaborate on that.
So without further ado here’s how you too can get rid of rebar
for compiling your Erlang apps and their dependencies (though we still use it for building the release):
We’re gonna be building an umbrella project (which in rebar
-speak just means you’ve got sub_dirs
), so we need to put a mix.exs
both in the project’s root and in each apps/*
sub directory.
To DRY up the configuration we’ve placed those parts of the app configuration that are the same in every app into the root mix.exs
and read it from there.
We’ll need a custom mix
task to copy the .app.src
file to the ebin
directory.
When building for testing (which we’ve -IFDEF
-ed away) we want to turn the TEST
define on.
Here’s the root mix.exs
:
defmodule Sauspiel.Mixfile do
use Mix.Project
def project do
[elixir: "0.14.3",
apps_path: "apps",
compile_path: ".",
default_task: "compile",
deps: deps]
end
def subproject_defaults do
Dict.merge [
elixir: project[:elixir],
compilers: [:copyapp, :erlang],
default_task: "compile",
erlc_options: erlc_options,
deps: deps,
deps_path: "../../deps",
lockfile: "../../mix.lock",
compile_path: ".",
], subproject_defaults(Mix.env)
end
defp subproject_defaults(:test), do: [
# Compile tests and -IFDEF'ed parts of source files
erlc_paths: ["src", "test"],
erlc_options: [{:d, :TEST}] ++ erlc_options
]
defp subproject_defaults(_), do: []
defp erlc_options do
# Using Mix.Project.compile_path here raises an exception,
# use Mix.Project.config[:compile_path] instead.
root_path = Path.expand("../..", Mix.Project.config[:compile_path])
includes = Path.wildcard(Path.join(root_path, "apps/*/include"))
[:debug_info] ++ Enum.map(includes, fn(path) -> {:i, path} end)
end
defp, deps do: [] # you know what goes here
end
defmodule Mix.Tasks.Compile.Copyapp do
use Mix.Task
def run(_) do
project = Mix.Project.config
source_paths = project[:erlc_paths]
compile_path = Mix.Project.compile_path
files = Mix.Utils.extract_files(source_paths, "*.app.src")
File.mkdir_p!(compile_path)
Enum.each(files, fn(app_src_file) ->
app_file = Path.join(compile_path, Path.basename(app_src_file, ".src"))
File.copy!(app_src_file, app_file)
end)
end
end
and one of the mix.exs
in the apps/*
sub directories:
Code.require_file "mix.exs", "../.."
defmodule Sauspiel.Cardgame.Mixfile do
use Mix.Project
def project do
Dict.merge(Sauspiel.Mixfile.subproject_defaults, [
app: :cardgame,
deps: [ {:storage, in_umbrella: true} ],
])
end
end
Bonus: Turning mix into an escript
This is actually really easy. Right now you have to check out a pull request, but I hope that it gets merged soon.
After that just cd lib/mix
and mix escript.build
Voila!
28 Jul 2014
Rails 4.0 has been out now for a while. It’s brought some great new features but as always there have been a few bugs too.
And two of them that have been annoying me especially much will be fixed in Rails 4.1. Hooray! \o/
1. has_one associations will stop always using ORDER BY id
Best to illustrate this with an example (taken from the corresponding rails/rails#12623
issue)
class User < ActiveRecord::Base
has_one :profile
end
class Profile < ActiveRecord::Base
belongs_to :user
end
In Rails 3.2 some_user.profile
executed this database statement
SELECT * FROM profiles WHERE profiles.user_id = ? LIMIT 1
but starting with Rails 4 first
always used a default ORDER BY
(rails/rails#5153)
because good databases (PostgreSQL :) ) will optimize a query without an explicit order, which means calling User.first
may return different users every time, while User.order(:id).first
may not.
(FYI Rails 4.0 introduced take
which doesn’t use a default order. Most of the time you probably want to write User.where(...).take
instead of User.where(...).first
)
Because of that in Rails 4.0 some_user.profile
executed this database statement:
-- Rails 4
SELECT * FROM profiles WHERE profiles.user_id = ? ORDER BY profiles.id ASC LIMIT 1
You would think, why does it matter if I send the ORDER BY
or not, the result set has only one row anyhow.
But if the stars are just right PostgreSQL will decide to use a different (slower) index depending on whether
the ORDER BY
is there or not. And yes I did run into that.
Marko Tiikkaja explained PostgreSQL’s thought process like this:
hey, this user_id is quite common, I bet if I just start scanning in id order, I’ll find one row quickly
Rails 4.1 (and the upcoming Rails 4.0.3) will go back to the old behavior.
-- Rails 4.1
SELECT * FROM profiles WHERE profiles.user_id = ? LIMIT 1

2. associations can be unscoped
This one is rails/rails#10643. Clark Giorgos posted a nice demo:
class Product
default_scope deleted_at: nil
end
class OrderItem
belongs_to :product
belongs_to :unscoped_product, -> { unscoped }, foreign_key: :product_id, class_name: "Product"
end
OrderItem.joins(:unscoped_product).group(:product_id).count
leads to Rails executing this SQL:
SELECT COUNT(*) AS count_all, product_id AS product_id
FROM "order_items"
INNER JOIN "products" ON "products"."id" = "order_items"."product_id"
AND "products"."deleted_at" IS NULL -- This should NOT be here
GROUP BY product_id
And starting with Rails 4.1 this will be fixed. \o/
Getting rid of a default_scope
in an association is going to be very useful. Some people think that
default_scope is EVIL and being able to unscope
an
association is going to go some way towards disproving that statement.
22 Jan 2014
UPDATE: Since I wrote this Erlang Solutions has shipped a new 32-bit OS X build that works with wx out of the box.
Erlang ships with both wx and tk GUI apps, but the tk ones are deprecated and will be removed soon.
Unfortunately neither the Erlang OS X build from Erlang Solutions nor the one from Homebrew nor the default kerl build support wx on OS X, they all fall back to tk.
I spent an afternoon figuring out how to get Erlang on OS X working with wx and this is the result:

And here’s what to do:
I’m going to use homebrew to install wxmac, but the wxmac recipe needs patching before it will work with Erlang. You can either manually apply the changes from my commit or just run
brew install https://raw.github.com/MSch/homebrew/master/Library/Formula/wxmac.rb --disable-monolithic
Once that’s done, I’ll use kerl to install Erlang. You can get that easily via homebrew too:
Via trial and error I found out that these configure options work. Just put the following in your ~/.kerlrc
KERL_CONFIGURE_OPTIONS="--disable-debug --without-javac --enable-shared-zlib --enable-dynamic-ssl-lib --enable-hipe --enable-smp-support --enable-threads --enable-kernel-poll --with-wx"
Build Erlang.
CPPFLAGS="-arch i386" kerl build R16B02 R16B02.x86.wx
The CPPFLAGS
is necessary, otherwise (e.g. with --enable-darwin-universal
) it didn’t work for me. And yes, I’m building a 32-bit Erlang, I didn’t manage to get 64-bit to work, it always crashed when starting wx.
Since kerl makes it easy to have multiple Erlang installs on one machine only using the 32-bit one for the wx GUI tools and connecting it to another local node should be an options if you want 64-bit Erlang on your dev machine.
Install and activate Erlang
sudo kerl install R16B02.x86.wx /opt/erlang/R16B02.x86.wx/
. /opt/erlang/R16B02.x86.wx/activate
Enjoy your new wx-enabled Erlang
5 Oct 2013