Skip to content
This repository has been archived by the owner on Aug 28, 2023. It is now read-only.

Commit

Permalink
Start using implementors supervision tree (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
maennchen committed Dec 2, 2019
1 parent 9627735 commit 6a9064c
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 65 deletions.
34 changes: 31 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,35 @@ The package can be installed by adding `currency_conversion` to your list of dep

```elixir
def deps do
[{:currency_conversion, "~> 0.3.1"}]
[
{:currency_conversion, "~> 0.3"}
]
end
```

## Setup

`CurrencyConversion` is a wrapper around the currency conversion. We can define an
implementation as follows:

```elixir
defmodule MyApp.CurrencyConversion do
use CurrencyConversion, otp_app: :my_app
end
```

If your application was generated with a supervisor (by passing `--sup` to `mix new`)
you will have a `lib/my_app/application.ex` file containing the application start
callback that defines and starts your supervisor. You just need to edit the `start/2`
function to start the converter as a supervisor on your application's supervisor:

```elixir
def start(_type, _args) do
children = [
{MyApp.CurrencyConversion, []}
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
```

Expand All @@ -40,7 +68,7 @@ end
* Example: `{:EUR, %{CHF: 7.0}}`

```elixir
config :currency_conversion,
config :my_app, MyApp.CurrencyConversion,
source: CurrencyConversion.Source.Fixer,
source_api_key: "FIXER_ACCESS_KEY",
# defaults to http since free access key only supports http
Expand All @@ -59,7 +87,7 @@ It only has to implement the function `load/0`, which produces a struct of type
To prevent HTTP calls in the Tests, configure the Test Source. (See the configuration `test_rates` for custom test rates.)

```elixir
config :currency_conversion,
config :my_app, MyApp.CurrencyConversion,
source: CurrencyConversion.Source.Test,
refresh_interval: 86_400_000
```
Expand Down
139 changes: 104 additions & 35 deletions lib/currency_conversion.ex
Original file line number Diff line number Diff line change
@@ -1,39 +1,118 @@
defmodule CurrencyConversion do
@moduledoc """
Module to Convert Currencies.
`CurrencyConversion` is a wrapper around the currency conversion. We can define an
implementation as follows:
defmodule MyApp.CurrencyConversion do
use CurrencyConversion, otp_app: :my_app
end
Where the configuration for the Converter must be in your application environment,
usually defined in your `config/config.exs`:
config :my_app, MyApp.CurrencyConversion,
source: MyApp.CurrencyConversion.Source.CustomSource
If your application was generated with a supervisor (by passing `--sup` to `mix new`)
you will have a `lib/my_app/application.ex` file containing the application start
callback that defines and starts your supervisor. You just need to edit the `start/2`
function to start the converter as a supervisor on your application's supervisor:
def start(_type, _args) do
children = [
{MyApp.CurrencyConversion, []}
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
"""

alias CurrencyConversion.Rates
alias CurrencyConversion.UpdateWorker
@callback convert(amount :: Money.t(), to_currency :: atom) :: Money.t()
@callback get_currencies :: [atom]

@doc """
Convert from currency A to B.
defmacro __using__(opts) do
otp_app = Keyword.fetch!(opts, :otp_app)

### Example
quote do
@moduledoc """
Module to Convert Currencies for `#{unquote(otp_app)}`.
"""

iex> CurrencyConversion.convert(Money.new(7_00, :CHF), :USD, %CurrencyConversion.Rates{base: :EUR,
...> rates: %{CHF: 0.5, USD: 0.75}})
%Money{amount: 10_50, currency: :USD}
@behaviour unquote(__MODULE__)

iex> CurrencyConversion.convert(Money.new(7_00, :EUR), :USD, %CurrencyConversion.Rates{base: :EUR,
...> rates: %{CHF: 0.5, USD: 0.75}})
%Money{amount: 5_25, currency: :USD}
@update_worker Module.concat(__MODULE__, UpdateWorker)
@otp_app unquote(otp_app)

iex> CurrencyConversion.convert(Money.new(7_00, :CHF), :EUR, %CurrencyConversion.Rates{base: :EUR,
...> rates: %{CHF: 0.5, USD: 0.75}})
%Money{amount: 14_00, currency: :EUR}
alias CurrencyConversion.UpdateWorker

iex> CurrencyConversion.convert(Money.new(0, :CHF), :EUR, %CurrencyConversion.Rates{base: :EUR,
...> rates: %{CHF: 0.5, USD: 0.75}})
%Money{amount: 0, currency: :EUR}
use Supervisor

iex> CurrencyConversion.convert(Money.new(7_20, :CHF), :CHF, %CurrencyConversion.Rates{base: :EUR,
...> rates: %{CHF: 0.5, USD: 0.75}})
%Money{amount: 7_20, currency: :CHF}
@doc false
def start_link(_opts) do
Supervisor.start_link(__MODULE__, nil, name: __MODULE__)
end

"""
@spec convert(Money.t(), atom, Rates.t()) :: Money.t()
def convert(amount, to_currency, rates \\ UpdateWorker.get_rates())
@doc false
def child_spec(init_arg) do
super(init_arg)
end

@impl Supervisor
def init(_init_arg) do
config = Application.get_env(@otp_app, __MODULE__)

Supervisor.init([{UpdateWorker, config ++ [name: @update_worker]}], strategy: :one_for_one)
end

@doc """
Convert from currency A to B.
### Example
iex> #{__MODULE__}.convert(Money.new(7_00, :CHF), :USD)
%Money{amount: 10_50, currency: :USD}
iex> #{__MODULE__}.convert(Money.new(7_00, :EUR), :USD)
%Money{amount: 5_25, currency: :USD}
iex> #{__MODULE__}.convert(Money.new(7_00, :CHF), :EUR)
%Money{amount: 14_00, currency: :EUR}
iex> #{__MODULE__}.convert(Money.new(0, :CHF), :EUR)
%Money{amount: 0, currency: :EUR}
iex> #{__MODULE__}.convert(Money.new(7_20, :CHF), :CHF)
%Money{amount: 7_20, currency: :CHF}
"""
@impl unquote(__MODULE__)
def convert(amount, to_currency) do
unquote(__MODULE__).convert(amount, to_currency, UpdateWorker.get_rates(@update_worker))
end

@doc """
Get all currencies
### Examples
iex> CurrencyConversion.get_currencies()
[:EUR, :CHF, :USD]
"""
@impl unquote(__MODULE__)
def get_currencies do
unquote(__MODULE__).get_currencies(UpdateWorker.get_rates(@update_worker))
end
end
end

alias CurrencyConversion.Rates

@doc false
@spec convert(amount :: Money.t(), to_currency :: atom, rates :: Rates.t()) :: Money.t()
def convert(%Money{amount: 0}, to_currency, _), do: Money.new(0, to_currency)
def convert(amount = %Money{currency: currency}, currency, _), do: amount

Expand All @@ -55,17 +134,7 @@ defmodule CurrencyConversion do
convert(convert(amount, rates.base, rates), to_currency, rates)
end

@doc """
Get all currencies
### Examples
iex> CurrencyConversion.get_currencies(%CurrencyConversion.Rates{base: :EUR,
...> rates: %{CHF: 0.5, USD: 0.75}})
[:EUR, :CHF, :USD]
"""
@spec get_currencies(Rates.t()) :: [atom]
def get_currencies(rates \\ UpdateWorker.get_rates())
@doc false
@spec get_currencies(rates :: Rates.t()) :: [atom]
def get_currencies(%Rates{base: base, rates: rates}), do: [base | Map.keys(rates)]
end
20 changes: 0 additions & 20 deletions lib/currency_conversion/application.ex

This file was deleted.

5 changes: 3 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
defmodule CurrencyConversion.Mixfile do
@moduledoc false

use Mix.Project

@version "0.3.4"
Expand Down Expand Up @@ -26,8 +28,7 @@ defmodule CurrencyConversion.Mixfile do

def application do
[
extra_applications: [:logger],
mod: {CurrencyConversion.Application, []}
extra_applications: [:logger]
]
end

Expand Down
2 changes: 2 additions & 0 deletions test/currency_conversion/update_worker_test.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
defmodule CurrencyConversion.UpdateWorkerTest do
@moduledoc false

use ExUnit.Case, async: true

alias CurrencyConversion.UpdateWorker
Expand Down
17 changes: 12 additions & 5 deletions test/currency_conversion_test.exs
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
defmodule CurrencyConversionTest do
@moduledoc false

use ExUnit.Case, async: false
use ExUnit.Case, async: true

doctest CurrencyConversion

defmodule Converter do
@moduledoc false

use CurrencyConversion, otp_app: :currency_conversion
end

setup_all do
start_supervised!({CurrencyConversion.UpdateWorker, source: CurrencyConversion.Source.Test})
Application.put_env(:currency_conversion, Converter, source: CurrencyConversion.Source.Test)
start_supervised!(Converter)
:ok
end

doctest CurrencyConversion

describe "get_currencies/0" do
test "fetches all currencies" do
assert CurrencyConversion.get_currencies() == [
assert Converter.get_currencies() == [
:EUR,
:AUD,
:BGN,
Expand Down

0 comments on commit 6a9064c

Please sign in to comment.