Skip to content

Commit

Permalink
expand/clarify implementation steps to help other beginners work thro…
Browse files Browse the repository at this point in the history
…ugh it ... #1
  • Loading branch information
nelsonic committed Sep 20, 2018
1 parent bf9bf09 commit 50a1476
Showing 1 changed file with 97 additions and 11 deletions.
108 changes: 97 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ This will create two new files:

Before you follow the instructions in your terminal, we'll need to edit the generated migration file.

The generated file should look like this:
The generated migration file should look like this:

``` elixir
defmodule Append.Repo.Migrations.CreateAddresses do
Expand Down Expand Up @@ -116,6 +116,9 @@ defmodule Append.Repo.Migrations.CreateAddresses do
end
```

> For reference, this is what your migration file _should_ look like now:
[priv/repo/migrations/20180912142549_create_addresses.exs](https://github.com/dwyl/phoenix-ecto-append-only-log-example/pull/2/files#diff-db55bfd345510f8bbb29d36daadf7061R21)

Once this is done, run:
``` sh
mix ecto.migrate
Expand All @@ -128,6 +131,13 @@ and you should see the following output:
[info] == Migrated in 0.0s
```

> **Note**: if you followed terminal instruction and ran `mix ecto.migrate`
**before** updating the migration file, you will need to run
[`mix ecto.drop`](https://hexdocs.pm/ecto/Mix.Tasks.Ecto.Drop.html#content)
and **first** update the migration file (_as per the instructions_)
and **then** run:
`mix ecto.create && mix ecto.migrate`.

### 3. Defining our Interface

Now that we have no way to delete or update the data, we need to define the functions we'll use to access and insert the data. To do this we'll define an Elixir behaviour (https://hexdocs.pm/elixir/behaviours.html) with some predefined functions.
Expand All @@ -151,6 +161,8 @@ The next step in defining a behaviour is to provide some callbacks that must be

``` elixir
defmodule Append.AppendOnlyLog do
alias Append.Repo

@callback insert
@callback get
@callback get_by
Expand All @@ -169,6 +181,8 @@ Callback definitions are similar to typespecs, in that you can provide the types

``` elixir
defmodule Append.AppendOnlyLog do
alias Append.Repo

@callback insert(struct) :: {:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()}
@callback get(integer) :: Ecto.Schema.t() | nil | no_return()
@callback get_by(Keyword.t() | map) :: Ecto.Schema.t() | nil | no_return()
Expand Down Expand Up @@ -423,7 +437,10 @@ defmodule Append.AddressTest do
end
```
You'll see we've refactored the insert call into a function so we can reuse it, and added some simple tests. Run `mix test` and make sure they fail, then we'll implement the functions.
You'll see we've refactored the insert call into a function
so we can reuse it, and added some simple tests.
Run `mix test` and make sure they fail,
then we'll implement the functions.
``` elixir
defmodule Append.AppendOnlyLog do
Expand All @@ -448,11 +465,23 @@ end
#### 4.3 Update
Now we come to the update function. "But I thought we were only appending to the database?" I hear you ask. This is true, but we still need to relate our existing data to the new, updated data we add.
Now we come to the update function. <br />
"But I thought we were only appending to the database?" I hear you ask. <br />
This is true, but we still need to relate our existing data to the new,
updated data we add.
To do this, we need to be able to reference the previous entry somehow. The simplest (conceptually) way of doing this is to provide each entry a unique id. Note that the id will be used to represent unique entries, but it will not be unique in a table, as revisions of entries will have the same id. This is the simplest way we can link our entries, but there may be some disadvantages, which we'll look into later.
To do this, we need to be able to reference the previous entry somehow.
The simplest (conceptually) way of doing this
is to provide a unique id to each entry.
Note that the id will be used to represent unique entries,
but it will not be unique in a table,
as revisions of entries will have the same id.
This is the simplest way we can link our entries,
but there may be some disadvantages,
which we'll look into later.
So, first we'll need to edit our schema to add a shared id to our address entries:
So, first we'll need to edit our schema
to add a shared id to our address entries:
``` elixir
defmodule Append.Address do
Expand Down Expand Up @@ -488,6 +517,21 @@ Then we create a migration file:
mix ecto.gen.migration add_entry_id
```
That created a file called
`priv/repo/migrations/20180914130516_add_entry_id.exs`
containing the following "blank" migration:
```elixir
defmodule Append.Repo.Migrations.AddEntryId do
use Ecto.Migration

def change do

end
end
```
Add an `alter` definition to the `change` function body:
``` elixir
defmodule Append.Repo.Migrations.AddEntryId do
use Ecto.Migration
Expand All @@ -500,10 +544,20 @@ defmodule Append.Repo.Migrations.AddEntryId do
end
end
```
This is fairly explanatory: it alters the `addresses` table
to add the `entry_id` field.
Run the migration:
```
mix ecto.migrate
```
You should see the following in your terminal:
```sh
[info] == Running Append.Repo.Migrations.AddEntryId.change/0 forward
[info] alter table addresses
[info] == Migrated in 0.0s
```
Now, we'll write a test for the update function.
Expand All @@ -522,10 +576,35 @@ defmodule Append.AddressTest do
end
```
Then we write the update function itself. We'll take the existing item, and a map of updated attributes as arguments.
If you attempt to _run_ this test with `mix test`,
you will see:
```sh
.....
1) test update item in database (Append.AddressTest)
test/append/address_test.exs:25
** (MatchError) no match of right hand side value: nil
code: {:ok, updated_item} = Address.update(item, %{tel: "0123444444"})
stacktrace:
test/append/address_test.exs:28: (test)
..
Finished in 0.2 seconds
8 tests, 1 failure
```
Let's _implement_ the `update` function to make the test pass.
The update function itself will receive two arguments:
the existing item and a map of updated attributes.
Then we'll use the `insert` function to create a new entry in the database, rather than `update`, which would overwrite the old one.
Then we'll use the `insert` function
to create a new entry in the database,
rather than `update`,
which would overwrite the old one.
In your `/lib/append/append_only_log.ex` file add the following `def update`:
``` elixir
defmodule Append.AppendOnlyLog do
...
Expand All @@ -542,7 +621,8 @@ defmodule Append.AppendOnlyLog do
end
```
But now, if we try to run our tests:
If we try to run our tests,
we will see the following error:
```
1) test update item in database (Append.AddressTest)
Expand All @@ -551,8 +631,11 @@ But now, if we try to run our tests:
* unique: addresses_pkey
```
We can't add the item again, because the unique id already exists in the database.
This is because we can't add the item again,
with the same unique id (`addresses_pkey`)
as it already exists in the database.
We need to "clear" the `:id` field _before_ attempting to update (insert):
``` elixir
def update(%__MODULE__{} = item, attrs) do
item
Expand All @@ -562,11 +645,14 @@ def update(%__MODULE__{} = item, attrs) do
end
```
So here we remove the original autogenerated id from the existing item, preventing us from duplicating it in the database.
So here we remove the original autogenerated id from the existing item,
preventing us from duplicating it in the database.
#### 4.4 Get history
The final part of our append only database will be the functionality to see the entire history of an item.
The final part of our append only database will be the functionality
to see the entire history of an item.
As usual, we'll write a test first:
Expand Down

0 comments on commit 50a1476

Please sign in to comment.