diff --git a/README.md b/README.md index 51790968..d7bef2b9 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 on error(it matches Repo.insert\2): + # {:error, Ecto.Changeset, # valid?: false>, %{}} @@ -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 on error(it matches Repo.update\2): + # {:error, Ecto.Changeset, # valid?: false>, %{}} @@ -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>}, @@ -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"}, @@ -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 @@ -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``` @@ -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 @@ -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 diff --git a/test/paper_trail/version_queries_test.exs b/test/paper_trail/version_queries_test.exs index c3c5e507..5a1726fa 100644 --- a/test/paper_trail/version_queries_test.exs +++ b/test/paper_trail/version_queries_test.exs @@ -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, @@ -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 diff --git a/test/paper_trail_strict_mode_test.exs b/test/paper_trail_strict_mode_test.exs index 51b38f11..555028d0 100644 --- a/test/paper_trail_strict_mode_test.exs +++ b/test/paper_trail_strict_mode_test.exs @@ -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" }) @@ -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() @@ -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 @@ -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: %{ diff --git a/test/paper_trail_test.exs b/test/paper_trail_test.exs index a7b132ce..32509e06 100644 --- a/test/paper_trail_test.exs +++ b/test/paper_trail_test.exs @@ -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" }) @@ -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() @@ -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), @@ -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: %{