diff --git a/.gitignore b/.gitignore index 9779ca6..c2956f5 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,6 @@ npm-debug.log # Alternatively, you may comment the line below and commit the # secrets files as long as you replace their contents by environment # variables. -/config/*.secret.exs \ No newline at end of file +/config/*.secret.exs + +.elixir_ls \ No newline at end of file diff --git a/README.md b/README.md index 09221e4..2a34f39 100644 --- a/README.md +++ b/README.md @@ -425,8 +425,9 @@ defmodule Append.AppendOnlyLog do @callback insert @callback get - @callback get_by + @callback all @callback update + @callback delete defmacro __using__(_opts) do ... @@ -434,7 +435,7 @@ defmodule Append.AppendOnlyLog do end ``` -These are the four functions we'll define in this macro to interface with the database. +These are the functions we'll define in this macro to interface with the database. You may think it odd that we're defining an `update` function for our append-only database, but we'll get to that later. @@ -448,8 +449,9 @@ defmodule Append.AppendOnlyLog do @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() + @callback all() :: [Ecto.Schema.t()] @callback update(Ecto.Schema.t(), struct) :: {:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()} + @callback delete(Ecto.Schema.t()) :: {:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()} defmacro __using__(_opts) do quote do @@ -461,7 +463,7 @@ defmodule Append.AppendOnlyLog do def get(id) do end - def get_by(clauses) do + def all() do end def update(item, attrs) do @@ -658,6 +660,9 @@ defmodule Append.AppendOnlyLog do def get(id) do end + def all() do + end + def update(item, attrs) do end end @@ -678,34 +683,35 @@ Finished in 0.1 seconds 4 tests, 0 failures ``` -#### 4.2 Get/Get By +#### 4.2 Get/All Now that we've done the _hard parts_, we'll implement the rest of the functionality for our Append Only Log. -The `get` and `get by` functions should be fairly simple, we just need to forward -the requests to the Repo. But first, a test. +The `get` and `all` functions should be fairly simple, we just need to forward +the requests to the Repo. But first, some tests. ``` elixir defmodule Append.AddressTest do ... - describe "get item from database" do + describe "get items from database" do test "get/1" do {:ok, item} = insert_address() assert Address.get(item.id) == item end - test "get_by/1" do + test "all/0" do {:ok, item} = insert_address() + {:ok, item_2} = insert_address("Loki") - assert Address.get_by(name: "Thor") == item + assert length(Address.all()) == 2 end end - def insert_address do + def insert_address(name \\ "Thor") do Address.insert(%{ - name: "Thor", + name: name, address_line_1: "The Hall", address_line_2: "Valhalla", city: "Asgard", @@ -731,8 +737,8 @@ defmodule Append.AppendOnlyLog do Repo.get(__MODULE__, id) end - def get_by(clauses) do - Repo.get_by(__MODULE__, clauses) + def all do + Repo.all(__MODULE__) end ... end @@ -919,6 +925,8 @@ We need to "clear" the `:id` field _before_ attempting to update (insert): def update(%__MODULE__{} = item, attrs) do item |> Map.put(:id, nil) + |> Map.put(:inserted_at, nil) + |> Map.put(:updated_at, nil) |> __MODULE__.changeset(attrs) |> Repo.insert() end @@ -927,10 +935,153 @@ end So here we remove the original autogenerated id from the existing item, preventing us from duplicating it in the database. +We also remove the `:inserted_at` and `:updated_at` fields. Again, if we leave those in, they'll be copied over from the old item, instead of being newly generated. + +Now we'll add some more tests, making sure our code so far is working as we expect it to: + +``` elixir +defmodule Append.AddressTest do + ... + test "get updated item" do + {:ok, item} = insert_address() + + {:ok, updated_item} = Address.update(item, %{tel: "0123444444"}) + + assert Address.get(item.id) == updated_item + end + + test "all/0 does not include old items" do + {:ok, item} = insert_address() + {:ok, _} = insert_address("Loki") + {:ok, _} = Address.update(item, %{postcode: "W2 3EC"}) + + assert length(Address.all()) == 2 + end + ... +end +``` + +Here we're testing that the items we receive from our 'get' and 'all' functions are the new, updated items. + +Run this test and... + +``` elixir +1) test get updated item (Append.AddressTest) + test/append/address_test.exs:34 + Assertion with == failed + code: assert Address.get(item.id()) == updated_item + left: %Append.Address{... tel: "0800123123"} + right: %Append.Address{... tel: "0123444444"} + stacktrace: + test/append/address_test.exs:39: (test) + +2) test all/0 does not include old items (Append.AddressTest) + test/append/address_test.exs:43 + Assertion with == failed + code: assert length(Address.all()) == 2 + left: 3 + right: 2 + stacktrace: + test/append/address_test.exs:48: (test) +``` + +We're still getting the old items. + +To fix this we'll have to revisit our `get` function. + +``` elixir +def get(id) do + Repo.get(__MODULE__, id) +end +``` + +The first issue is that we're still using the `id` to get the item. As we know, this `id` will always reference the same `version` of the item, meaning no matter how many times we update it, the `id` will still point to the original, unmodified item. + +Luckily, we have another way to reference the item. Our `entry_id` that we created earlier. Let's use that in our query: + +``` elixir +def get(entry_id) do + query = + from( + m in __MODULE__, + where: m.entry_id == ^entry_id, + select: m + ) + + Repo.one(query) +end +``` + +Don't forget to update the tests too: + +``` elixir +... +test "get/1" do + {:ok, item} = insert_address() + + assert Address.get(item.entry_id) == item +end +... +test "get updated item" do + {:ok, item} = insert_address() + + {:ok, updated_item} = Address.update(item, %{tel: "0123444444"}) + + assert Address.get(item.entry_id) == updated_item +end +... +``` + +Then we'll run the tests: + +``` elixir +test get updated item (Append.AddressTest) + test/append/address_test.exs:34 + ** (Ecto.MultipleResultsError) expected at most one result but got 2 in query: +``` + +Another error: "expected at most one result but got 2 in query" + +Of course, this makes sense, we have two items with that entry id, but we only want one. The most recent one. Let's modify our query further: + +``` elixir +def get(entry_id) do + query = + from( + m in __MODULE__, + where: m.entry_id == ^entry_id, + order_by: [desc: :inserted_at], + limit: 1, + select: m + ) + + Repo.one(query) +end +``` + +This will order our items in descending order by the inserted date, and take the most recent one. + +We'll use the same query in our `all` function, but replacing the `limit: 1` with `distinct: entry_id`: + +``` elixir +def all do + sub = + from(m in __MODULE__, + distinct: m.entry_id, + order_by: [desc: :inserted_at], + select: m + ) + + Repo.all(query) +end +``` + +This ensures we get more than one item, but only the most recent of each `entry_id`. + #### 4.4 Get history -The final part of our append-only database will be the functionality +A useful 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: @@ -972,7 +1123,7 @@ defmodule Append.AppendOnlyLog do ... defmacro __before_compile__(_env) do quote do - import Ecto.Query, only: [from: 2] + import Ecto.Query ... def get_history(%__MODULE__{} = item) do @@ -996,3 +1147,123 @@ where we end up calling it. Now run your tests, and you'll see that we're now able to view the whole history of the changes of all items in our database. + +#### 4.5 Delete + +As you may realise, even though we are using an append only database, we still need some way to "delete" items. + +Of course they won't actually be deleted, merely marked as "inactive", so they don't show anywhere unless we specifically want them to (For example in our history function). + +To implement this functionality, we'll need to add a field to our schema, and to the cast function in our changeset. + +``` elixir +defmodule Append.Address do + schema "addresses" do + ... + field(:deleted, :boolean, default: false) + ... + end + + def changeset(address, attrs) do + address + |> insert_entry_id() + |> cast(attrs, [ + ..., + :deleted + ]) + |> validate_required([ + ... + ]) + end +end +``` + +and a new migration: +``` +mix ecto.gen.migration add_deleted +``` + +``` elixir +defmodule Append.Repo.Migrations.AddDeleted do + use Ecto.Migration + + def change do + alter table("addresses") do + add(:deleted, :boolean, default: false) + end + end +end +``` + +This adds a `boolean` field, with a default value of `false`. We'll use this to determine whether a given item is "deleted" or not. + +As usual, before we implement it, we'll add a test for our expected functionality. + +``` elixir +describe "delete:" do + test "deleted items are not retrieved with 'get'" do + {:ok, item} = insert_address() + {:ok, _} = Address.delete(item) + + assert Address.get(item.entry_id) == nil + end + + test "deleted items are not retrieved with 'all'" do + {:ok, item} = insert_address() + {:ok, _} = Address.delete(item) + + assert length(Address.all()) == 0 + end + end +``` + +Our delete function is fairly simple: + +``` elixir +def delete(%__MODULE__{} = item) do + item + |> Map.put(:id, nil) + |> Map.put(:inserted_at, nil) + |> Map.put(:updated_at, nil) + |> __MODULE__.changeset(%{deleted: true}) + |> Repo.insert() +end +``` + +It acts just the same as the update function, but adds a value of `deleted: true`. But this is only half of the story. + +We also need to make sure we don't return any deleted items when they're requested. So again, we'll have to edit our `get` and `all` functions: + +``` elixir +def get(entry_id) do + sub = + from( + m in __MODULE__, + where: m.entry_id == ^entry_id, + order_by: [desc: :inserted_at], + limit: 1, + select: m + ) + + query = from(m in subquery(sub), where: not m.deleted, select: m) + + Repo.one(query) +end + +def all do + sub = + from(m in __MODULE__, + distinct: m.entry_id, + order_by: [desc: :inserted_at], + select: m + ) + + query = from(m in subquery(sub), where: not m.deleted, select: m) + + Repo.all(query) +end +``` + +What we're doing here is taking our original query, then performing another query on the result of that, only returning the item if it has not been marked as `deleted`. + +So now, when we run our tests, we should see that we're succesfully ignoring "deleted" items. \ No newline at end of file diff --git a/lib/append/address.ex b/lib/append/address.ex index d447d4a..47b28be 100644 --- a/lib/append/address.ex +++ b/lib/append/address.ex @@ -11,6 +11,7 @@ defmodule Append.Address do field(:postcode, :string) field(:tel, :string) field(:entry_id, :string) + field(:deleted, :boolean, default: false) timestamps() end @@ -19,8 +20,25 @@ defmodule Append.Address do def changeset(address, attrs) do address |> insert_entry_id() - |> cast(attrs, [:name, :address_line_1, :address_line_2, :city, :postcode, :tel, :entry_id]) - |> validate_required([:name, :address_line_1, :address_line_2, :city, :postcode, :tel, :entry_id]) + |> cast(attrs, [ + :name, + :address_line_1, + :address_line_2, + :city, + :postcode, + :tel, + :entry_id, + :deleted + ]) + |> validate_required([ + :name, + :address_line_1, + :address_line_2, + :city, + :postcode, + :tel, + :entry_id + ]) end def insert_entry_id(address) do diff --git a/lib/append/append_only_log.ex b/lib/append/append_only_log.ex index 29e4cff..b8e059d 100644 --- a/lib/append/append_only_log.ex +++ b/lib/append/append_only_log.ex @@ -8,7 +8,7 @@ defmodule Append.AppendOnlyLog do @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() + @callback all() :: [Ecto.Schema.t()] @callback update(Ecto.Schema.t(), struct) :: {:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()} @callback get_history(Ecto.Schema.t()) :: [Ecto.Schema.t()] | no_return() @@ -24,7 +24,7 @@ defmodule Append.AppendOnlyLog do defmacro __before_compile__(_env) do quote do - import Ecto.Query, only: [from: 2] + import Ecto.Query def insert(attrs) do %__MODULE__{} @@ -32,28 +32,61 @@ defmodule Append.AppendOnlyLog do |> Repo.insert() end - def get(id) do - Repo.get(__MODULE__, id) + def get(entry_id) do + sub = + from( + m in __MODULE__, + where: m.entry_id == ^entry_id, + order_by: [desc: :inserted_at], + limit: 1, + select: m + ) + + query = from(m in subquery(sub), where: not m.deleted, select: m) + + Repo.one(query) end - def get_by(clauses) do - Repo.get_by(__MODULE__, clauses) + def all do + sub = + from(m in __MODULE__, + distinct: m.entry_id, + order_by: [desc: :inserted_at], + select: m + ) + + query = from(m in subquery(sub), where: not m.deleted, select: m) + + Repo.all(query) end def update(%__MODULE__{} = item, attrs) do item |> Map.put(:id, nil) + |> Map.put(:inserted_at, nil) + |> Map.put(:updated_at, nil) |> __MODULE__.changeset(attrs) |> Repo.insert() end def get_history(%__MODULE__{} = item) do - query = from m in __MODULE__, - where: m.entry_id == ^item.entry_id, - select: m + query = + from(m in __MODULE__, + where: m.entry_id == ^item.entry_id, + select: m + ) Repo.all(query) end + + def delete(%__MODULE__{} = item) do + item + |> Map.put(:id, nil) + |> Map.put(:inserted_at, nil) + |> Map.put(:updated_at, nil) + |> __MODULE__.changeset(%{deleted: true}) + |> Repo.insert() + end end end end diff --git a/mix.lock b/mix.lock index 35fc4d5..5385d33 100644 --- a/mix.lock +++ b/mix.lock @@ -1,22 +1,22 @@ %{ - "certifi": {:hex, :certifi, "2.4.2", "75424ff0f3baaccfd34b1214184b6ef616d89e420b258bb0a5ea7d7bc628f7f0", [], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, + "certifi": {:hex, :certifi, "2.4.2", "75424ff0f3baaccfd34b1214184b6ef616d89e420b258bb0a5ea7d7bc628f7f0", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"}, "db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, "decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"}, "ecto": {:hex, :ecto, "2.2.10", "e7366dc82f48f8dd78fcbf3ab50985ceeb11cb3dc93435147c6e13f2cda0992e", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, - "excoveralls": {:hex, :excoveralls, "0.7.5", "339e433e5d3bce09400dc8de7b9040741a409c93917849916c136a0f51fdc183", [], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, - "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"}, + "excoveralls": {:hex, :excoveralls, "0.7.5", "339e433e5d3bce09400dc8de7b9040741a409c93917849916c136a0f51fdc183", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, + "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"}, "file_system": {:hex, :file_system, "0.2.6", "fd4dc3af89b9ab1dc8ccbcc214a0e60c41f34be251d9307920748a14bf41f1d3", [:mix], [], "hexpm"}, "gettext": {:hex, :gettext, "0.16.0", "4a7e90408cef5f1bf57c5a39e2db8c372a906031cc9b1466e963101cb927dafc", [:mix], [], "hexpm"}, - "hackney": {:hex, :hackney, "1.14.0", "66e29e78feba52176c3a4213d42b29bdc4baff93a18cfe480f73b04677139dee", [], [{:certifi, "2.4.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, - "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, - "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [], [], "hexpm"}, - "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [], [], "hexpm"}, + "hackney": {:hex, :hackney, "1.14.0", "66e29e78feba52176c3a4213d42b29bdc4baff93a18cfe480f73b04677139dee", [:rebar3], [{:certifi, "2.4.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, + "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, + "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, "mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"}, - "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [], [], "hexpm"}, - "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [], [], "hexpm"}, + "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, + "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, "phoenix": {:hex, :phoenix, "1.3.4", "aaa1b55e5523083a877bcbe9886d9ee180bf2c8754905323493c2ac325903dc5", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_ecto": {:hex, :phoenix_ecto, "3.4.0", "91cd39427006fe4b5588d69f0941b9c3d3d8f5e6477c563a08379de7de2b0c58", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_html": {:hex, :phoenix_html, "2.12.0", "1fb3c2e48b4b66d75564d8d63df6d53655469216d6b553e7e14ced2b46f97622", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, @@ -27,6 +27,6 @@ "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"}, "postgrex": {:hex, :postgrex, "0.13.5", "3d931aba29363e1443da167a4b12f06dcd171103c424de15e5f3fc2ba3e6d9c5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"}, "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [], [], "hexpm"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [], [], "hexpm"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, } diff --git a/priv/repo/migrations/20181031160106_add_deleted.exs b/priv/repo/migrations/20181031160106_add_deleted.exs new file mode 100644 index 0000000..e6656cb --- /dev/null +++ b/priv/repo/migrations/20181031160106_add_deleted.exs @@ -0,0 +1,9 @@ +defmodule Append.Repo.Migrations.AddDeleted do + use Ecto.Migration + + def change do + alter table("addresses") do + add(:deleted, :boolean, default: false) + end + end +end diff --git a/test/append/address_test.exs b/test/append/address_test.exs index 4c0122b..7dc4181 100644 --- a/test/append/address_test.exs +++ b/test/append/address_test.exs @@ -8,17 +8,18 @@ defmodule Append.AddressTest do assert item.name == "Thor" end - describe "get item from database" do + describe "get items from database" do test "get/1" do {:ok, item} = insert_address() - assert Address.get(item.id) == item + assert Address.get(item.entry_id) == item end - test "get_by/1" do - {:ok, item} = insert_address() + test "all/0" do + {:ok, _} = insert_address() + {:ok, _} = insert_address("Loki") - assert Address.get_by(name: "Thor") == item + assert length(Address.all()) == 2 end end @@ -31,15 +32,32 @@ defmodule Append.AddressTest do assert updated_item.tel != item.tel end + test "get updated item" do + {:ok, item} = insert_address() + + {:ok, updated_item} = Address.update(item, %{tel: "0123444444"}) + + assert Address.get(item.entry_id) == updated_item + end + + test "all/0 does not include old items" do + {:ok, item} = insert_address() + {:ok, _} = insert_address("Loki") + {:ok, _} = Address.update(item, %{postcode: "W2 3EC"}) + + assert length(Address.all()) == 2 + end + test "get history of item" do {:ok, item} = insert_address() - {:ok, updated_item} = Address.update(item, %{ - address_line_1: "12", - address_line_2: "Kvadraturen", - city: "Oslo", - postcode: "NW SCA", - }) + {:ok, updated_item} = + Address.update(item, %{ + address_line_1: "12", + address_line_2: "Kvadraturen", + city: "Oslo", + postcode: "NW SCA" + }) history = Address.get_history(updated_item) @@ -49,9 +67,9 @@ defmodule Append.AddressTest do assert Map.fetch(h2, :city) == {:ok, "Oslo"} end - def insert_address do + def insert_address(name \\ "Thor") do Address.insert(%{ - name: "Thor", + name: name, address_line_1: "The Hall", address_line_2: "Valhalla", city: "Asgard", @@ -59,4 +77,20 @@ defmodule Append.AddressTest do tel: "0800123123" }) end + + describe "delete:" do + test "deleted items are not retrieved with 'get'" do + {:ok, item} = insert_address() + {:ok, _} = Address.delete(item) + + assert Address.get(item.entry_id) == nil + end + + test "deleted items are not retrieved with 'all'" do + {:ok, item} = insert_address() + {:ok, _} = Address.delete(item) + + assert length(Address.all()) == 0 + end + end end