diff --git a/chapter7/persistable_todo_cache/README.md b/chapter7/persistable_todo_cache/README.md new file mode 100644 index 0000000..6b31c9c --- /dev/null +++ b/chapter7/persistable_todo_cache/README.md @@ -0,0 +1,21 @@ +# Todo + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `todo` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:todo, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at [https://hexdocs.pm/todo](https://hexdocs.pm/todo). + diff --git a/chapter7/persistable_todo_cache/_build/dev/lib/todo/.mix/compile.elixir b/chapter7/persistable_todo_cache/_build/dev/lib/todo/.mix/compile.elixir new file mode 100644 index 0000000..916882c Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/dev/lib/todo/.mix/compile.elixir differ diff --git a/chapter7/persistable_todo_cache/_build/dev/lib/todo/.mix/compile.elixir_scm b/chapter7/persistable_todo_cache/_build/dev/lib/todo/.mix/compile.elixir_scm new file mode 100644 index 0000000..f129754 Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/dev/lib/todo/.mix/compile.elixir_scm differ diff --git a/chapter7/persistable_todo_cache/_build/dev/lib/todo/.mix/compile.protocols b/chapter7/persistable_todo_cache/_build/dev/lib/todo/.mix/compile.protocols new file mode 100644 index 0000000..51d52fc Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/dev/lib/todo/.mix/compile.protocols differ diff --git a/chapter7/persistable_todo_cache/_build/dev/lib/todo/consolidated/Elixir.Collectable.beam b/chapter7/persistable_todo_cache/_build/dev/lib/todo/consolidated/Elixir.Collectable.beam new file mode 100644 index 0000000..5c7d2d6 Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/dev/lib/todo/consolidated/Elixir.Collectable.beam differ diff --git a/chapter7/persistable_todo_cache/_build/dev/lib/todo/consolidated/Elixir.Enumerable.beam b/chapter7/persistable_todo_cache/_build/dev/lib/todo/consolidated/Elixir.Enumerable.beam new file mode 100644 index 0000000..ac7f27d Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/dev/lib/todo/consolidated/Elixir.Enumerable.beam differ diff --git a/chapter7/persistable_todo_cache/_build/dev/lib/todo/consolidated/Elixir.IEx.Info.beam b/chapter7/persistable_todo_cache/_build/dev/lib/todo/consolidated/Elixir.IEx.Info.beam new file mode 100644 index 0000000..c777470 Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/dev/lib/todo/consolidated/Elixir.IEx.Info.beam differ diff --git a/chapter7/persistable_todo_cache/_build/dev/lib/todo/consolidated/Elixir.Inspect.beam b/chapter7/persistable_todo_cache/_build/dev/lib/todo/consolidated/Elixir.Inspect.beam new file mode 100644 index 0000000..63b17f2 Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/dev/lib/todo/consolidated/Elixir.Inspect.beam differ diff --git a/chapter7/persistable_todo_cache/_build/dev/lib/todo/consolidated/Elixir.List.Chars.beam b/chapter7/persistable_todo_cache/_build/dev/lib/todo/consolidated/Elixir.List.Chars.beam new file mode 100644 index 0000000..d4e8fb6 Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/dev/lib/todo/consolidated/Elixir.List.Chars.beam differ diff --git a/chapter7/persistable_todo_cache/_build/dev/lib/todo/consolidated/Elixir.String.Chars.beam b/chapter7/persistable_todo_cache/_build/dev/lib/todo/consolidated/Elixir.String.Chars.beam new file mode 100644 index 0000000..84c95b8 Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/dev/lib/todo/consolidated/Elixir.String.Chars.beam differ diff --git a/chapter7/persistable_todo_cache/_build/dev/lib/todo/ebin/Elixir.Todo.Cache.beam b/chapter7/persistable_todo_cache/_build/dev/lib/todo/ebin/Elixir.Todo.Cache.beam new file mode 100644 index 0000000..3a94877 Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/dev/lib/todo/ebin/Elixir.Todo.Cache.beam differ diff --git a/chapter7/persistable_todo_cache/_build/dev/lib/todo/ebin/Elixir.Todo.Database.beam b/chapter7/persistable_todo_cache/_build/dev/lib/todo/ebin/Elixir.Todo.Database.beam new file mode 100644 index 0000000..3a3758e Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/dev/lib/todo/ebin/Elixir.Todo.Database.beam differ diff --git a/chapter7/persistable_todo_cache/_build/dev/lib/todo/ebin/Elixir.Todo.List.beam b/chapter7/persistable_todo_cache/_build/dev/lib/todo/ebin/Elixir.Todo.List.beam new file mode 100644 index 0000000..d85d8ab Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/dev/lib/todo/ebin/Elixir.Todo.List.beam differ diff --git a/chapter7/persistable_todo_cache/_build/dev/lib/todo/ebin/Elixir.Todo.Server.beam b/chapter7/persistable_todo_cache/_build/dev/lib/todo/ebin/Elixir.Todo.Server.beam new file mode 100644 index 0000000..179f881 Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/dev/lib/todo/ebin/Elixir.Todo.Server.beam differ diff --git a/chapter7/persistable_todo_cache/_build/dev/lib/todo/ebin/todo.app b/chapter7/persistable_todo_cache/_build/dev/lib/todo/ebin/todo.app new file mode 100644 index 0000000..179a83b --- /dev/null +++ b/chapter7/persistable_todo_cache/_build/dev/lib/todo/ebin/todo.app @@ -0,0 +1,7 @@ +{application,todo, + [{applications,[kernel,stdlib,elixir,logger]}, + {description,"todo"}, + {modules,['Elixir.Todo.Cache','Elixir.Todo.Database', + 'Elixir.Todo.List','Elixir.Todo.Server']}, + {registered,[]}, + {vsn,"0.1.0"}]}. diff --git a/chapter7/persistable_todo_cache/_build/test/lib/todo/.mix/.mix_test_failures b/chapter7/persistable_todo_cache/_build/test/lib/todo/.mix/.mix_test_failures new file mode 100644 index 0000000..002e9d7 Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/test/lib/todo/.mix/.mix_test_failures differ diff --git a/chapter7/persistable_todo_cache/_build/test/lib/todo/.mix/compile.elixir b/chapter7/persistable_todo_cache/_build/test/lib/todo/.mix/compile.elixir new file mode 100644 index 0000000..a9665eb Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/test/lib/todo/.mix/compile.elixir differ diff --git a/chapter7/persistable_todo_cache/_build/test/lib/todo/.mix/compile.elixir_scm b/chapter7/persistable_todo_cache/_build/test/lib/todo/.mix/compile.elixir_scm new file mode 100644 index 0000000..f129754 Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/test/lib/todo/.mix/compile.elixir_scm differ diff --git a/chapter7/persistable_todo_cache/_build/test/lib/todo/.mix/compile.protocols b/chapter7/persistable_todo_cache/_build/test/lib/todo/.mix/compile.protocols new file mode 100644 index 0000000..51d52fc Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/test/lib/todo/.mix/compile.protocols differ diff --git a/chapter7/persistable_todo_cache/_build/test/lib/todo/consolidated/Elixir.Collectable.beam b/chapter7/persistable_todo_cache/_build/test/lib/todo/consolidated/Elixir.Collectable.beam new file mode 100644 index 0000000..5c7d2d6 Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/test/lib/todo/consolidated/Elixir.Collectable.beam differ diff --git a/chapter7/persistable_todo_cache/_build/test/lib/todo/consolidated/Elixir.Enumerable.beam b/chapter7/persistable_todo_cache/_build/test/lib/todo/consolidated/Elixir.Enumerable.beam new file mode 100644 index 0000000..ac7f27d Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/test/lib/todo/consolidated/Elixir.Enumerable.beam differ diff --git a/chapter7/persistable_todo_cache/_build/test/lib/todo/consolidated/Elixir.IEx.Info.beam b/chapter7/persistable_todo_cache/_build/test/lib/todo/consolidated/Elixir.IEx.Info.beam new file mode 100644 index 0000000..c777470 Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/test/lib/todo/consolidated/Elixir.IEx.Info.beam differ diff --git a/chapter7/persistable_todo_cache/_build/test/lib/todo/consolidated/Elixir.Inspect.beam b/chapter7/persistable_todo_cache/_build/test/lib/todo/consolidated/Elixir.Inspect.beam new file mode 100644 index 0000000..63b17f2 Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/test/lib/todo/consolidated/Elixir.Inspect.beam differ diff --git a/chapter7/persistable_todo_cache/_build/test/lib/todo/consolidated/Elixir.List.Chars.beam b/chapter7/persistable_todo_cache/_build/test/lib/todo/consolidated/Elixir.List.Chars.beam new file mode 100644 index 0000000..d4e8fb6 Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/test/lib/todo/consolidated/Elixir.List.Chars.beam differ diff --git a/chapter7/persistable_todo_cache/_build/test/lib/todo/consolidated/Elixir.String.Chars.beam b/chapter7/persistable_todo_cache/_build/test/lib/todo/consolidated/Elixir.String.Chars.beam new file mode 100644 index 0000000..84c95b8 Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/test/lib/todo/consolidated/Elixir.String.Chars.beam differ diff --git a/chapter7/persistable_todo_cache/_build/test/lib/todo/ebin/Elixir.Todo.Cache.beam b/chapter7/persistable_todo_cache/_build/test/lib/todo/ebin/Elixir.Todo.Cache.beam new file mode 100644 index 0000000..7dfbe6c Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/test/lib/todo/ebin/Elixir.Todo.Cache.beam differ diff --git a/chapter7/persistable_todo_cache/_build/test/lib/todo/ebin/Elixir.Todo.List.beam b/chapter7/persistable_todo_cache/_build/test/lib/todo/ebin/Elixir.Todo.List.beam new file mode 100644 index 0000000..6be8437 Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/test/lib/todo/ebin/Elixir.Todo.List.beam differ diff --git a/chapter7/persistable_todo_cache/_build/test/lib/todo/ebin/Elixir.Todo.Server.beam b/chapter7/persistable_todo_cache/_build/test/lib/todo/ebin/Elixir.Todo.Server.beam new file mode 100644 index 0000000..8205ea2 Binary files /dev/null and b/chapter7/persistable_todo_cache/_build/test/lib/todo/ebin/Elixir.Todo.Server.beam differ diff --git a/chapter7/persistable_todo_cache/_build/test/lib/todo/ebin/todo.app b/chapter7/persistable_todo_cache/_build/test/lib/todo/ebin/todo.app new file mode 100644 index 0000000..5e22f56 --- /dev/null +++ b/chapter7/persistable_todo_cache/_build/test/lib/todo/ebin/todo.app @@ -0,0 +1,7 @@ +{application,todo, + [{applications,[kernel,stdlib,elixir,logger]}, + {description,"todo"}, + {modules,['Elixir.Todo.Cache','Elixir.Todo.List', + 'Elixir.Todo.Server']}, + {registered,[]}, + {vsn,"0.1.0"}]}. diff --git a/chapter7/persistable_todo_cache/lib/todo/cache.ex b/chapter7/persistable_todo_cache/lib/todo/cache.ex new file mode 100644 index 0000000..90b64e6 --- /dev/null +++ b/chapter7/persistable_todo_cache/lib/todo/cache.ex @@ -0,0 +1,34 @@ +defmodule Todo.Cache do + use GenServer + + def start() do + GenServer.start(__MODULE__, nil) + end + + def server_process(cache_pid, todo_list_name) do + GenServer.call(cache_pid, {:server_process, todo_list_name}) + end + + @impl true + def init(_) do + Todo.Database.start() + {:ok, %{}} + end + + @impl true + def handle_call({:server_process, todo_list_name}, _, todo_servers) do + case Map.fetch(todo_servers, todo_list_name) do + {:ok, todo_server} -> + {:reply, todo_server, todo_servers} + + :error -> + {:ok, new_todo_server} = Todo.Server.start(todo_list_name) + + { + :reply, + new_todo_server, + Map.put(todo_servers, todo_list_name, new_todo_server) + } + end + end +end diff --git a/chapter7/persistable_todo_cache/lib/todo/database.ex b/chapter7/persistable_todo_cache/lib/todo/database.ex new file mode 100644 index 0000000..89b30db --- /dev/null +++ b/chapter7/persistable_todo_cache/lib/todo/database.ex @@ -0,0 +1,47 @@ +defmodule Todo.Database do + use GenServer + + @db_folder "./persist" + + def start do + GenServer.start(__MODULE__, nil, name: __MODULE__) + end + + def store(key, data) do + GenServer.cast(__MODULE__, {:store, key, data}) + end + + def get(key) do + GenServer.call(__MODULE__, {:get, key}) + end + + @impl true + def init(_) do + File.mkdir_p!(@db_folder) + {:ok, nil} + end + + @impl true + def handle_cast({:store, key, data}, state) do + key + |> file_name() + |> File.write!(:erlang.term_to_binary(data)) + + {:noreply, state} + end + + @impl true + def handle_call({:get, key}, _, state) do + data = + case File.read(file_name(key)) do + {:ok, contents} -> :erlang.binary_to_term(contents) + _ -> nil + end + + {:reply, data, state} + end + + def file_name(key) do + Path.join(@db_folder, to_string(key)) + end +end diff --git a/chapter7/persistable_todo_cache/lib/todo/list.ex b/chapter7/persistable_todo_cache/lib/todo/list.ex new file mode 100644 index 0000000..9e1ed06 --- /dev/null +++ b/chapter7/persistable_todo_cache/lib/todo/list.ex @@ -0,0 +1,57 @@ +defmodule Todo.List do + defstruct auto_id: 1, entries: %{} + + def new(entries \\ []) do + Enum.reduce( + entries, + %Todo.List{}, + &add_entry(&2, &1) + ) + end + + def size(todo_list) do + map_size(todo_list.entries) + end + + def add_entry(todo_list, entry) do + entry = Map.put(entry, :id, todo_list.auto_id) + + new_entries = + Map.put( + todo_list.entries, + todo_list.auto_id, + entry + ) + + %Todo.List{todo_list | entries: new_entries, auto_id: todo_list.auto_id + 1} + end + + def entries(todo_list, date) do + todo_list.entries + |> Stream.filter(fn {_, entry} -> entry.date == date end) + |> Enum.map(fn {_, entry} -> entry end) + end + + def update_entry(todo_list, %{} = new_entry) do + update_entry(todo_list, new_entry.id, fn _ -> new_entry end) + end + + def update_entry(todo_list, entry_id, updater_fun) do + case Map.fetch(todo_list.entries, entry_id) do + :error -> + todo_list + + {:ok, old_entry} -> + old_entry_id = old_entry.id + # Make sure that the result of the updater is a map and the + # id remains unchanged. + new_entry = %{id: ^old_entry_id} = updater_fun.(old_entry) + new_entries = Map.put(todo_list.entries, new_entry.id, new_entry) + %Todo.List{todo_list | entries: new_entries} + end + end + + def delete_entry(todo_list, entry_id) do + %Todo.List{todo_list | entries: Map.delete(todo_list.entries, entry_id)} + end +end diff --git a/chapter7/persistable_todo_cache/lib/todo/server.ex b/chapter7/persistable_todo_cache/lib/todo/server.ex new file mode 100644 index 0000000..5f32c74 --- /dev/null +++ b/chapter7/persistable_todo_cache/lib/todo/server.ex @@ -0,0 +1,62 @@ +defmodule Todo.Server do + use GenServer + + def start(todo_list_name) do + GenServer.start(__MODULE__, todo_list_name) + end + + def add_entry(pid, entry) do + GenServer.cast(pid, {:add_entry, entry}) + end + + def delete_entry(pid, entry_id) do + GenServer.cast(pid, {:delete_entry, entry_id}) + end + + def update_entry(pid, entry_id, updater_fun) do + GenServer.cast(pid, {:update_entry, entry_id, updater_fun}) + end + + def entries(pid, date) do + GenServer.call(pid, {:entries, date}) + end + + @impl true + def init(todo_list_name) do + send(self(), :init_data) + {:ok, {todo_list_name, nil}} + end + + @impl true + def handle_cast({:add_entry, entry}, {name, todo_list}) do + # Get the new todo list. + new_list = Todo.List.add_entry(todo_list, entry) + # Persist the new list to disk. + Todo.Database.store(name, new_list) + {:noreply, {name, new_list}} + end + + @impl true + def handle_cast({:delete_entry, entry_id}, {name, todo_list}) do + new_list = Todo.List.delete_entry(todo_list, entry_id) + Todo.Database.store(name, new_list) + {:noreply, {name, new_list}} + end + + @impl true + def handle_cast({:update_entry, entry_id, updater_fun}, {name, todo_list}) do + new_list = Todo.List.update_entry(todo_list, entry_id, updater_fun) + Todo.Database.store(name, new_list) + {:noreply, {name, new_list}} + end + + @impl true + def handle_call({:entries, date}, _, {_, todo_list} = state) do + {:reply, Todo.List.entries(todo_list, date), state} + end + + @impl true + def handle_info(:init_data, {name, _}) do + {:noreply, {name, Todo.Database.get(name) || Todo.List.new()}} + end +end diff --git a/chapter7/persistable_todo_cache/mix.exs b/chapter7/persistable_todo_cache/mix.exs new file mode 100644 index 0000000..f7ef09c --- /dev/null +++ b/chapter7/persistable_todo_cache/mix.exs @@ -0,0 +1,28 @@ +defmodule Todo.MixProject do + use Mix.Project + + def project do + [ + app: :todo, + version: "0.1.0", + elixir: "~> 1.10", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + # {:dep_from_hexpm, "~> 0.3.0"}, + # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + ] + end +end diff --git a/chapter7/persistable_todo_cache/persist/marianos list b/chapter7/persistable_todo_cache/persist/marianos list new file mode 100644 index 0000000..22ac27d Binary files /dev/null and b/chapter7/persistable_todo_cache/persist/marianos list differ diff --git a/chapter7/persistable_todo_cache/test/test_helper.exs b/chapter7/persistable_todo_cache/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/chapter7/persistable_todo_cache/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/chapter7/persistable_todo_cache/test/todo_cache_test.exs b/chapter7/persistable_todo_cache/test/todo_cache_test.exs new file mode 100644 index 0000000..711d58d --- /dev/null +++ b/chapter7/persistable_todo_cache/test/todo_cache_test.exs @@ -0,0 +1,21 @@ +defmodule TodoCacheTest do + use ExUnit.Case + + test "server_process" do + {:ok, cache} = Todo.Cache.start() + bob_pid = Todo.Cache.server_process(cache, "bob") + + assert bob_pid != Todo.Cache.server_process(cache, "alice") + assert bob_pid == Todo.Cache.server_process(cache, "bob") + end + + test "to-do operations" do + {:ok, cache} = Todo.Cache.start() + alice = Todo.Cache.server_process(cache, "alice") + + Todo.Server.add_entry(alice, %{date: ~D[2020-12-12], title: "Hello"}) + entries = Todo.Server.entries(alice, ~D[2020-12-12]) + + assert [%{date: ~D[2020-12-12], title: "Hello"}] = entries + end +end diff --git a/chapter7/persistable_todo_cache/test/todo_list_test.exs b/chapter7/persistable_todo_cache/test/todo_list_test.exs new file mode 100644 index 0000000..0575e8c --- /dev/null +++ b/chapter7/persistable_todo_cache/test/todo_list_test.exs @@ -0,0 +1,64 @@ +defmodule TodoListTest do + use ExUnit.Case, async: true + + test "empty list" do + assert Todo.List.size(Todo.List.new()) == 0 + end + + test "entries" do + todo_list = + Todo.List.new([ + %{date: ~D[2018-12-19], title: "Dentist"}, + %{date: ~D[2018-12-20], title: "Shopping"}, + %{date: ~D[2018-12-19], title: "Movies"} + ]) + + assert Todo.List.size(todo_list) == 3 + assert todo_list |> Todo.List.entries(~D[2018-12-19]) |> length() == 2 + assert todo_list |> Todo.List.entries(~D[2018-12-20]) |> length() == 1 + assert todo_list |> Todo.List.entries(~D[2018-12-21]) |> length() == 0 + + titles = todo_list |> Todo.List.entries(~D[2018-12-19]) |> Enum.map(& &1.title) + assert ["Dentist", "Movies"] = titles + end + + test "add_entry" do + todo_list = + Todo.List.new() + |> Todo.List.add_entry(%{date: ~D[2018-12-19], title: "Dentist"}) + |> Todo.List.add_entry(%{date: ~D[2018-12-20], title: "Shopping"}) + |> Todo.List.add_entry(%{date: ~D[2018-12-19], title: "Movies"}) + + assert Todo.List.size(todo_list) == 3 + assert todo_list |> Todo.List.entries(~D[2018-12-19]) |> length() == 2 + assert todo_list |> Todo.List.entries(~D[2018-12-20]) |> length() == 1 + assert todo_list |> Todo.List.entries(~D[2018-12-21]) |> length() == 0 + + titles = todo_list |> Todo.List.entries(~D[2018-12-19]) |> Enum.map(& &1.title) + assert ["Dentist", "Movies"] = titles + end + + test "update_entry" do + todo_list = + Todo.List.new() + |> Todo.List.add_entry(%{date: ~D[2018-12-19], title: "Dentist"}) + |> Todo.List.add_entry(%{date: ~D[2018-12-20], title: "Shopping"}) + |> Todo.List.add_entry(%{date: ~D[2018-12-19], title: "Movies"}) + |> Todo.List.update_entry(2, &Map.put(&1, :title, "Updated shopping")) + + assert Todo.List.size(todo_list) == 3 + assert [%{title: "Updated shopping"}] = Todo.List.entries(todo_list, ~D[2018-12-20]) + end + + test "delete_entry" do + todo_list = + Todo.List.new() + |> Todo.List.add_entry(%{date: ~D[2018-12-19], title: "Dentist"}) + |> Todo.List.add_entry(%{date: ~D[2018-12-20], title: "Shopping"}) + |> Todo.List.add_entry(%{date: ~D[2018-12-19], title: "Movies"}) + |> Todo.List.delete_entry(2) + + assert Todo.List.size(todo_list) == 2 + assert Todo.List.entries(todo_list, ~D[2018-12-20]) == [] + end +end diff --git a/chapter7/todo/.formatter.exs b/chapter7/todo/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/chapter7/todo/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/chapter7/todo/.gitignore b/chapter7/todo/.gitignore new file mode 100644 index 0000000..001bac6 --- /dev/null +++ b/chapter7/todo/.gitignore @@ -0,0 +1,24 @@ +# 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 third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# 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 + +# Ignore package tarball (built via "mix hex.build"). +todo-*.tar + diff --git a/chapter7/todo/README.md b/chapter7/todo/README.md new file mode 100644 index 0000000..6b31c9c --- /dev/null +++ b/chapter7/todo/README.md @@ -0,0 +1,21 @@ +# Todo + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `todo` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:todo, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at [https://hexdocs.pm/todo](https://hexdocs.pm/todo). + diff --git a/chapter7/todo/lib/todo/list.ex b/chapter7/todo/lib/todo/list.ex new file mode 100644 index 0000000..228cd8e --- /dev/null +++ b/chapter7/todo/lib/todo/list.ex @@ -0,0 +1,47 @@ +defmodule Todo.List do + defstruct auto_id: 1, entries: %{} + + def new(), do: %Todo.List{} + + def add_entry(todo_list, entry) do + entry = Map.put(entry, :id, todo_list.auto_id) + + new_entries = + Map.put( + todo_list.entries, + todo_list.auto_id, + entry + ) + + %Todo.List{todo_list | entries: new_entries, auto_id: todo_list.auto_id + 1} + end + + def entries(todo_list, date) do + todo_list.entries + |> Stream.filter(fn {_, entry} -> entry.date == date end) + |> Enum.map(fn {_, entry} -> entry end) + end + + def update_entry(todo_list, %{} = new_entry) do + update_entry(todo_list, new_entry.id, fn _ -> new_entry end) + end + + def update_entry(todo_list, entry_id, updater_fun) do + case Map.fetch(todo_list.entries, entry_id) do + :error -> + todo_list + + {:ok, old_entry} -> + old_entry_id = old_entry.id + # Make sure that the result of the updater is a map and the + # id remains unchanged. + new_entry = %{id: ^old_entry_id} = updater_fun.(old_entry) + new_entries = Map.put(todo_list.entries, new_entry.id, new_entry) + %Todo.List{todo_list | entries: new_entries} + end + end + + def delete_entry(todo_list, entry_id) do + %Todo.List{todo_list | entries: Map.delete(todo_list.entries, entry_id)} + end +end diff --git a/chapter7/todo/lib/todo/server.ex b/chapter7/todo/lib/todo/server.ex new file mode 100644 index 0000000..c42f664 --- /dev/null +++ b/chapter7/todo/lib/todo/server.ex @@ -0,0 +1,48 @@ +defmodule Todo.Server do + use GenServer + + def start do + GenServer.start(__MODULE__, nil) + end + + def add_entry(pid, entry) do + GenServer.cast(pid, {:add_entry, entry}) + end + + def delete_entry(pid, entry_id) do + GenServer.cast(pid, {:delete_entry, entry_id}) + end + + def update_entry(pid, entry_id, updater_fun) do + GenServer.cast(pid, {:update_entry, entry_id, updater_fun}) + end + + def entries(pid, date) do + GenServer.call(pid, {:entries, date}) + end + + @impl true + def init(_) do + {:ok, Todo.List.new()} + end + + @impl true + def handle_cast({:add_entry, entry}, state) do + {:noreply, Todo.List.add_entry(state, entry)} + end + + @impl true + def handle_cast({:delete_entry, entry_id}, state) do + {:noreply, Todo.List.delete_entry(state, entry_id)} + end + + @impl true + def handle_cast({:update_entry, entry_id, updater_fun}, state) do + {:noreply, Todo.List.update_entry(state, entry_id, updater_fun)} + end + + @impl true + def handle_call({:entries, date}, _, state) do + {:reply, Todo.List.entries(state, date), state} + end +end diff --git a/chapter7/todo/mix.exs b/chapter7/todo/mix.exs new file mode 100644 index 0000000..f7ef09c --- /dev/null +++ b/chapter7/todo/mix.exs @@ -0,0 +1,28 @@ +defmodule Todo.MixProject do + use Mix.Project + + def project do + [ + app: :todo, + version: "0.1.0", + elixir: "~> 1.10", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + # {:dep_from_hexpm, "~> 0.3.0"}, + # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + ] + end +end diff --git a/chapter7/todo/test/test_helper.exs b/chapter7/todo/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/chapter7/todo/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/chapter7/todo_cache/README.md b/chapter7/todo_cache/README.md new file mode 100644 index 0000000..6b31c9c --- /dev/null +++ b/chapter7/todo_cache/README.md @@ -0,0 +1,21 @@ +# Todo + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `todo` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:todo, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at [https://hexdocs.pm/todo](https://hexdocs.pm/todo). + diff --git a/chapter7/todo_cache/_build/dev/lib/todo/.mix/compile.elixir b/chapter7/todo_cache/_build/dev/lib/todo/.mix/compile.elixir new file mode 100644 index 0000000..ed97bdc Binary files /dev/null and b/chapter7/todo_cache/_build/dev/lib/todo/.mix/compile.elixir differ diff --git a/chapter7/todo_cache/_build/dev/lib/todo/.mix/compile.elixir_scm b/chapter7/todo_cache/_build/dev/lib/todo/.mix/compile.elixir_scm new file mode 100644 index 0000000..f129754 Binary files /dev/null and b/chapter7/todo_cache/_build/dev/lib/todo/.mix/compile.elixir_scm differ diff --git a/chapter7/todo_cache/_build/dev/lib/todo/.mix/compile.protocols b/chapter7/todo_cache/_build/dev/lib/todo/.mix/compile.protocols new file mode 100644 index 0000000..51d52fc Binary files /dev/null and b/chapter7/todo_cache/_build/dev/lib/todo/.mix/compile.protocols differ diff --git a/chapter7/todo_cache/_build/dev/lib/todo/consolidated/Elixir.Collectable.beam b/chapter7/todo_cache/_build/dev/lib/todo/consolidated/Elixir.Collectable.beam new file mode 100644 index 0000000..5c7d2d6 Binary files /dev/null and b/chapter7/todo_cache/_build/dev/lib/todo/consolidated/Elixir.Collectable.beam differ diff --git a/chapter7/todo_cache/_build/dev/lib/todo/consolidated/Elixir.Enumerable.beam b/chapter7/todo_cache/_build/dev/lib/todo/consolidated/Elixir.Enumerable.beam new file mode 100644 index 0000000..ac7f27d Binary files /dev/null and b/chapter7/todo_cache/_build/dev/lib/todo/consolidated/Elixir.Enumerable.beam differ diff --git a/chapter7/todo_cache/_build/dev/lib/todo/consolidated/Elixir.IEx.Info.beam b/chapter7/todo_cache/_build/dev/lib/todo/consolidated/Elixir.IEx.Info.beam new file mode 100644 index 0000000..c777470 Binary files /dev/null and b/chapter7/todo_cache/_build/dev/lib/todo/consolidated/Elixir.IEx.Info.beam differ diff --git a/chapter7/todo_cache/_build/dev/lib/todo/consolidated/Elixir.Inspect.beam b/chapter7/todo_cache/_build/dev/lib/todo/consolidated/Elixir.Inspect.beam new file mode 100644 index 0000000..63b17f2 Binary files /dev/null and b/chapter7/todo_cache/_build/dev/lib/todo/consolidated/Elixir.Inspect.beam differ diff --git a/chapter7/todo_cache/_build/dev/lib/todo/consolidated/Elixir.List.Chars.beam b/chapter7/todo_cache/_build/dev/lib/todo/consolidated/Elixir.List.Chars.beam new file mode 100644 index 0000000..d4e8fb6 Binary files /dev/null and b/chapter7/todo_cache/_build/dev/lib/todo/consolidated/Elixir.List.Chars.beam differ diff --git a/chapter7/todo_cache/_build/dev/lib/todo/consolidated/Elixir.String.Chars.beam b/chapter7/todo_cache/_build/dev/lib/todo/consolidated/Elixir.String.Chars.beam new file mode 100644 index 0000000..84c95b8 Binary files /dev/null and b/chapter7/todo_cache/_build/dev/lib/todo/consolidated/Elixir.String.Chars.beam differ diff --git a/chapter7/todo_cache/_build/dev/lib/todo/ebin/Elixir.Todo.Cache.beam b/chapter7/todo_cache/_build/dev/lib/todo/ebin/Elixir.Todo.Cache.beam new file mode 100644 index 0000000..7dfbe6c Binary files /dev/null and b/chapter7/todo_cache/_build/dev/lib/todo/ebin/Elixir.Todo.Cache.beam differ diff --git a/chapter7/todo_cache/_build/dev/lib/todo/ebin/Elixir.Todo.List.beam b/chapter7/todo_cache/_build/dev/lib/todo/ebin/Elixir.Todo.List.beam new file mode 100644 index 0000000..8f9845c Binary files /dev/null and b/chapter7/todo_cache/_build/dev/lib/todo/ebin/Elixir.Todo.List.beam differ diff --git a/chapter7/todo_cache/_build/dev/lib/todo/ebin/Elixir.Todo.Server.beam b/chapter7/todo_cache/_build/dev/lib/todo/ebin/Elixir.Todo.Server.beam new file mode 100644 index 0000000..c926ce4 Binary files /dev/null and b/chapter7/todo_cache/_build/dev/lib/todo/ebin/Elixir.Todo.Server.beam differ diff --git a/chapter7/todo_cache/_build/dev/lib/todo/ebin/todo.app b/chapter7/todo_cache/_build/dev/lib/todo/ebin/todo.app new file mode 100644 index 0000000..5e22f56 --- /dev/null +++ b/chapter7/todo_cache/_build/dev/lib/todo/ebin/todo.app @@ -0,0 +1,7 @@ +{application,todo, + [{applications,[kernel,stdlib,elixir,logger]}, + {description,"todo"}, + {modules,['Elixir.Todo.Cache','Elixir.Todo.List', + 'Elixir.Todo.Server']}, + {registered,[]}, + {vsn,"0.1.0"}]}. diff --git a/chapter7/todo_cache/_build/test/lib/todo/.mix/.mix_test_failures b/chapter7/todo_cache/_build/test/lib/todo/.mix/.mix_test_failures new file mode 100644 index 0000000..002e9d7 Binary files /dev/null and b/chapter7/todo_cache/_build/test/lib/todo/.mix/.mix_test_failures differ diff --git a/chapter7/todo_cache/_build/test/lib/todo/.mix/compile.elixir b/chapter7/todo_cache/_build/test/lib/todo/.mix/compile.elixir new file mode 100644 index 0000000..a9665eb Binary files /dev/null and b/chapter7/todo_cache/_build/test/lib/todo/.mix/compile.elixir differ diff --git a/chapter7/todo_cache/_build/test/lib/todo/.mix/compile.elixir_scm b/chapter7/todo_cache/_build/test/lib/todo/.mix/compile.elixir_scm new file mode 100644 index 0000000..f129754 Binary files /dev/null and b/chapter7/todo_cache/_build/test/lib/todo/.mix/compile.elixir_scm differ diff --git a/chapter7/todo_cache/_build/test/lib/todo/.mix/compile.protocols b/chapter7/todo_cache/_build/test/lib/todo/.mix/compile.protocols new file mode 100644 index 0000000..51d52fc Binary files /dev/null and b/chapter7/todo_cache/_build/test/lib/todo/.mix/compile.protocols differ diff --git a/chapter7/todo_cache/_build/test/lib/todo/consolidated/Elixir.Collectable.beam b/chapter7/todo_cache/_build/test/lib/todo/consolidated/Elixir.Collectable.beam new file mode 100644 index 0000000..5c7d2d6 Binary files /dev/null and b/chapter7/todo_cache/_build/test/lib/todo/consolidated/Elixir.Collectable.beam differ diff --git a/chapter7/todo_cache/_build/test/lib/todo/consolidated/Elixir.Enumerable.beam b/chapter7/todo_cache/_build/test/lib/todo/consolidated/Elixir.Enumerable.beam new file mode 100644 index 0000000..ac7f27d Binary files /dev/null and b/chapter7/todo_cache/_build/test/lib/todo/consolidated/Elixir.Enumerable.beam differ diff --git a/chapter7/todo_cache/_build/test/lib/todo/consolidated/Elixir.IEx.Info.beam b/chapter7/todo_cache/_build/test/lib/todo/consolidated/Elixir.IEx.Info.beam new file mode 100644 index 0000000..c777470 Binary files /dev/null and b/chapter7/todo_cache/_build/test/lib/todo/consolidated/Elixir.IEx.Info.beam differ diff --git a/chapter7/todo_cache/_build/test/lib/todo/consolidated/Elixir.Inspect.beam b/chapter7/todo_cache/_build/test/lib/todo/consolidated/Elixir.Inspect.beam new file mode 100644 index 0000000..63b17f2 Binary files /dev/null and b/chapter7/todo_cache/_build/test/lib/todo/consolidated/Elixir.Inspect.beam differ diff --git a/chapter7/todo_cache/_build/test/lib/todo/consolidated/Elixir.List.Chars.beam b/chapter7/todo_cache/_build/test/lib/todo/consolidated/Elixir.List.Chars.beam new file mode 100644 index 0000000..d4e8fb6 Binary files /dev/null and b/chapter7/todo_cache/_build/test/lib/todo/consolidated/Elixir.List.Chars.beam differ diff --git a/chapter7/todo_cache/_build/test/lib/todo/consolidated/Elixir.String.Chars.beam b/chapter7/todo_cache/_build/test/lib/todo/consolidated/Elixir.String.Chars.beam new file mode 100644 index 0000000..84c95b8 Binary files /dev/null and b/chapter7/todo_cache/_build/test/lib/todo/consolidated/Elixir.String.Chars.beam differ diff --git a/chapter7/todo_cache/_build/test/lib/todo/ebin/Elixir.Todo.Cache.beam b/chapter7/todo_cache/_build/test/lib/todo/ebin/Elixir.Todo.Cache.beam new file mode 100644 index 0000000..7dfbe6c Binary files /dev/null and b/chapter7/todo_cache/_build/test/lib/todo/ebin/Elixir.Todo.Cache.beam differ diff --git a/chapter7/todo_cache/_build/test/lib/todo/ebin/Elixir.Todo.List.beam b/chapter7/todo_cache/_build/test/lib/todo/ebin/Elixir.Todo.List.beam new file mode 100644 index 0000000..6be8437 Binary files /dev/null and b/chapter7/todo_cache/_build/test/lib/todo/ebin/Elixir.Todo.List.beam differ diff --git a/chapter7/todo_cache/_build/test/lib/todo/ebin/Elixir.Todo.Server.beam b/chapter7/todo_cache/_build/test/lib/todo/ebin/Elixir.Todo.Server.beam new file mode 100644 index 0000000..8205ea2 Binary files /dev/null and b/chapter7/todo_cache/_build/test/lib/todo/ebin/Elixir.Todo.Server.beam differ diff --git a/chapter7/todo_cache/_build/test/lib/todo/ebin/todo.app b/chapter7/todo_cache/_build/test/lib/todo/ebin/todo.app new file mode 100644 index 0000000..5e22f56 --- /dev/null +++ b/chapter7/todo_cache/_build/test/lib/todo/ebin/todo.app @@ -0,0 +1,7 @@ +{application,todo, + [{applications,[kernel,stdlib,elixir,logger]}, + {description,"todo"}, + {modules,['Elixir.Todo.Cache','Elixir.Todo.List', + 'Elixir.Todo.Server']}, + {registered,[]}, + {vsn,"0.1.0"}]}. diff --git a/chapter7/todo_cache/lib/todo/cache.ex b/chapter7/todo_cache/lib/todo/cache.ex new file mode 100644 index 0000000..bf9e92b --- /dev/null +++ b/chapter7/todo_cache/lib/todo/cache.ex @@ -0,0 +1,33 @@ +defmodule Todo.Cache do + use GenServer + + def start() do + GenServer.start(__MODULE__, nil) + end + + def server_process(cache_pid, todo_list_name) do + GenServer.call(cache_pid, {:server_process, todo_list_name}) + end + + @impl true + def init(_) do + {:ok, %{}} + end + + @impl true + def handle_call({:server_process, todo_list_name}, _, todo_servers) do + case Map.fetch(todo_servers, todo_list_name) do + {:ok, todo_server} -> + {:reply, todo_server, todo_servers} + + :error -> + {:ok, new_todo_server} = Todo.Server.start() + + { + :reply, + new_todo_server, + Map.put(todo_servers, todo_list_name, new_todo_server) + } + end + end +end diff --git a/chapter7/todo_cache/lib/todo/list.ex b/chapter7/todo_cache/lib/todo/list.ex new file mode 100644 index 0000000..af13fbe --- /dev/null +++ b/chapter7/todo_cache/lib/todo/list.ex @@ -0,0 +1,57 @@ +defmodule Todo.List do + defstruct auto_id: 1, entries: %{} + + def new(entries \\ []) do + Enum.reduce( + entries, + %Todo.List{}, + &add_entry(&2, &1) + ) + end + + def size(todo_list) do + Map.size(todo_list.entries) + end + + def add_entry(todo_list, entry) do + entry = Map.put(entry, :id, todo_list.auto_id) + + new_entries = + Map.put( + todo_list.entries, + todo_list.auto_id, + entry + ) + + %Todo.List{todo_list | entries: new_entries, auto_id: todo_list.auto_id + 1} + end + + def entries(todo_list, date) do + todo_list.entries + |> Stream.filter(fn {_, entry} -> entry.date == date end) + |> Enum.map(fn {_, entry} -> entry end) + end + + def update_entry(todo_list, %{} = new_entry) do + update_entry(todo_list, new_entry.id, fn _ -> new_entry end) + end + + def update_entry(todo_list, entry_id, updater_fun) do + case Map.fetch(todo_list.entries, entry_id) do + :error -> + todo_list + + {:ok, old_entry} -> + old_entry_id = old_entry.id + # Make sure that the result of the updater is a map and the + # id remains unchanged. + new_entry = %{id: ^old_entry_id} = updater_fun.(old_entry) + new_entries = Map.put(todo_list.entries, new_entry.id, new_entry) + %Todo.List{todo_list | entries: new_entries} + end + end + + def delete_entry(todo_list, entry_id) do + %Todo.List{todo_list | entries: Map.delete(todo_list.entries, entry_id)} + end +end diff --git a/chapter7/todo_cache/lib/todo/server.ex b/chapter7/todo_cache/lib/todo/server.ex new file mode 100644 index 0000000..c42f664 --- /dev/null +++ b/chapter7/todo_cache/lib/todo/server.ex @@ -0,0 +1,48 @@ +defmodule Todo.Server do + use GenServer + + def start do + GenServer.start(__MODULE__, nil) + end + + def add_entry(pid, entry) do + GenServer.cast(pid, {:add_entry, entry}) + end + + def delete_entry(pid, entry_id) do + GenServer.cast(pid, {:delete_entry, entry_id}) + end + + def update_entry(pid, entry_id, updater_fun) do + GenServer.cast(pid, {:update_entry, entry_id, updater_fun}) + end + + def entries(pid, date) do + GenServer.call(pid, {:entries, date}) + end + + @impl true + def init(_) do + {:ok, Todo.List.new()} + end + + @impl true + def handle_cast({:add_entry, entry}, state) do + {:noreply, Todo.List.add_entry(state, entry)} + end + + @impl true + def handle_cast({:delete_entry, entry_id}, state) do + {:noreply, Todo.List.delete_entry(state, entry_id)} + end + + @impl true + def handle_cast({:update_entry, entry_id, updater_fun}, state) do + {:noreply, Todo.List.update_entry(state, entry_id, updater_fun)} + end + + @impl true + def handle_call({:entries, date}, _, state) do + {:reply, Todo.List.entries(state, date), state} + end +end diff --git a/chapter7/todo_cache/mix.exs b/chapter7/todo_cache/mix.exs new file mode 100644 index 0000000..f7ef09c --- /dev/null +++ b/chapter7/todo_cache/mix.exs @@ -0,0 +1,28 @@ +defmodule Todo.MixProject do + use Mix.Project + + def project do + [ + app: :todo, + version: "0.1.0", + elixir: "~> 1.10", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + # {:dep_from_hexpm, "~> 0.3.0"}, + # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + ] + end +end diff --git a/chapter7/todo_cache/test/test_helper.exs b/chapter7/todo_cache/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/chapter7/todo_cache/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/chapter7/todo_cache/test/todo_cache_test.exs b/chapter7/todo_cache/test/todo_cache_test.exs new file mode 100644 index 0000000..711d58d --- /dev/null +++ b/chapter7/todo_cache/test/todo_cache_test.exs @@ -0,0 +1,21 @@ +defmodule TodoCacheTest do + use ExUnit.Case + + test "server_process" do + {:ok, cache} = Todo.Cache.start() + bob_pid = Todo.Cache.server_process(cache, "bob") + + assert bob_pid != Todo.Cache.server_process(cache, "alice") + assert bob_pid == Todo.Cache.server_process(cache, "bob") + end + + test "to-do operations" do + {:ok, cache} = Todo.Cache.start() + alice = Todo.Cache.server_process(cache, "alice") + + Todo.Server.add_entry(alice, %{date: ~D[2020-12-12], title: "Hello"}) + entries = Todo.Server.entries(alice, ~D[2020-12-12]) + + assert [%{date: ~D[2020-12-12], title: "Hello"}] = entries + end +end diff --git a/chapter7/todo_cache/test/todo_list_test.exs b/chapter7/todo_cache/test/todo_list_test.exs new file mode 100644 index 0000000..0575e8c --- /dev/null +++ b/chapter7/todo_cache/test/todo_list_test.exs @@ -0,0 +1,64 @@ +defmodule TodoListTest do + use ExUnit.Case, async: true + + test "empty list" do + assert Todo.List.size(Todo.List.new()) == 0 + end + + test "entries" do + todo_list = + Todo.List.new([ + %{date: ~D[2018-12-19], title: "Dentist"}, + %{date: ~D[2018-12-20], title: "Shopping"}, + %{date: ~D[2018-12-19], title: "Movies"} + ]) + + assert Todo.List.size(todo_list) == 3 + assert todo_list |> Todo.List.entries(~D[2018-12-19]) |> length() == 2 + assert todo_list |> Todo.List.entries(~D[2018-12-20]) |> length() == 1 + assert todo_list |> Todo.List.entries(~D[2018-12-21]) |> length() == 0 + + titles = todo_list |> Todo.List.entries(~D[2018-12-19]) |> Enum.map(& &1.title) + assert ["Dentist", "Movies"] = titles + end + + test "add_entry" do + todo_list = + Todo.List.new() + |> Todo.List.add_entry(%{date: ~D[2018-12-19], title: "Dentist"}) + |> Todo.List.add_entry(%{date: ~D[2018-12-20], title: "Shopping"}) + |> Todo.List.add_entry(%{date: ~D[2018-12-19], title: "Movies"}) + + assert Todo.List.size(todo_list) == 3 + assert todo_list |> Todo.List.entries(~D[2018-12-19]) |> length() == 2 + assert todo_list |> Todo.List.entries(~D[2018-12-20]) |> length() == 1 + assert todo_list |> Todo.List.entries(~D[2018-12-21]) |> length() == 0 + + titles = todo_list |> Todo.List.entries(~D[2018-12-19]) |> Enum.map(& &1.title) + assert ["Dentist", "Movies"] = titles + end + + test "update_entry" do + todo_list = + Todo.List.new() + |> Todo.List.add_entry(%{date: ~D[2018-12-19], title: "Dentist"}) + |> Todo.List.add_entry(%{date: ~D[2018-12-20], title: "Shopping"}) + |> Todo.List.add_entry(%{date: ~D[2018-12-19], title: "Movies"}) + |> Todo.List.update_entry(2, &Map.put(&1, :title, "Updated shopping")) + + assert Todo.List.size(todo_list) == 3 + assert [%{title: "Updated shopping"}] = Todo.List.entries(todo_list, ~D[2018-12-20]) + end + + test "delete_entry" do + todo_list = + Todo.List.new() + |> Todo.List.add_entry(%{date: ~D[2018-12-19], title: "Dentist"}) + |> Todo.List.add_entry(%{date: ~D[2018-12-20], title: "Shopping"}) + |> Todo.List.add_entry(%{date: ~D[2018-12-19], title: "Movies"}) + |> Todo.List.delete_entry(2) + + assert Todo.List.size(todo_list) == 2 + assert Todo.List.entries(todo_list, ~D[2018-12-20]) == [] + end +end