Agent Journal
Mix.install([
{:jason, "~> 1.4"},
{:kino, "~> 0.9", override: true},
{:youtube, github: "brooklinjazz/youtube"},
{:hidden_cell, github: "brooklinjazz/hidden_cell"}
])
Navigation
Home Report An Issue State: Agent And ETSETS Inventory ManagementAgent Journal
For this exercise, you’re going to create a Journal
Agent which will store journal entries as strings.
Example Solution
defmodule Journal do
use Agent
def start_link(entries) do
Agent.start_link(fn -> entries end)
end
def all_entries(pid, opts \\ []) do
entries = Agent.get(pid, fn state -> state end)
if opts[:order] == :desc, do: Enum.reverse(entries), else: entries
end
def add_entry(pid, entry) do
# Using ++ is less performant than prepending.
# A more optimal solution would be to store entries in reverse order so they can be prepended.
Agent.update(pid, fn state -> state ++ [entry] end)
end
end
Implement the Journal
module as documented below.
defmodule Journal do
@moduledoc """
Documentation for `Journal`
"""
use Agent
@doc """
Start the Agent process.
## Examples
Default.
iex> {:ok, pid} = Journal.start_link([])
With initial entries.
iex> {:ok, pid} = Journal.start_link(["Entry1", "Entry 2"])
"""
def start_link(_opts) do
end
@doc """
Get all journal entries.
## Examples
Empty journal.
iex> {:ok, pid} = Journal.start_link([])
iex> Journal.all_entries(pid)
[]
Journal with entries. Entries are returned in ascending order (oldest entries first).
iex> {:ok, pid} = Journal.start_link(["Entry 1", "Entry 2"])
iex> Journal.all_entries(pid)
["Entry 1", "Entry 2"]
Ascending order (default).
iex> {:ok, pid} = Journal.start_link(["Entry 1", "Entry 2"])
iex> Journal.all_entries(pid, order: :asc)
["Entry 1", "Entry 2"]
Descending order.
iex> {:ok, pid} = Journal.start_link(["Entry 1", "Entry 2"])
iex> Journal.all_entries(pid, order: :desc)
["Entry 2", "Entry 1"]
"""
def all_entries(pid, opts \\ []) do
end
@doc """
Add a journal entry.
## Examples
iex> {:ok, pid} = Journal.start_link([])
iex> Journal.add_entry(pid, "Entry 1")
:ok
iex> Journal.add_entry(pid, "Entry 2")
:ok
iex> Journal.all_entries(pid)
["Entry 1", "Entry 2"]
"""
def add_entry(pid, entry) do
end
end
:
Bonus: Advanced Journal
Expand upon your original journal with several additional features. Journal entries will now be stored as a struct with :id
, :title
, :content
, :updated_at
, and created_at
fields. Enforce all keys.
example_entry = %AdvancedJournal{
# ids start at `0` and auto increment with each new entry.
id: 0,
title: "Title",
content: "Content",
created_at: DateTime.utc_now(),
updated_at: DateTime.utc_now()
}
Example Solution
defmodule AdvancedJournal do
use Agent
@enforce_keys [:id, :title, :content, :created_at, :updated_at]
defstruct @enforce_keys
def start_link(entry_attrs) do
entries =
entry_attrs
|> Enum.with_index()
|> Enum.map(fn {attrs, index} -> make_entry(index, attrs) end)
|> Enum.reverse()
Agent.start_link(fn -> %{entries: entries, current_index: Enum.count(entries)} end)
end
def all_entries(pid, opts \\ []) do
entries = Agent.get(pid, fn state -> state.entries end)
if opts[:order] == :desc, do: entries, else: Enum.reverse(entries)
end
def add_entry(pid, attrs) do
Agent.update(pid, fn state ->
entry = make_entry(state.current_index, attrs)
%{
state
| current_index: state.current_index + 1,
entries: [entry | state.entries]
}
end)
end
def update_entry(pid, id, attrs) do
Agent.update(pid, fn state ->
index = Enum.find_index(state.entries, fn entry -> entry.id == id end)
new_entries = List.update_at(state.entries, index, fn entry -> Map.merge(entry, attrs) end)
%{state | entries: new_entries}
end)
end
def delete_entry(pid, id) do
Agent.update(pid, fn state ->
new_entries = Enum.reject(state.entries, fn entry -> entry.id == id end)
%{state | entries: new_entries}
end)
end
def make_entry(index, attrs) do
%__MODULE__{
id: index,
title: attrs[:title] || "",
content: attrs[:content] || "",
created_at: DateTime.utc_now(),
updated_at: DateTime.utc_now()
}
end
end
Implement the AdvancedJournal
using Agent as documented below.
defmodule AdvancedJournal do
@moduledoc """
Documentation for `AdvancedJournal`
"""
use Agent
@doc """
Start the Agent process
## Examples
Default.
iex> {:ok, pid} = AdvancedJournal.start_link([])
With initial entries.
iex> {:ok, pid} = AdvancedJournal.start_link([%{title: "Entry 1", content: "Entry 1 Content"}])
"""
def start_link(opts) do
end
@doc """
Return all entries. Entries are automatically assigned an `:id` starting at `0` in the
order they are created in. The `:created_at` and `:updated_at` fields should be the current
UTC [DateTime](https://hexdocs.pm/elixir/DateTime.html) at the moment of creation.
We do not include `:created_at` and `:updated_at` in doctests as small differences
in time could cause these tests to fail.
## Examples
Empty journal.
iex> {:ok, pid} = AdvancedJournal.start_link([])
iex> AdvancedJournal.all_entries(pid)
[]
One entry.
iex> {:ok, pid} = AdvancedJournal.start_link([%{title: "Title", content: "Content"}])
iex> [%AdvancedJournal{id: 0, title: "Title", content: "Content", created_at: _, updated_at: _}] = AdvancedJournal.all_entries(pid)
Multiple entries. Order is ascending by default.
iex> {:ok, pid} = AdvancedJournal.start_link([%{title: "Entry 1", content: "Entry 1"}, %{title: "Entry 2", content: "Entry 2"}])
iex>
..>[
..> %AdvancedJournal{id: 0, title: "Entry 1", content: "Entry 1", created_at: _, updated_at: _},
..> %AdvancedJournal{id: 1, title: "Entry 2", content: "Entry 2", created_at: _, updated_at: _}
..>] = AdvancedJournal.all_entries(pid)
Ascending order (default).
iex> {:ok, pid} = AdvancedJournal.start_link([%{title: "Entry 1", content: "Entry 1"}, %{title: "Entry 2", content: "Entry 2"}])
iex>
..>[
..> %AdvancedJournal{id: 0, title: "Entry 1", content: "Entry 1", created_at: _, updated_at: _},
..> %AdvancedJournal{id: 1, title: "Entry 2", content: "Entry 2", created_at: _, updated_at: _}
..>] = AdvancedJournal.all_entries(pid, order: :asc)
Descending order.
iex> {:ok, pid} = AdvancedJournal.start_link([%{title: "Entry 1", content: "Entry 1"}, %{title: "Entry 2", content: "Entry 2"}])
iex>
..>[
..> %AdvancedJournal{id: 1, title: "Entry 2", content: "Entry 2", created_at: _, updated_at: _}
..> %AdvancedJournal{id: 0, title: "Entry 1", content: "Entry 1", created_at: _, updated_at: _},
..>] = AdvancedJournal.all_entries(pid, order: :desc)
"""
def all_entries(pid, opts \\ []) do
end
@doc """
Add a journal entry. Automatically create `:id`, `:created_at`, and `:updated_at` fields.
## Examples
One entry.
iex> {:ok, pid} = AdvancedJournal.start_link([])
iex> AdvancedJournal.add_entry(pid, %{title: "Title", content: "Content"})
iex> [%AdvancedJournal{id: 0, title: "Title", content: "Content", created_at: _, updated_at: _}] = AdvancedJournal.all_entries(pid)
Multiple entries.
iex> {:ok, pid} = AdvancedJournal.start_link([])
iex> AdvancedJournal.add_entry(pid, %{title: "Entry 1", content: "Entry 1"})
iex> AdvancedJournal.add_entry(pid, %{title: "Entry 2", content: "Entry 2"})
iex>
..>[
..> %AdvancedJournal{id: 0, title: "Entry 1", content: "Entry 1", created_at: _, updated_at: _}
..> %AdvancedJournal{id: 1, title: "Entry 2", content: "Entry 2", created_at: _, updated_at: _},
..>] = AdvancedJournal.all_entries(pid)
"""
def add_entry(pid, attrs) do
end
@doc """
Update a journal entry.
This should automatically set the `:updated_at` field to the current [DateTime](https://hexdocs.pm/elixir/DateTime.html).
## Examples
iex> {:ok, pid} = AdvancedJournal.start_link([%{title: "Title", content: "Content"}])
iex> AdvancedJournal.update_entry(pid, 0, %{title: "Updated Title", content: "Updated Content"})
iex> [%AdvancedJournal{id: 0, title: "Updated Title", content: "Updated Content", created_at: _, updated_at: _}] = AdvancedJournal.all_entries(pid)
"""
def update_entry(pid, id, attrs) do
end
@doc """
Delete a journal entry by it's id.
## Examples
iex> {:ok, pid} = AdvancedJournal.start_link([%{title: "Title", content: "Content"}])
iex> AdvancedJournal.delete_entry(pid, 0)
iex> []
"""
def delete_entry(pid, id, attrs) do
end
end
Commit Your Progress
DockYard Academy now recommends you use the latest Release rather than forking or cloning our repository.
Run git status
to ensure there are no undesirable changes.
Then run the following in your command line from the curriculum
folder to commit your progress.
$ git add .
$ git commit -m "finish Agent Journal exercise"
$ git push
We’re proud to offer our open-source curriculum free of charge for anyone to learn from at their own pace.
We also offer a paid course where you can learn from an instructor alongside a cohort of your peers. We will accept applications for the June-August 2023 cohort soon.