Skip to content

Commit

Permalink
structure_type/unique_identifier -> item_type/item_id
Browse files Browse the repository at this point in the history
  • Loading branch information
Qqwy committed Apr 25, 2017
1 parent beeb7f6 commit f9069a0
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 83 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ The lower-arity versions of Revisionair expect that you use structs that contain
is used to differentiate between different kinds of data (i.e. data types) that are stored, while the `:id` field is used
to differentiate between different entities of this data type.

However, this is fully configurable. Revisionair's functions can be called with custom `structure_type`s and `unique_identifier` fields,
However, this is fully configurable. Revisionair's functions can be called with custom `item_type`s and `item_id` fields,
and when you pass (arity-1) functions as these arguments, they are called on the passed structure. So this also works:

```elixir
Expand Down Expand Up @@ -92,6 +92,7 @@ be found at [https://hexdocs.pm/revisionair](https://hexdocs.pm/revisionair).

## Changelog

- 0.13 Renaming `structure_type` and `unique_identifier` parameter names to `item_type` and `item_id` respectively, for brevity/readability.
- 0.12 Possibility of passing extra options to the storage layer, under the `storage_options:` key.
- 0.11 Rename `persistence:` option to `storage:`, as this follows the naming of `Revisionair.Storage`.
- 0.10 Adds `Revisionair.get_revision`, and all revisions are now required to store a `:revision` field, that allows you to track which exact revision some other part of your app is talking about.
Expand Down
106 changes: 53 additions & 53 deletions lib/revisionair.ex
Original file line number Diff line number Diff line change
Expand Up @@ -45,25 +45,25 @@ defmodule Revisionair do

@doc """
Store a revision of the given structure,
of the 'type' `structure_type`,
uniquely identified by `unique_identifier`, and possibly with the given `options`
of the 'type' `item_type`,
uniquely identified by `item_id`, and possibly with the given `options`
in the storage layer.
If `structure_type` or `unique_identifier` is an arity-1 function,
then to find the structure_type or unique_identifier, they are called on the given structure.
If `item_type` or `item_id` is an arity-1 function,
then to find the item_type or item_id, they are called on the given structure.
As an example, the default function that extracts the unique identifier from the `:id` field of the structure, is `&(&1.id)`.
`options` might contain the `metadata:` field, in which case the given metadata is saved alongside the stored structure.
"""
def store_revision(structure, structure_type, unique_identifier), do: store_revision(structure, structure_type, unique_identifier, [])
def store_revision(structure, structure_type, unique_identifier, options) when is_map(structure) and is_list(options) do
def store_revision(structure, item_type, item_id), do: store_revision(structure, item_type, item_id, [])
def store_revision(structure, item_type, item_id, options) when is_map(structure) and is_list(options) do
storage_module = storage_module(options)
storage_options = extract_storage_options(options)
structure_type = extract_structure_type(structure, structure_type)
unique_identifier = extract_unique_identifier(structure, unique_identifier)
item_type = extract_item_type(structure, item_type)
item_id = extract_item_id(structure, item_id)
metadata = Keyword.get(options, :metadata, %{})

storage_module.store_revision(structure, structure_type, unique_identifier, metadata, storage_options)
storage_module.store_revision(structure, item_type, item_id, metadata, storage_options)
end

@doc """
Expand All @@ -88,25 +88,25 @@ defmodule Revisionair do
@doc """
Returns a list with all revisions of the structure of given type and identifier.
"""
def list_revisions(structure_type, unique_identifier), do: list_revisions(structure_type, unique_identifier, [])
def list_revisions(structure_type, unique_identifier, options) when is_list(options) do
def list_revisions(item_type, item_id), do: list_revisions(item_type, item_id, [])
def list_revisions(item_type, item_id, options) when is_list(options) do
storage_module = storage_module(options)
storage_options = extract_storage_options(options)
storage_module.list_revisions(structure_type, unique_identifier, storage_options)
storage_module.list_revisions(item_type, item_id, storage_options)
end

@doc """
A four-arity version that allows you to specify functions to call on the given structure to extract the structure_type and unique_identifier.
A four-arity version that allows you to specify functions to call on the given structure to extract the item_type and item_id.
Used internally; part of the public API as it might be useful in pipelines.
"""
def list_revisions(structure, structure_type, unique_identifier) do
list_revisions(structure, structure_type, unique_identifier, [])
def list_revisions(structure, item_type, item_id) do
list_revisions(structure, item_type, item_id, [])
end
def list_revisions(structure, structure_type, unique_identifier, options) when is_function(structure_type) or is_function(unique_identifier) do
structure_type = extract_structure_type(structure, structure_type)
unique_identifier = extract_unique_identifier(structure, unique_identifier)
def list_revisions(structure, item_type, item_id, options) when is_function(item_type) or is_function(item_id) do
item_type = extract_item_type(structure, item_type)
item_id = extract_item_id(structure, item_id)

list_revisions(structure_type, unique_identifier, options)
list_revisions(item_type, item_id, options)
end

@doc """
Expand All @@ -121,25 +121,25 @@ defmodule Revisionair do
@doc """
Returns the newest stored revision of the structure of given type and identifier.
"""
def newest_revision(structure_type, unique_identifier), do: newest_revision(structure_type, unique_identifier, [])
def newest_revision(structure_type, unique_identifier, options) when is_list(options) do
def newest_revision(item_type, item_id), do: newest_revision(item_type, item_id, [])
def newest_revision(item_type, item_id, options) when is_list(options) do
storage_module = storage_module(options)
storage_options = extract_storage_options(options)
storage_module.newest_revision(structure_type, unique_identifier, storage_options)
storage_module.newest_revision(item_type, item_id, storage_options)
end

@doc """
A four-arity version that allows you to specify functions to call on the given structure to extract the structure_type and unique_identifier.
A four-arity version that allows you to specify functions to call on the given structure to extract the item_type and item_id.
Used internally; part of the public API as it might be useful in pipelines.
"""
def newest_revision(structure, structure_type, unique_identifier) do
newest_revision(structure, structure_type, unique_identifier, [])
def newest_revision(structure, item_type, item_id) do
newest_revision(structure, item_type, item_id, [])
end
def newest_revision(structure, structure_type, unique_identifier, options) when is_function(structure_type) or is_function(unique_identifier) do
structure_type = extract_structure_type(structure, structure_type)
unique_identifier = extract_unique_identifier(structure, unique_identifier)
def newest_revision(structure, item_type, item_id, options) when is_function(item_type) or is_function(item_id) do
item_type = extract_item_type(structure, item_type)
item_id = extract_item_id(structure, item_id)

newest_revision(structure_type, unique_identifier, options)
newest_revision(item_type, item_id, options)
end

@doc """
Expand All @@ -156,21 +156,21 @@ defmodule Revisionair do
@doc """
Returns the newest stored revision of the structure of given type and identifier.
"""
def get_revision(structure_type, unique_identifier, revision), do: get_revision(structure_type, unique_identifier, revision, [])
def get_revision(structure_type, unique_identifier, revision, options) when is_list(options) do
def get_revision(item_type, item_id, revision), do: get_revision(item_type, item_id, revision, [])
def get_revision(item_type, item_id, revision, options) when is_list(options) do
storage_module = storage_module(options)
storage_options = extract_storage_options(options)
storage_module.get_revision(structure_type, unique_identifier, revision, storage_options)
storage_module.get_revision(item_type, item_id, revision, storage_options)
end

def get_revision(structure, structure_type, unique_identifier, revision) do
get_revision(structure, structure_type, unique_identifier, revision, [])
def get_revision(structure, item_type, item_id, revision) do
get_revision(structure, item_type, item_id, revision, [])
end
def get_revision(structure, structure_type, unique_identifier, revision, options) when is_list(options) do
structure_type = extract_structure_type(structure, structure_type)
unique_identifier = extract_unique_identifier(structure, unique_identifier)
def get_revision(structure, item_type, item_id, revision, options) when is_list(options) do
item_type = extract_item_type(structure, item_type)
item_id = extract_item_id(structure, item_id)

get_revision(structure_type, unique_identifier, revision, options)
get_revision(item_type, item_id, revision, options)
end

@doc """
Expand All @@ -181,37 +181,37 @@ defmodule Revisionair do
delete_all_revisions_of(structure, &(&1.__struct__), &(&1.id), options)
end

def delete_all_revisions_of(structure_type, unique_identifier), do: delete_all_revisions_of(structure_type, unique_identifier, [])
def delete_all_revisions_of(structure_type, unique_identifier, options) when is_list(options) do
def delete_all_revisions_of(item_type, item_id), do: delete_all_revisions_of(item_type, item_id, [])
def delete_all_revisions_of(item_type, item_id, options) when is_list(options) do
storage_module = storage_module(options)
storage_options = extract_storage_options(options)
storage_module.delete_all_revisions_of(structure_type, unique_identifier, storage_options)
storage_module.delete_all_revisions_of(item_type, item_id, storage_options)
end

def delete_all_revisions_of(structure, structure_type, unique_identifier) do
delete_all_revisions_of(structure, structure_type, unique_identifier, [])
def delete_all_revisions_of(structure, item_type, item_id) do
delete_all_revisions_of(structure, item_type, item_id, [])
end
def delete_all_revisions_of(structure, structure_type, unique_identifier, options) when is_list(options) do
structure_type = extract_structure_type(structure, structure_type)
unique_identifier = extract_unique_identifier(structure, unique_identifier)
def delete_all_revisions_of(structure, item_type, item_id, options) when is_list(options) do
item_type = extract_item_type(structure, item_type)
item_id = extract_item_id(structure, item_id)

delete_all_revisions_of(structure_type, unique_identifier, options)
delete_all_revisions_of(item_type, item_id, options)
end

# Either read from the options, or otherwise from the application configuration.
defp storage_module(options) do
options[:storage] || Application.fetch_env!(:revisionair, :storage)
end

defp extract_structure_type(structure, structure_type) when is_function(structure_type, 1) do
structure_type.(structure)
defp extract_item_type(structure, item_type) when is_function(item_type, 1) do
item_type.(structure)
end
defp extract_structure_type(_structure, structure_type), do: structure_type
defp extract_item_type(_structure, item_type), do: item_type

defp extract_unique_identifier(structure, unique_identifier) when is_function(unique_identifier, 1) do
unique_identifier.(structure)
defp extract_item_id(structure, item_id) when is_function(item_id, 1) do
item_id.(structure)
end
defp extract_unique_identifier(_structure, unique_identifier), do: unique_identifier
defp extract_item_id(_structure, item_id), do: item_id

defp extract_storage_options(options), do: options[:storage_options] || []
end
22 changes: 11 additions & 11 deletions lib/revisionair/storage.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ defmodule Revisionair.Storage do
by the kind of persistence layer you want to use.
Note that, while written out in this behaviour, some Storage implementations might put
restrictions on the kind of values `structure_type` and/or `unique_identifier` might have.
restrictions on the kind of values `item_type` and/or `item_id` might have.
## Metadata
Expand All @@ -34,34 +34,34 @@ defmodule Revisionair.Storage do
@type metadata :: %{revision: any}
@type revision :: any
@type structure :: %{}
@type structure_type :: integer | bitstring | atom
@type unique_identifier :: integer | bitstring | atom
@type item_type :: integer | bitstring | atom
@type item_id :: integer | bitstring | atom
@type options :: list

@doc """
Stores a new revision for the given map, uniquely identified by the {structure_type, unique_identifier} combination.
Stores a new revision for the given map, uniquely identified by the {item_type, item_id} combination.
"""
@callback store_revision(structure, structure_type, unique_identifier, metadata, options) :: :ok | :error
@callback store_revision(structure, item_type, item_id, metadata, options) :: :ok | :error

@doc """
Returns a {structure, metadata}-list of all revisions of the given struture, newest-to-oldest.
The metadata field is required to be a map, which has to include a `:revision` field.
"""
@callback list_revisions(structure_type, unique_identifier, options) :: [{structure, metadata}]
@callback list_revisions(item_type, item_id, options) :: [{structure, metadata}]

@doc """
Returns the newest revision for the given {structure_type, unique_identifier} combination.
Returns the newest revision for the given {item_type, item_id} combination.
This callback is supplied decoupled from `list_revisions` for efficiency,
because it is very common to check only the newest revision.
"""
@callback newest_revision(structure_type, unique_identifier, options) :: {:ok, {structure, metadata}} | :error
@callback newest_revision(item_type, item_id, options) :: {:ok, {structure, metadata}} | :error

@callback get_revision(structure_type, unique_identifier, revision, options) :: {:ok, {structure, metadata}} | :error
@callback get_revision(item_type, item_id, revision, options) :: {:ok, {structure, metadata}} | :error

@doc """
Deletes all revisions for the given {structure_type, unique_identifier}
Deletes all revisions for the given {item_type, item_id}
"""
@callback delete_all_revisions_of(structure_type, unique_identifier, options) :: :ok | :error
@callback delete_all_revisions_of(item_type, item_id, options) :: :ok | :error
end
35 changes: 18 additions & 17 deletions lib/revisionair/storage/agent.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,31 @@ defmodule Revisionair.Storage.Agent do
Agent.start_link(fn -> %{} end, name: __MODULE__)
end

def store_revision(structure, structure_type, unique_identifier, metadata, _opts) do
Agent.update(__MODULE__, fn structure_types ->
structure_types = Map.put_new(structure_types, structure_type, %{})
structure_types = put_in structure_types[structure_type], Map.put_new(structure_types[structure_type], unique_identifier, %{num_revisions: 0, revisions: %{}})
def store_revision(structure, item_type, item_id, metadata, _opts) do
Agent.update(__MODULE__, fn item_types ->
item_types = Map.put_new(item_types, item_type, %{})
item_types = put_in item_types[item_type], Map.put_new(item_types[item_type], item_id, %{num_revisions: 0, revisions: %{}})

num_revisions = structure_types[structure_type][unique_identifier].num_revisions
revisions = structure_types[structure_type][unique_identifier].revisions
num_revisions = item_types[item_type][item_id].num_revisions
revisions = item_types[item_type][item_id].revisions

put_in structure_types[structure_type][unique_identifier], %{revisions: Map.put_new(revisions, num_revisions, {structure, metadata}), num_revisions: num_revisions + 1}
put_in item_types[item_type][item_id], %{revisions: Map.put_new(revisions, num_revisions, {structure, metadata}), num_revisions: num_revisions + 1}
end)
end

def list_revisions(structure_type, unique_identifier, _opts) do
def list_revisions(item_type, item_id, _opts) do
Agent.get(__MODULE__, fn
%{^structure_type => %{^unique_identifier => %{revisions: revisions}}} ->
%{^item_type => %{^item_id => %{revisions: revisions}}} ->
revisions
|> Enum.sort_by(fn {revision, _elem} -> -revision end)
|> Enum.map(fn {revision, {structure, metadata}} -> {structure, Map.put(metadata, :revision, revision) }end)
_ -> []
end)
end

def newest_revision(structure_type, unique_identifier, _opts) do
def newest_revision(item_type, item_id, _opts) do
Agent.get(__MODULE__, fn
%{^structure_type => %{^unique_identifier => %{num_revisions: num_revisions, revisions: revisions}}} ->
%{^item_type => %{^item_id => %{num_revisions: num_revisions, revisions: revisions}}} ->
case num_revisions do
0 -> :error
_ ->
Expand All @@ -51,19 +51,20 @@ defmodule Revisionair.Storage.Agent do
end)
end

def get_revision(structure_type, unique_identifier, revision, _opts) do
IO.inspect({structure_type, unique_identifier, revision})
def get_revision(item_type, item_id, revision, _opts) do
IO.inspect({item_type, item_id, revision})
Agent.get(__MODULE__, fn
%{^structure_type => %{^unique_identifier => %{revisions: %{^revision => data}}}} ->
%{^item_type => %{^item_id => %{revisions: %{^revision => data}}}} ->
{:ok, put_revision_in_metadata(data, revision)}
_ -> :error
end)
end

def delete_all_revisions_of(structure_type, unique_identifier, _opts) do
Agent.update(__MODULE__, fn structure_types ->
pop_in structure_types, [structure_type, unique_identifier]
def delete_all_revisions_of(item_type, item_id, _opts) do
Agent.update(__MODULE__, fn item_types ->
pop_in item_types, [item_type, item_id]
end)
:ok
end

defp put_revision_in_metadata({data, metadata}, revision) do
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ defmodule Revisionair.Mixfile do

def project do
[app: :revisionair,
version: "0.12.0",
version: "0.13.0",
elixir: "~> 1.4",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
Expand Down

0 comments on commit f9069a0

Please sign in to comment.