Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add multi tenancy capabilities with ecto meta prefix #25

Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e9f28cb
Add new add_prefix function in paper_trail module
dreamingechoes Jul 27, 2017
c27bbc8
Add default value for the new :prefix option on the functions definit…
dreamingechoes Jul 27, 2017
61737dc
Add patter matching case for nil prefix on add_prefix function
dreamingechoes Jul 27, 2017
d4d8e40
Add defdelegate in PaperTrail module to PaperTrail.VersionQueries
dreamingechoes Jul 28, 2017
59f34c4
Add new definition for version_query function to merge options into t…
dreamingechoes Jul 28, 2017
e71b3d4
Add new definitions for all the functions in PaperTrail.VersionQueries
dreamingechoes Jul 28, 2017
ad52cb4
Remove result type from PaperTrail.VersionQueries and specifies the v…
dreamingechoes Jul 28, 2017
83a0d7a
Change param names in defdelegates for better readability
dreamingechoes Jul 28, 2017
4ef58a0
Add some multi tenancy documentation on README.md
dreamingechoes Jul 28, 2017
696a6c3
Add new MultiTenantHelper module into example test support folder
dreamingechoes Aug 1, 2017
a50177a
Add new MultiTenantHelper module into lib test support folder
dreamingechoes Aug 1, 2017
f2519db
Add new QueryHelper module into example test support folder
dreamingechoes Aug 1, 2017
c62150e
Add new ChangesetHelper module into example test support folder
dreamingechoes Aug 1, 2017
2c4aa4d
Require new support helpers in example test_helper file
dreamingechoes Aug 1, 2017
9bd355f
Add refactor in example company test in order to use helpers
dreamingechoes Aug 1, 2017
77b669c
Add refactor in example person test in order to use helpers
dreamingechoes Aug 1, 2017
4975d2b
Add new multi_tenant_company_test file
dreamingechoes Aug 1, 2017
96ecccd
Add new multi_tenant_person_test file
dreamingechoes Aug 1, 2017
d01fedf
Add tests for multi tenant in bang_functions_simple_mode_test
dreamingechoes Aug 1, 2017
95b3312
Add tests for multi tenant in bang_functions_strict_mode_test
dreamingechoes Aug 1, 2017
8a2dfb5
Add new count functions for multi tenancy in test support models
dreamingechoes Aug 1, 2017
a1ffd44
Add new functions for multi tenancy in lib/version.ex
dreamingechoes Aug 1, 2017
8a42220
Add changes in tests to use new definition of Version functions with …
dreamingechoes Aug 1, 2017
cf638d6
Require new support helper in lib test_helper file
dreamingechoes Aug 1, 2017
b914cfd
Add multi tenancy tests in paper_trail_version_test file
dreamingechoes Aug 1, 2017
7414a66
Add some changes to the previous code added in paper_trail_version_te…
dreamingechoes Aug 1, 2017
5f51b7e
Add multi tenancy tests in version_queries_test file
dreamingechoes Aug 1, 2017
c50eef8
Add dreamingechoes to credits section in README.md
dreamingechoes Aug 1, 2017
0604631
Remove changeset_helper.exs and query_helper.exs from example/test/su…
dreamingechoes Aug 5, 2017
ead9cc6
Add final refactor to example app test files
dreamingechoes Aug 6, 2017
64231d7
Propagate options to repo actions in delete functions
dreamingechoes Aug 9, 2017
1359026
Add some additional assertions in tests
dreamingechoes Aug 9, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,58 @@ Bang functions assume the operation will always be successful, otherwise functio
# "updated_at" => "2016-09-15T22:00:59"},
# item_id: 1, item_type: "Post", originator_id: nil, originator: nil, meta: nil}
```

## Working with multi tenancy

Sometimes you have to deal with applications where you need multi tenancy capabilities,
and you want to keep tracking of the versions of your data on different schemas (PostgreSQL)
or databases (MySQL).

You can use the [Ecto.Query prefix](https://hexdocs.pm/ecto/Ecto.Query.html#module-query-prefix)
in order to switch between different schemas/databases for your own data, so
you can specify in your changeset where to store your record. Example:

```elixir
tenant = "tenant_id"
changeset = User.changeset(%User{}, %{first_name: "Izel", last_name: "Nakri"})

changeset
|> Ecto.Queryable.to_query()
|> Map.put(:prefix, tenant)
|> Repo.insert()
```

PaperTrail also allows you to store the `Version` entries generated by your activity in
different schemas/databases by using the value of the element `:prefix` on the options
of the functions. Example:

```elixir
tenant = "tenant_id"

changeset =
User.changeset(%User{}, %{first_name: "Izel", last_name: "Nakri"})
|> Ecto.Queryable.to_query()
|> Map.put(:prefix, tenant)

PaperTrail.insert(changeset, [prefix: tenant])
```

By doing this, you're storing the new `User` entry into the schema/database
specified by the `:prefix` value (`tenant_id`). Notice that the `User`'s changeset it's sent
with the `:prefix` already set properly, so PaperTrail **only will take care of the
storage of the generated `Version` entry in the desired schema/database**, be sure
to add this info to your changeset before the execution of the PaperTrail function.

PaperTrail can get versions of records or models from different schemas/databases as well
by using the value of the element `:prefix` on the options of the functions. Example:

```elixir
tenant = "tenant_id"
id = 1

PaperTrail.get_versions(User, id, [prefix: tenant])
```

## Suggestions
- PaperTrail.Version(s) order matter,
- don't delete your paper_trail versions, instead you can merge them
Expand All @@ -392,6 +444,7 @@ Many thanks to:
- [Jason Draper](https://github.com/drapergeek) - UUID primary keys feature
- [Josh Taylor](https://github.com/joshuataylor) - Maintenance and new feature suggestions
- [Mitchell Henke](https://github.com/mitchellhenke) - Fixed weird elixir compiler warnings
- [Iván González](https://github.com/dreamingechoes) - Multi tenancy feature and some minor refactors
- [Izel Nakri](https://github.com/izelnakri) - The Originator of this library. See what I did there ;)

Additional thanks to:
Expand Down
100 changes: 50 additions & 50 deletions example/test/company_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,23 @@ defmodule CompanyTest do
end

test "creating a company creates a company version with correct attributes" do
new_company = Company.changeset(%Company{}, %{
name: "Acme LLC", is_active: true, city: "Greenwich", people: []
})

{:ok, result} = PaperTrail.insert(new_company, origin: "test")

company_count = Repo.all(
from company in Company,
select: count(company.id)
)
{:ok, result} =
%Company{}
|> Company.changeset(%{name: "Acme LLC", is_active: true, city: "Greenwich", people: []})
|> PaperTrail.insert(origin: "test")

company_count =
from(company in Company, select: count(company.id))
|> Repo.all()
version_count =
from(version in PaperTrail.Version, select: count(version.id))
|> Repo.all()
first_company =
first(Company, :id)
|> preload(:people)
|> Repo.one()

company = result[:model] |> Map.drop([:__meta__, :__struct__, :inserted_at, :updated_at, :id])

version_count = Repo.all(
from version in PaperTrail.Version,
select: count(version.id)
)

version = result[:version] |> Map.drop([:__meta__, :__struct__, :inserted_at])

assert company_count == [1]
Expand All @@ -50,7 +49,7 @@ defmodule CompanyTest do
assert Map.drop(version, [:id]) == %{
event: "insert",
item_type: "Company",
item_id: Repo.one(first(Company, :id)).id,
item_id: first_company.id,
item_changes: Map.drop(result[:model], [:__meta__, :__struct__, :people]),
origin: "test",
originator_id: nil,
Expand All @@ -59,27 +58,27 @@ defmodule CompanyTest do
end

test "updating a company creates a company version with correct item_changes" do
old_company = first(Company, :id) |> preload(:people) |> Repo.one
new_company = Company.changeset(old_company, %{
city: "Hong Kong",
website: "http://www.acme.com",
facebook: "acme.llc"
})

{:ok, result} = PaperTrail.update(new_company)
first_company =
first(Company, :id)
|> preload(:people)
|> Repo.one()

{:ok, result} =
first_company
|> Company.changeset(%{
city: "Hong Kong",
website: "http://www.acme.com",
facebook: "acme.llc"
}) |> PaperTrail.update()

company_count = Repo.all(
from company in Company,
select: count(company.id)
)
company_count =
from(company in Company, select: count(company.id))
|> Repo.all()
version_count =
from(version in PaperTrail.Version, select: count(version.id))
|> Repo.all()

company = result[:model] |> Map.drop([:__meta__, :__struct__, :inserted_at, :updated_at, :id])

version_count = Repo.all(
from version in PaperTrail.Version,
select: count(version.id)
)

version = result[:version] |> Map.drop([:__meta__, :__struct__, :inserted_at])

assert company_count == [1]
Expand All @@ -100,7 +99,7 @@ defmodule CompanyTest do
assert Map.drop(version, [:id]) == %{
event: "update",
item_type: "Company",
item_id: Repo.one(first(Company, :id)).id,
item_id: first_company.id,
item_changes: %{city: "Hong Kong", website: "http://www.acme.com", facebook: "acme.llc"},
origin: nil,
originator_id: nil,
Expand All @@ -109,22 +108,23 @@ defmodule CompanyTest do
end

test "deleting a company creates a company version with correct attributes" do
company = first(Company, :id) |> preload(:people) |> Repo.one

{:ok, result} = PaperTrail.delete(company)

company_count = Repo.all(
from company in Company,
select: count(company.id)
)
company =
first(Company, :id)
|> preload(:people)
|> Repo.one()

{:ok, result} =
company
|> PaperTrail.delete()

company_count =
from(company in Company, select: count(company.id))
|> Repo.all()
version_count =
from(version in PaperTrail.Version, select: count(version.id))
|> Repo.all()

company_ref = result[:model] |> Map.drop([:__meta__, :__struct__, :inserted_at, :updated_at, :id])

version_count = Repo.all(
from version in PaperTrail.Version,
select: count(version.id)
)

version = result[:version] |> Map.drop([:__meta__, :__struct__, :inserted_at])

assert company_count == [0]
Expand Down
175 changes: 175 additions & 0 deletions example/test/multi_tenant_company_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
defmodule MultiTenantCompanyTest do
use ExUnit.Case
import Ecto.Query

setup_all do
MultiTenantHelper.setup_tenant(Repo)
:ok
end

test "[multi tenant] creating a company creates a company version with correct attributes" do
{:ok, result} =
%Company{}
|> Company.changeset(%{name: "Acme LLC", is_active: true, city: "Greenwich", people: []})
|> MultiTenantHelper.add_prefix_to_changeset()
|> PaperTrail.insert(origin: "test", prefix: MultiTenantHelper.tenant())

company_count =
from(company in Company, select: count(company.id))
|> MultiTenantHelper.add_prefix_to_query()
|> Repo.all()
version_count =
from(version in PaperTrail.Version, select: count(version.id))
|> MultiTenantHelper.add_prefix_to_query()
|> Repo.all()
first_company =
first(Company, :id)
|> preload(:people)
|> MultiTenantHelper.add_prefix_to_query()
|> Repo.one()

company = result[:model] |> Map.drop([:__meta__, :__struct__, :inserted_at, :updated_at, :id])
version = result[:version] |> Map.drop([:__meta__, :__struct__, :inserted_at])

assert company_count == [1]
assert version_count == [1]

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Version.count == [0]

assert company == %{
name: "Acme LLC",
is_active: true,
city: "Greenwich",
website: nil,
address: nil,
facebook: nil,
twitter: nil,
founded_in: nil,
people: []
}

assert Map.drop(version, [:id]) == %{
event: "insert",
item_type: "Company",
item_id: first_company.id,
item_changes: Map.drop(result[:model], [:__meta__, :__struct__, :people]),
origin: "test",
originator_id: nil,
meta: nil
}
end

test "[multi tenant] updating a company creates a company version with correct item_changes" do
first_company =
first(Company, :id)
|> preload(:people)
|> MultiTenantHelper.add_prefix_to_query()
|> Repo.one()

{:ok, result} =
first_company
|> Company.changeset(%{
city: "Hong Kong",
website: "http://www.acme.com",
facebook: "acme.llc"
})
|> MultiTenantHelper.add_prefix_to_changeset()
|> PaperTrail.update(prefix: MultiTenantHelper.tenant())

company_count =
from(company in Company, select: count(company.id))
|> MultiTenantHelper.add_prefix_to_query()
|> Repo.all()
version_count =
from(version in PaperTrail.Version, select: count(version.id))
|> MultiTenantHelper.add_prefix_to_query()
|> Repo.all()

company = result[:model] |> Map.drop([:__meta__, :__struct__, :inserted_at, :updated_at, :id])
version = result[:version] |> Map.drop([:__meta__, :__struct__, :inserted_at])

assert company_count == [1]
assert version_count == [2]

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert Version.count == [0]

assert company == %{
name: "Acme LLC",
is_active: true,
city: "Hong Kong",
website: "http://www.acme.com",
address: nil,
facebook: "acme.llc",
twitter: nil,
founded_in: nil,
people: []
}

assert Map.drop(version, [:id]) == %{
event: "update",
item_type: "Company",
item_id: first_company.id,
item_changes: %{city: "Hong Kong", website: "http://www.acme.com", facebook: "acme.llc"},
origin: nil,
originator_id: nil,
meta: nil
}
end

test "[multi tenant] deleting a company creates a company version with correct attributes" do
company =
first(Company, :id)
|> preload(:people)
|> MultiTenantHelper.add_prefix_to_query()
|> Repo.one()

{:ok, result} =
company
|> PaperTrail.delete(prefix: MultiTenantHelper.tenant())

company_count =
from(company in Company, select: count(company.id))
|> MultiTenantHelper.add_prefix_to_query()
|> Repo.all()
version_count =
from(version in PaperTrail.Version, select: count(version.id))
|> MultiTenantHelper.add_prefix_to_query()
|> Repo.all()

company_ref = result[:model] |> Map.drop([:__meta__, :__struct__, :inserted_at, :updated_at, :id])
version = result[:version] |> Map.drop([:__meta__, :__struct__, :inserted_at])

assert company_count == [0]
assert version_count == [3]

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert Version.count == [0]

assert company_ref == %{
name: "Acme LLC",
is_active: true,
city: "Hong Kong",
website: "http://www.acme.com",
address: nil,
facebook: "acme.llc",
twitter: nil,
founded_in: nil,
people: []
}

assert Map.drop(version, [:id]) == %{
event: "delete",
item_type: "Company",
item_id: company.id,
item_changes: %{
id: company.id,
inserted_at: company.inserted_at,
updated_at: company.updated_at,
name: "Acme LLC",
is_active: true,
website: "http://www.acme.com",
city: "Hong Kong",
address: nil,
facebook: "acme.llc",
twitter: nil,
founded_in: nil
},
origin: nil,
originator_id: nil,
meta: nil
}
end
end
Loading