Skip to content

Commit

Permalink
[#5] – Refactor some modules to better project organization
Browse files Browse the repository at this point in the history
  • Loading branch information
cabol committed Feb 13, 2017
1 parent bd472b7 commit 5adca0f
Show file tree
Hide file tree
Showing 13 changed files with 283 additions and 140 deletions.
19 changes: 19 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
# The directory Mix will write compiled artifacts to.
/_build

# If you run "mix test --cover", coverage assets end up here.
/cover

# The directory Mix downloads your dependencies sources to.
/deps

# Where 3rd-party dependencies like ExDoc output generated docs.
/doc

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez

# Others
*.o
*.beam
*.plt
erl_crash.dump
.DS_Store
._*
86 changes: 58 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,37 +47,38 @@ Once into the Elixir console:

```elixir
# create a table with default options
> ExShards.new :mytab
iex> ExShards.new :mytab
:mytab

> ExShards.insert :mytab, [k1: 1, k2: 2, k3: 3]
iex> ExShards.insert :mytab, [k1: 1, k2: 2, k3: 3]
true

> for k <- [:k1, :k2, :k3] do
[{_, v}] = ExShards.lookup(:mytab, k)
v
end
iex> for k <- [:k1, :k2, :k3] do
...> [{_, v}] = ExShards.lookup(:mytab, k)
...> v
...> end
[1, 2, 3]

# let's query all values using select
# we need to require Ex2ms to build match specs
> require Ex2ms
> ms = Ex2ms.fun do {_, v} -> v end
iex> require Ex2ms
Ex2ms
iex> ms = Ex2ms.fun do {_, v} -> v end
[{{:_, :"$1"}, [], [:"$1"]}]
> ExShards.select :mytab, ms
iex> ExShards.select :mytab, ms
[1, 2, 3]

> ExShards.delete :mytab, :k3
iex> ExShards.delete :mytab, :k3
true
> ExShards.lookup :mytab, :k3
iex> ExShards.lookup :mytab, :k3
[]

# let's create another table
> ExShards.new :mytab2, [{:n_shards, 4}]
iex> ExShards.new :mytab2, [{:n_shards, 4}]
:mytab2

# start the observer so you can see how shards behaves
> :observer.start
iex> :observer.start
:ok
```

Expand All @@ -86,7 +87,36 @@ As you might have noticed, it's extremely easy, such as you were using **ETS** A

## Extended API

As you probably have noticed, most of the Elixir APIs are designed to be [Fluent](https://en.wikipedia.org/wiki/Fluent_interface),
they allow us to take advantage of the pipe operator, making the code more readable
and elegant of course.

Because `shards` implements the same `ets` API, most of the functions follows
the old-traditional Erlang-style, so it is not possible to pipe them. Here is
where the extended API comes in!

[ExShards.Ext](lib/ex_shards/ext.ex) is the module that implements the extended API,
and provides a fluent API with a set of nicer and fresh functions, based on the
`Elixir.Map` API. No more words, let's play a bit:

```elixir
iex> :t |> ExShards.new |> ExShards.set(a: 1, b: 2) |> ExShards.put(:c, 3) |> ExShards.update!(:a, &(&1 * 2))
:t

iex> for k <- [:a, :b, :c, :d], do: ExShards.get(:t, k)
[2, 2, 3, nil]

iex> :t |> ExShards.remove(:c) |> ExShards.fetch!(:c)
** (KeyError) key :c not found in: :t

iex> :t |> ExShards.drop([:a, :b, :x]) |> ExShards.put(:y, "new!") |> ExShards.keys
[:y]
```

`ExShards.Ext` is well documented, and you can find the documentation in the next links:

* [ExShards.Ext](https://hexdocs.pm/ex_shards/ExShards.Ext.html)
* [API Reference](https://hexdocs.pm/ex_shards/api-reference.html)

## Distributed ExShards

Expand Down Expand Up @@ -115,10 +145,10 @@ $ iex --sname c@localhost -S mix
**2.** Create a table with global scope (`scope: :g`) on each node and then join them.

```elixir
> ExShards.new :mytab, scope: :g, nodes: [:b@localhost, :c@localhost]
iex> ExShards.new :mytab, scope: :g, nodes: [:b@localhost, :c@localhost]
:mytab

> ExShards.get_nodes :mytab
iex> ExShards.get_nodes :mytab
[:a@localhost, :b@localhost, :c@localhost]
```

Expand All @@ -127,31 +157,31 @@ $ iex --sname c@localhost -S mix
From node `a`:

```elixir
> ExShards.insert :mytab, k1: 1, k2: 2
iex> ExShards.insert :mytab, k1: 1, k2: 2
true
```

From node `b`:

```elixir
> ExShards.insert :mytab, k3: 3, k4: 4
iex> ExShards.insert :mytab, k3: 3, k4: 4
true
```

From node `c`:

```elixir
> ExShards.insert :mytab, k5: 5, k6: 6
iex> ExShards.insert :mytab, k5: 5, k6: 6
true
```

Now, from any of previous nodes:

```elixir
> for k <- [:k1, :k2, :k3, :k4, :k5, :k6] do
[{_, v}] = ExShards.lookup(:mytab, k)
v
end
iex> for k <- [:k1, :k2, :k3, :k4, :k5, :k6] do
...> [{_, v}] = ExShards.lookup(:mytab, k)
...> v
...> end
[1, 2, 3, 4, 5, 6]
```

Expand All @@ -160,24 +190,24 @@ All nodes should return the same result.
Let's do some deletions, from any node:

```elixir
> ExShards.delete :mytab, :k6
iex> ExShards.delete :mytab, :k6
true
```

From any node:

```elixir
> ExShards.lookup :mytab, :k6
iex> ExShards.lookup :mytab, :k6
[]
```

Let's check again all:

```elixir
> for k <- [:k1, :k2, :k3, :k4, :k5] do
[{_, v}] = ExShards.lookup(:mytab, k)
v
end
iex> for k <- [:k1, :k2, :k3, :k4, :k5] do
...> [{_, v}] = ExShards.lookup(:mytab, k)
...> v
...> end
[1, 2, 3, 4, 5]
```

Expand Down
20 changes: 12 additions & 8 deletions lib/ex_shards.ex
Original file line number Diff line number Diff line change
Expand Up @@ -45,25 +45,26 @@ defmodule ExShards do
* [Shards](https://github.com/cabol/shards)
* [API Reference](http://cabol.github.io/shards)
* `ExShards.Ext` – Extended API
"""

use ExShards.API

@type tab :: atom
@type key :: term
@type value :: term
@type state :: ExShards.State.t
use ExShards.Construct

## API

construct :shards, exclude_all: [:start, :stop], exclude: [new: 2]
inject :shards, except: [:start, :stop, new: 2]

@doc false
def new(tab, opts \\ []), do: :shards.new(tab, opts)

## Extended API

@spec drop(tab, Enumerable.t) :: map
@type tab :: atom
@type key :: term
@type value :: term
@type state :: ExShards.State.t

@spec drop(tab, Enumerable.t) :: tab
def drop(tab, keys), do: call(tab, :drop, [tab, keys])

@spec fetch(tab, key) :: {:ok, value} | :error
Expand Down Expand Up @@ -102,6 +103,9 @@ defmodule ExShards do
@spec set(tab, tuple | [tuple]) :: tab
def set(tab, obj_or_objs), do: call(tab, :set, [tab, obj_or_objs])

@spec take_and_drop(tab, Enumerable.t) :: map
def take_and_drop(tab, keys), do: call(tab, :take_and_drop, [tab, keys])

@spec update(tab, key, value, (value -> value)) :: tab
def update(tab, key, initial, fun), do: call(tab, :update, [tab, key, initial, fun])

Expand Down
52 changes: 0 additions & 52 deletions lib/ex_shards/api.ex

This file was deleted.

84 changes: 84 additions & 0 deletions lib/ex_shards/construct.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
defmodule ExShards.Construct do
@moduledoc """
This is an utility module, which provides a set of macros to
inject functions from Erlang modules.
"""

@doc false
defmacro __using__(_opts) do
quote do
@before_compile unquote(__MODULE__)

import unquote(__MODULE__)
end
end

@doc false
defmacro __before_compile__(_env) do
quote do
@public_defs Module.definitions_in(__MODULE__, :def)

def definitions, do: @public_defs
end
end

@doc """
Injects exported functions from an Erlang module.
By default, it injects all functions from the given module:
inject :lists
Alternatively, it allows you to pass pairs of name/arities to `:except`
as a fine grained control on what to inject (or not):
inject :lists, except: [append: 1]
Besides, it allows you to pass only the name of the fuction(s) to `:except`,
in order to exclude all function that matches with that name – no matter
the arity. E.g.:
inject :lists, except: [:append]
This will exclude either `:lists.append/1` and `:lists.append/2`.
## Example
defmodule Utils do
use ExShards.Construct
# injects all exported functions in module `:lists`
inject :lists
# injects all exported functions in module `:maps` except:
# `:maps.get/2`, `:maps.get/3` and `:maps.find/2`.
inject :maps, except: [:get, find: 2]
end
"""
defmacro inject(mod, opts \\ []) do
# build public function list
public_defs = opts
|> Keyword.get(:except, [])
|> :lists.append([:module_info])
|> Enum.reduce(mod.module_info(:exports), fn
({k, v}, acc) -> Keyword.delete(acc, k, v)
(k, acc) -> Keyword.delete(acc, k)
end)

# generate definitions
for {fun, arity} <- public_defs do
args = if arity > 0 do
1..arity
|> Enum.map(&(Module.concat(["arg#{&1}"])))
|> Enum.map(fn(x) -> {x, [], __MODULE__} end)
else
[]
end
quote do
def unquote(fun)(unquote_splicing(args)) do
unquote(mod).unquote(fun)(unquote_splicing(args))
end
end
end
end
end
8 changes: 5 additions & 3 deletions lib/ex_shards/dist.ex
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,14 @@ defmodule ExShards.Dist do
* [shards_dist](https://github.com/cabol/shards/blob/master/src/shards_dist.erl)
* [API Reference](http://cabol.github.io/shards)
* `ExShards.Ext` – Extended API
"""

use ExShards.API
#use ExShards.API.Ext
use ExShards.Construct
#use ExShards.Ext

construct :shards_dist, exclude: [new: 2]
inject :shards_dist, except: [new: 2]

@doc false
def new(tab, opts \\ []), do: :shards_dist.new(tab, opts)
end
Loading

0 comments on commit 5adca0f

Please sign in to comment.