I wanted to capture the talk in a blog post and here it is.
1. Canada
Many of us have used cancan for authorization in our Rails applications. When
I was searching for a similar package in Elixir, I found the awesome canada
package.
defmoduleUserdo defstruct id:nil, name:nil, admin:false end
defmodulePostdo defstruct user_id:nil, content:nil end
defimplCanada.Can, for: User do defcan?(%User{id: user_id}, action, %Post{user_id: user_id}) when action in [:update, :read, :destroy, :touch], do:true
defcan?(%User{admin: admin}, action, _) when action in [:update, :read, :destroy, :touch], do: admin
defcan?(%User{}, :create, Post), do:true end
import Canada, only: [can?:2]
if some_user |> can? read(some_post) do else end
When using packages, I try to take a peek at the source code and
understand how things work. And, I was schocked when I saw just 10 lines of
code in the lib folder! See for yourself:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
defmoduleCanadado defmacrocan?(subject, {action, _, [argument]}) do quotedo Canada.Can.can? unquote(subject), unquote(action), unquote(argument) end end end
defprotocolCanada.Can do @doc"Evaluates permissions" defcan?(subject, action, resource) end
The protocol is what allows you to define your custom rules for authorization
and the Canada module defines a neat little macro which allows you to test if
a user is authorized to perform an action using syntax like: can? user,
read(post). How cool is that!
2. Readable binary match specs
Postgrex is another one of those packages which is filled with neat Elixir code.
When I was skimming through the code, I ran into a piece of code which surprised
me:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
defmodulePostgrex.BinaryUtils do @moduledocfalse
defmacroint64do quotedo: signed-64 end
defmacrouint16do quotedo: unsigned-16 end
end
I was having a difficult time understanding how signed-64 could be valid
Elixir code. I quickly spun up an iex console and typed in signed-64 and
unsurprisingly it threw an error. Upon further searching I found that this was
actually used in binary pattern matches all over the code:
1 2 3 4 5 6 7 8 9
defmodulePostgrex.Messages do import Postgrex.BinaryUtils defparse(<<type :: int32, rest :: binary>>, ?R, size) do
defparse(<<pid :: int32, key :: int32>>, ?K, _size) do end
So, the macro int32 would actually be spliced inside of a binary pattern
match. I would never have thought of doing this! And it makes the code so much
more readable and easy to follow.
3. Compiling lookup tables in Modules
While browsing through postgrex, I found a text file called errcodes.txt which
I thought was a bit strange. Here is a snippet of that file:
# # errcodes.txt # PostgreSQL error codes # # Copyright (c) 2003-2015, PostgreSQL Global Development Group
# ...
Section: Class 00 - Successful Completion
00000 S ERRCODE_SUCCESSFUL_COMPLETION successful_completion
Section: Class 01 - Warning
# do not use this class for failure conditions 01000 W ERRCODE_WARNING warning 0100C W ERRCODE_WARNING_DYNAMIC_RESULT_SETS_RETURNED dynamic_result_sets_returned 01008 W ERRCODE_WARNING_IMPLICIT_ZERO_BIT_PADDING implicit_zero_bit_padding 01003 W ERRCODE_WARNING_NULL_VALUE_ELIMINATED_IN_SET_FUNCTION null_value_eliminated_in_set_function 01007 W ERRCODE_WARNING_PRIVILEGE_NOT_GRANTED privilege_not_granted 01006 W ERRCODE_WARNING_PRIVILEGE_NOT_REVOKED privilege_not_revoked 01004 W ERRCODE_WARNING_STRING_DATA_RIGHT_TRUNCATION string_data_right_truncation 01P01 W ERRCODE_WARNING_DEPRECATED_FEATURE deprecated_feature
# ...
This file maps error codes to their symbols. The reason this was in the lib
folder was because it was supposed to be used as a source for error codes
mapping. Upon further reading I found that this was being used in a module
called Postgrex.ErrorCode. Here are the interesting pieces of that module:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
defmodulePostgrex.ErrorCode do @external_resource errcodes_path = Path.join(__DIR__, "errcodes.txt")
errcodes = for line <- File.stream!(errcodes_path),
for {code, errcodes} <- Enum.group_by(errcodes, &elem(&1, 0)) do [{^code, name}] = errcodes defcode_to_name(unquote(code)), do: unquote(name) end defcode_to_name(_), do:nil
end
This code file uses our errorcodes text file to define around 400 functions which
embed the actual code to name mapping. And whenever you wanted to do the actual lookup you could just use
Postgrex.ErrorCode.code_to_name(error_code)
4. Validating UUIDs
Did you know that you don’t need the uuid package to generate UUIDs? UUID
generation is available in Ecto as part of the Ecto.UUID module. And it even
has a function which allows you to validate a UUID. Most of us would quickly
reach for a regex pattern to validate a UUID, However, the Ecto library uses an
interesting approach:
This code is pretty self explanatory and is a literal translation of how you
would validate a UUID using a pen and paper.
5. Honorable Mentions
With Elixir you can assert that the argument your function receives is of a
specific type by using a pattern like below:
1 2 3 4 5
defmoduleUserdo defauthorized?(%User{} = user) do end end
This code would blow up if the argument passed was not a User struct. This is
a nice way of asserting the type. However, you can overdo this by using it
everywhere. A good rule of thumb is to use this pattern in your public API at
the periphery where data comes in.
Tagged with blocks
You can wrap your with matches in tagged tuples like below if you want to
handle errors differently for different failures.