Update: Finished persistable todo server
This commit is contained in:
parent
09665c3b88
commit
55589a963f
80 changed files with 809 additions and 0 deletions
21
chapter7/todo_cache/README.md
Normal file
21
chapter7/todo_cache/README.md
Normal file
|
|
@ -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).
|
||||
|
||||
BIN
chapter7/todo_cache/_build/dev/lib/todo/.mix/compile.elixir
Normal file
BIN
chapter7/todo_cache/_build/dev/lib/todo/.mix/compile.elixir
Normal file
Binary file not shown.
BIN
chapter7/todo_cache/_build/dev/lib/todo/.mix/compile.elixir_scm
Normal file
BIN
chapter7/todo_cache/_build/dev/lib/todo/.mix/compile.elixir_scm
Normal file
Binary file not shown.
BIN
chapter7/todo_cache/_build/dev/lib/todo/.mix/compile.protocols
Normal file
BIN
chapter7/todo_cache/_build/dev/lib/todo/.mix/compile.protocols
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
7
chapter7/todo_cache/_build/dev/lib/todo/ebin/todo.app
Normal file
7
chapter7/todo_cache/_build/dev/lib/todo/ebin/todo.app
Normal file
|
|
@ -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"}]}.
|
||||
BIN
chapter7/todo_cache/_build/test/lib/todo/.mix/.mix_test_failures
Normal file
BIN
chapter7/todo_cache/_build/test/lib/todo/.mix/.mix_test_failures
Normal file
Binary file not shown.
BIN
chapter7/todo_cache/_build/test/lib/todo/.mix/compile.elixir
Normal file
BIN
chapter7/todo_cache/_build/test/lib/todo/.mix/compile.elixir
Normal file
Binary file not shown.
BIN
chapter7/todo_cache/_build/test/lib/todo/.mix/compile.elixir_scm
Normal file
BIN
chapter7/todo_cache/_build/test/lib/todo/.mix/compile.elixir_scm
Normal file
Binary file not shown.
BIN
chapter7/todo_cache/_build/test/lib/todo/.mix/compile.protocols
Normal file
BIN
chapter7/todo_cache/_build/test/lib/todo/.mix/compile.protocols
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
7
chapter7/todo_cache/_build/test/lib/todo/ebin/todo.app
Normal file
7
chapter7/todo_cache/_build/test/lib/todo/ebin/todo.app
Normal file
|
|
@ -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"}]}.
|
||||
33
chapter7/todo_cache/lib/todo/cache.ex
Normal file
33
chapter7/todo_cache/lib/todo/cache.ex
Normal file
|
|
@ -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
|
||||
57
chapter7/todo_cache/lib/todo/list.ex
Normal file
57
chapter7/todo_cache/lib/todo/list.ex
Normal file
|
|
@ -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
|
||||
48
chapter7/todo_cache/lib/todo/server.ex
Normal file
48
chapter7/todo_cache/lib/todo/server.ex
Normal file
|
|
@ -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
|
||||
28
chapter7/todo_cache/mix.exs
Normal file
28
chapter7/todo_cache/mix.exs
Normal file
|
|
@ -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
|
||||
1
chapter7/todo_cache/test/test_helper.exs
Normal file
1
chapter7/todo_cache/test/test_helper.exs
Normal file
|
|
@ -0,0 +1 @@
|
|||
ExUnit.start()
|
||||
21
chapter7/todo_cache/test/todo_cache_test.exs
Normal file
21
chapter7/todo_cache/test/todo_cache_test.exs
Normal file
|
|
@ -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
|
||||
64
chapter7/todo_cache/test/todo_list_test.exs
Normal file
64
chapter7/todo_cache/test/todo_list_test.exs
Normal file
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue