Skip to content

Commit

Permalink
test and documentation update
Browse files Browse the repository at this point in the history
  • Loading branch information
izelnakri committed Mar 13, 2017
1 parent 1b2cb7b commit 8a851b4
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 29 deletions.
35 changes: 15 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

# How does it work?

PaperTrail lets you record every change in your database in a seperate database table called ```versions```. Library generates a new version record with associated data every time you run ```PaperTrail.insert/1```, ```PaperTrail.update/1``` or ```PaperTrail.delete/1``` functions. Simply these functions wrap your Repo insert, update or destroy actions in a database transaction, so if your database action fails you won't get a new version.
PaperTrail lets you record every change in your database in a separate database table called ```versions```. Library generates a new version record with associated data every time you run ```PaperTrail.insert/1```, ```PaperTrail.update/1``` or ```PaperTrail.delete/1``` functions. Simply these functions wrap your Repo insert, update or destroy actions in a database transaction, so if your database action fails you won't get a new version.

PaperTrail is assailed with tests for each release. Data integrity is an important purpose of this project, please refer to the strict_mode if you want to ensure data correctness and integrity of your versions. For simpler use cases the default mode of PaperTrail should suffice.

## Example

Expand All @@ -20,15 +22,14 @@ PaperTrail lets you record every change in your database in a seperate database
# content: "You should try it now!", id: 1, inserted_at: #Ecto.DateTime<2016-09-15 21:42:38>,
# updated_at: #Ecto.DateTime<2016-09-15 21:42:38>},
# version: %PaperTrail.Version{__meta__: #Ecto.Schema.Metadata<:loaded, "versions">,
# event: "create", id: 1, inserted_at: #Ecto.DateTime<2016-09-15 21:42:38>,
# event: "insert", id: 1, inserted_at: #Ecto.DateTime<2016-09-15 21:42:38>,
# item_changes: %{title: "Word on the street is Elixir got its own database versioning library",
# content: "You should try it now!", id: 1, inserted_at: #Ecto.DateTime<2016-09-15 21:42:38>,
# updated_at: #Ecto.DateTime<2016-09-15 21:42:38>},
# item_id: 1, item_type: "Post", meta: nil}}}

# => on error:
# {:error, :model,
# Ecto.Changeset<action: :insert,
# => on error(it matches Repo.insert\2):
# {:error, Ecto.Changeset<action: :insert,
# changes: %{title: "Word on the street is Elixir got its own database versioning library", content: "You should try it now!"},
# errors: [content: {"is too short", []}], data: #Post<>,
# valid?: false>, %{}}
Expand All @@ -52,9 +53,8 @@ PaperTrail lets you record every change in your database in a seperate database
# item_id: 1, item_type: "Post",
# meta: nil}}}

# => on error:
# {:error, :model,
# Ecto.Changeset<action: :update,
# => on error(it matches Repo.update\2):
# {:error, Ecto.Changeset<action: :update,
# changes: %{title: "Elixir matures fast", content: "Future is already here, you deserve to be awesome!"},
# errors: [title: {"is too short", []}], data: #Post<>,
# valid?: false>, %{}}
Expand All @@ -75,7 +75,7 @@ PaperTrail lets you record every change in your database in a seperate database
# id: 1, inserted_at: #Ecto.DateTime<2016-09-15 21:42:38>,
# updated_at: #Ecto.DateTime<2016-09-15 22:00:59>},
# version: %PaperTrail.Version{__meta__: #Ecto.Schema.Metadata<:loaded, "versions">,
# event: "destroy", id: 3, inserted_at: #Ecto.DateTime<2016-09-15 22:22:12>,
# event: "delete", id: 3, inserted_at: #Ecto.DateTime<2016-09-15 22:22:12>,
# item_changes: %{title: "Elixir matures fast", content: "Future is already here, you deserve to be awesome!",
# id: 1, inserted_at: #Ecto.DateTime<2016-09-15 21:42:38>,
# updated_at: #Ecto.DateTime<2016-09-15 22:00:59>},
Expand All @@ -86,7 +86,7 @@ PaperTrail lets you record every change in your database in a seperate database

last(PaperTrail.Version, :id) |> Repo.one
# %PaperTrail.Version{__meta__: #Ecto.Schema.Metadata<:loaded, "versions">,
# event: "destroy", id: 3, inserted_at: #Ecto.DateTime<2016-09-15 22:22:12>,
# event: "delete", id: 3, inserted_at: #Ecto.DateTime<2016-09-15 22:22:12>,
# item_changes: %{"title" => "Elixir matures fast", content: "Future is already here, you deserve to be awesome!", "id" => 1,
# "inserted_at" => "2016-09-15T21:42:38",
# "updated_at" => "2016-09-15T22:00:59"},
Expand All @@ -95,7 +95,7 @@ PaperTrail lets you record every change in your database in a seperate database

PaperTrail is inspired by the ruby gem ```paper_trail```. However, unlike the ```paper_trail``` gem this library actually results in less data duplication, faster and more explicit programming model to version your record changes.

The library source code is minimal and tested. It is highly suggested that you check it out, it isn't rocket science.
The library source code is minimal and tested. It is highly suggested that you check it out.

## Installation

Expand All @@ -121,10 +121,6 @@ The library source code is minimal and tested. It is highly suggested that you c

```mix papertrail.install```

5. If you do not wish to use `:utc_datetime` for storing your timestamps, change the migration to `:naive_datetime`.
This was changed in Ecto 2.1, see the [CHANGELOG.md](https://github.com/elixir-ecto/ecto/blob/v2.1/CHANGELOG.md) for
more details.

5. run the migration:

```mix ecto.migrate```
Expand All @@ -138,15 +134,15 @@ YES! Make sure you do the steps.
# Introduction of Strict mode
This is a feature needed for larger applications where every change needs to have an owner reference. This mode adds the following behavior:

1 - PaperTrail records get a required string field called ````originator```. PaperTrail.insert/1, PaperTrail.update/1, PaperTrail.delete/1 functions accepts a second argument for the originator. Example:
1 - PaperTrail records get a required string field called ````set_by```. PaperTrail.insert/1, PaperTrail.update/1, PaperTrail.delete/1 functions accepts a second argument for the originator. Example:
```elixir
PaperTrail.update(changeset, "migration")
# or:
PaperTrail.update(changeset, "user:1234")
```
If the originator field isn't provided originator field will be "unknown" by default.
If the set_by field isn't provided set_by field will be "unknown" by default.

2 - Strict mode expects tracked models to have foreign-key reference to their insert_version and current_versions(s). These columns should be named ```insert_version_id```, and ```current_version_id``` in their respective model tables. Example migration:
2 - Strict mode expects tracked models to have foreign-key reference to their insert_version and current_version. These columns should be named ```insert_version_id```, and ```current_version_id``` in their respective model tables. Example migration:

TODO: give here a migration example

Expand All @@ -159,9 +155,8 @@ When you delete a model, current_version_id gets updated during the transaction.
** explain the columns

## Storing version meta data
Your versions don't need a model lifecycle callbacks like before_create or before_update for any extra meta data, all your meta data could be stored in one object and that object could be passed as the second optional parameter to PaperTrail.insert || PaperTrail.update || PaperTrail.delete
Your versions don't need a model lifecycle callbacks like before_create or before_update for any extra meta data, all your meta data could be stored in one object and that object could be passed as the second optional parameter to PaperTrail.insert || PaperTrail.update || PaperTrail.delete :

## Suggestions

- PaperTrail.Version(s) order matter,
- don't delete your paper_trail versions, instead you can merge them
4 changes: 2 additions & 2 deletions test/paper_trail/version_queries_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ defmodule PaperTrailTest.VersionQueries do
last_name: "Nakri",
gender: true,
company_id: company.id
}) |> PaperTrail.insert(%{originator: "admin"}) # add link name later on
}) |> PaperTrail.insert(set_by: "admin") # add link name later on

another_company = @repo.one(
from c in Company,
Expand All @@ -59,7 +59,7 @@ defmodule PaperTrailTest.VersionQueries do
visit_count: 10,
birthdate: ~D[1992-04-01],
company_id: another_company.id
}) |> PaperTrail.update(%{originator: "user:1", linkname: "izelnakri"})
}) |> PaperTrail.update(set_by: "user:1", meta: %{linkname: "izelnakri"})

:ok
end
Expand Down
12 changes: 8 additions & 4 deletions test/paper_trail_strict_mode_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,9 @@ defmodule PaperTrailStrictModeTest do
end

test "updating a person creates a person version with correct attributes" do
create_company_with_version(%{name: "Acme LLC", website: "http://www.acme.com"})
{:ok, insert_company_result} = create_company_with_version(%{
name: "Acme LLC", website: "http://www.acme.com"
})
{:ok, target_company_insertion} = create_company_with_version(%{
name: "Another Company Corp.", is_active: true, address: "Sesame street 100/3, 101010"
})
Expand All @@ -248,7 +250,8 @@ defmodule PaperTrailStrictModeTest do
{:ok, result} = Person.changeset(insert_person_result[:model], %{
first_name: "Isaac",
visit_count: 10,
birthdate: ~D[1992-04-01]
birthdate: ~D[1992-04-01],
company_id: insert_company_result[:model].id
}) |> PaperTrail.update(set_by: "scraper", meta: %{linkname: "izelnakri"})

person_count = Person.count()
Expand All @@ -261,7 +264,7 @@ defmodule PaperTrailStrictModeTest do
assert person_count == [1]
assert version_count == [4]
assert Map.drop(person, [:id, :inserted_at, :updated_at]) == %{
company_id: target_company_insertion[:model].id,
company_id: insert_company_result[:model].id,
first_name: "Isaac",
visit_count: 10,
birthdate: elem(Ecto.Date.cast(~D[1992-04-01]), 1), # this is the only problem
Expand All @@ -278,7 +281,8 @@ defmodule PaperTrailStrictModeTest do
first_name: "Isaac",
visit_count: 10,
birthdate: elem(Ecto.Date.cast(~D[1992-04-01]), 1),
current_version_id: version.id
current_version_id: version.id,
company_id: insert_company_result[:model].id
},
set_by: "scraper",
meta: %{
Expand Down
10 changes: 7 additions & 3 deletions test/paper_trail_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,9 @@ defmodule PaperTrailTest do
end

test "updating a person creates a person version with correct attributes" do
create_company_with_version(%{name: "Acme LLC", website: "http://www.acme.com"})
{:ok, initial_company_insertion} = create_company_with_version(%{
name: "Acme LLC", website: "http://www.acme.com"
})
{:ok, target_company_insertion} = create_company_with_version(%{
name: "Another Company Corp.", is_active: true, address: "Sesame street 100/3, 101010"
})
Expand All @@ -234,6 +236,7 @@ defmodule PaperTrailTest do
first_name: "Isaac",
visit_count: 10,
birthdate: ~D[1992-04-01],
company_id: initial_company_insertion[:model].id
}) |> PaperTrail.update(set_by: "scraper", meta: %{linkname: "izelnakri"})

person_count = Person.count()
Expand All @@ -246,7 +249,7 @@ defmodule PaperTrailTest do
assert person_count == [1]
assert version_count == [4]
assert Map.drop(person, [:id, :inserted_at, :updated_at]) == %{
company_id: target_company_insertion[:model].id,
company_id: initial_company_insertion[:model].id,
first_name: "Isaac",
visit_count: 10,
birthdate: elem(Ecto.Date.cast(~D[1992-04-01]), 1),
Expand All @@ -260,7 +263,8 @@ defmodule PaperTrailTest do
item_changes: %{
first_name: "Isaac",
visit_count: 10,
birthdate: elem(Ecto.Date.cast(~D[1992-04-01]), 1)
birthdate: elem(Ecto.Date.cast(~D[1992-04-01]), 1),
company_id: initial_company_insertion[:model].id
},
set_by: "scraper",
meta: %{
Expand Down

0 comments on commit 8a851b4

Please sign in to comment.