diff --git a/README.md b/README.md index 1955f82..ecb61c8 100644 --- a/README.md +++ b/README.md @@ -445,3 +445,106 @@ end ``` `mix test` again, and we should be all green. + +#### 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. + +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. + +So, first we'll need to edit our schema to add a shared id to our address entries: + +``` elixir +defmodule Append.Address do + ... + schema "addresses" do + ... + field(:entry_id, :integer) + + ... + end + ... +end +``` + +And create a migration file: + +``` +mix ecto.gen.migration add_entry_id +``` + +``` elixir +defmodule Append.Repo.Migrations.AddEntryId do + use Ecto.Migration + + def change do + alter table("addresses") do + add :entry_id, :integer + end + + end +end +``` + +``` +mix ecto.migrate +``` + +Now, we'll write a test for the update function. + +``` elixir +defmodule Append.AddressTest do + ... + test "update item in database" do + {:ok, item} = insert_address() + + {:ok, updated_item} = Address.update(item, %{tel: "0123444444"}) + + assert updated_item.name == item.name + assert updated_item.tel != item.tel + end + ... +end +``` + +Then we write the update function itself. We'll take the existing item, and a map of updated attributes as arguments. + +Then we'll use the `insert` function to create a new entry in the database, rather than `update`, which would overwrite the old one. + +``` elixir +defmodule Append.AppendOnlyLog do + ... + defmacro __before_compile__(_env) do + quote do + ... + def update(%__MODULE__{} = item, attrs) do + item + |> __MODULE__.changeset(attrs) + |> Repo.insert() + end + end + end +end +``` + +But now, if we try to run our tests: + +``` +1) test update item in database (Append.AddressTest) + test/append/address_test.exs:25 + ** (Ecto.ConstraintError) constraint error when attempting to insert struct: + + * unique: addresses_pkey +``` +We can't add the item again, because the unique id already exists in the database. + +``` elixir +def update(%__MODULE__{} = item, attrs) do + item + |> Map.put(:id, nil) + |> __MODULE__.changeset(attrs) + |> Repo.insert() +end +``` + +So here we remove the original autogenerated id from the existing item, preventing us from duplicating it in the database. diff --git a/lib/append/address.ex b/lib/append/address.ex index 298fb4d..5e3a453 100644 --- a/lib/append/address.ex +++ b/lib/append/address.ex @@ -10,6 +10,7 @@ defmodule Append.Address do field(:name, :string) field(:postcode, :string) field(:tel, :string) + field(:entry_id, :integer) timestamps() end diff --git a/lib/append/append_only_log.ex b/lib/append/append_only_log.ex index 08a359d..0b7ce02 100644 --- a/lib/append/append_only_log.ex +++ b/lib/append/append_only_log.ex @@ -38,6 +38,10 @@ defmodule Append.AppendOnlyLog do end def update(%__MODULE__{} = item, attrs) do + item + |> Map.put(:id, nil) + |> __MODULE__.changeset(attrs) + |> Repo.insert() end end end diff --git a/priv/repo/migrations/20180914130516_add_entry_id.exs b/priv/repo/migrations/20180914130516_add_entry_id.exs new file mode 100644 index 0000000..048b201 --- /dev/null +++ b/priv/repo/migrations/20180914130516_add_entry_id.exs @@ -0,0 +1,10 @@ +defmodule Append.Repo.Migrations.AddEntryId do + use Ecto.Migration + + def change do + alter table("addresses") do + add :entry_id, :integer + end + + end +end diff --git a/test/append/address_test.exs b/test/append/address_test.exs index 2719bdf..a03e62e 100644 --- a/test/append/address_test.exs +++ b/test/append/address_test.exs @@ -22,6 +22,15 @@ defmodule Append.AddressTest do end end + test "update item in database" do + {:ok, item} = insert_address() + + {:ok, updated_item} = Address.update(item, %{tel: "0123444444"}) + + assert updated_item.name == item.name + assert updated_item.tel != item.tel + end + def insert_address do Address.insert(%{ name: "Thor",