riptide v0.3.13 Riptide.Mutation

A mutation represents a set of changes that can be applied to a Riptide.Store. This module contains functions that make it easy to compose complex mutations and combine them together.

Mutations contains two types of operations:

  • :merge - A map containing the values that will be merged in - creating them if they don't already exist
  • :delete - A map containing the paths that should be deleted from the store

Deleting

In a mutation, the deletes are always applied first. They are expressed as a map with a value of 1 for each path to be deleted.

iex> Mutation.put_delete(["todo:info", "001"])
%Riptide.Mutation{
  delete: %{
    "todo:info" => %{
      "001" => 1
    }
  },
  merge: %{}
}

This mutation will delete everything under ["todo:info", "001]

Merging

Merges are applied after deletes and are expressed as a map pointing to the values that should be set.

Mutation.put_merge(
  ["todo:info", "001"],
  %{
    "key" => "001",
    "text" => "Document riptide!"
  }
)
%Riptide.Mutation{
  delete: %{},
  merge: %{
    "todo:info" => %{
      "001" => %{
        "key" => "001",
        "text" => "Document riptide!"
      }
    }
  }
}

This mutation will delete everything under ["todo:info", "001]

Composing

There are various functions in this module for composing sophisticated mutations. A good approach is to break down a complex mutation into atomic pieces for clarity and combine them together.

Here are some examples of how they can be helpful:

Mutation.new()
|> Mutation.put_merge(["user:info", "001", "name"], "jack")
|> Mutation.put_merge(["user:info", "002", "name"], "john")
|> Mutation.put_delete(["todo:info"])
%Riptde.Mutation{
  delete: %{"todo:info" => 1},
  merge: %{
    "user:info" => %{
      "001" => %{"name" => "jack"},
      "002" => %{"name" => "john"}
    }
  }
}
def create_user_mut(key, email) do
  Mutation.put_merge(["user:info", key], %{
    "key" => key,
    "email" => email
  })
end

def set_password_mut(key, password) do
  Mutation.put_merge(["user:passwords", key], Bcrypt.encrypt(password))
end

Mutation.combine(
  create_user_mut("001", "user@example.com"),
  set_password_mut("001", "mypassword")
)
%Riptde.Mutation{
  merge: %{
    "user:info" => %{
      "001" => %{
        "key" => "001",
        "email" => "user@example.com",
      }
    },
    "user:password" => %{
      "001" => "$2a$10$kj5ZhhLWIwik8uK4RJrDA.ddOEIK5VO9f4Y5FwL5D3CvVafSVXcYe"
    }
  }
}
1..100
|> Stream.map(fn index -> Mutation.merge(["todo:info", to_string(index)], index) end)
|> Mutation.combine()
%Riptide.Mutation{
  delete: %{},
  merge: %{
    "todo:info" => %{
      "1" => 1,
      "2" => 2,
      "3" => 3,
      ...
  }
}

Link to this section Summary

Types

A key-value pair representing a layer of the mutation. The key is a list of strings representing the path to the current layer. The value is a mutation, representing any deeper sub-mutations.

t()

A map containing paths to be added (merge) and paths to be removed (delete).

Functions

Applies the entire mutation to the input map.

Takes a list or stream of Mutations and combines them in order to produce a single output mutation.

Combines the right mutation into the left mutation and returns a singular mutation

Takes two maps and returns a mutation that could be applied to turn the the first map into the second.

Accepts a list and mutation, and returns a new mutation with the given mutation nested at the given path.

Returns a mapping with an entry for every layer of the mutation. The keys represent a path and the value represents the full mutation that is being merged in at that path.

Creates a new mutation, optionally passing in a map for merges or deletes

Creates a new mutation and puts a path to be deleted

Adds a delete path to the input mutation

Creates a new mutation and puts a value to be merged

Adds a merge value to the input mutation

Link to this section Types

Link to this type

layer()

layer() :: {[String.t()], t()}

A key-value pair representing a layer of the mutation. The key is a list of strings representing the path to the current layer. The value is a mutation, representing any deeper sub-mutations.

Link to this type

t()

t() :: %Riptide.Mutation{delete: map(), merge: map()}

A map containing paths to be added (merge) and paths to be removed (delete).

Link to this section Functions

Link to this function

apply(input, mutation)

apply(map(), t()) :: map()

Applies the entire mutation to the input map.

Example

iex> Riptide.Mutation.apply(
...>  %{"b" => false},
...>  %{delete: %{}, merge: %{"a" => true}}
...> )
%{"a" => true, "b" => false}
Link to this function

chunk(stream, count)

Link to this function

combine(enumerable)

combine(Enum.t()) :: t()

Takes a list or stream of Mutations and combines them in order to produce a single output mutation.

Examples

iex> 0..3
...> |> Stream.map(fn index ->
...>   Riptide.Mutation.put_merge(["todo:info", to_string(index)], index)
...> end)
...> |> Riptide.Mutation.combine()
%Riptide.Mutation{delete: %{}, merge: %{"todo:info" => %{"0" => 0, "1" => 1, "2" => 2, "3" => 3}}}
Link to this function

combine(left, right)

combine(t(), t()) :: t()

Combines the right mutation into the left mutation and returns a singular mutation

Examples

iex> Riptide.Mutation.combine(
...>   %Riptide.Mutation{delete: %{}, merge: %{"a" => true}},
...>   %Riptide.Mutation{delete: %{}, merge: %{"b" => false}}
...> )
%Riptide.Mutation{delete: %{}, merge: %{"a" => true, "b" => false}}
Link to this function

delete(struct, key)

Link to this function

from_diff(old, new)

Takes two maps and returns a mutation that could be applied to turn the the first map into the second.

Example

iex> Riptide.Mutation.from_diff( ...> %{"a" => 1}, ...> %{"b" => 2} ...>) %Riptide.Mutation{delete: %{"a" => 1}, merge: %{"b" => 2}}

Link to this function

inflate(path, mut)

inflate([String.t()], t()) :: t()

Accepts a list and mutation, and returns a new mutation with the given mutation nested at the given path.

Example

iex> Riptide.Mutation.inflate(
...>   ["a", "b"],
...>   %{
...>     delete: %{},
...>     merge: %{
...>       "a" => 1
...>     }
...>   }
...> )
...> %Riptide.Mutation{
...>   delete: %{
...>     "a" => %{
...>       "b" => %{}
...>     }
...>   },
...>   merge: %{
...>    "a" => %{
...>      "b" => %{
...>        "a" => 1
...>      }
...>    }
...>  }
...> }
Link to this function

layers(map)

layers(t()) :: %{required([String.t()]) => layer()}

Returns a mapping with an entry for every layer of the mutation. The keys represent a path and the value represents the full mutation that is being merged in at that path.

Examples

iex> Riptide.Mutation.put_merge(["a", "b"], true) |> Riptide.Mutation.layers
%{
  [] => %Riptide.Mutation{
    delete: %{},
    merge: %{
      "a" => %{
        "b" => true
      }
    }
  },
  ["a"] => %Riptide.Mutation{
    delete: %{},
    merge: %{
      "b" => true
    }
  }
}
Link to this function

new(merge \\ %{}, delete \\ %{})

new(map(), map()) :: t()

Creates a new mutation, optionally passing in a map for merges or deletes

Examples

iex> Riptide.Mutation.new
%Riptide.Mutation{delete: %{}, merge: %{}}
Link to this function

put(struct, key, val)

Link to this function

put_delete(path)

put_delete([String.t()]) :: t()

Creates a new mutation and puts a path to be deleted

Link to this function

put_delete(input, path)

put_delete(t(), [String.t()]) :: t()

Adds a delete path to the input mutation

Examples

iex> Riptide.Mutation.new()
...> |> Riptide.Mutation.put_delete(["c"])
%Riptide.Mutation{delete: %{"c" => 1}, merge: %{}}
Link to this function

put_merge(path, value)

put_merge([String.t()], any()) :: t()

Creates a new mutation and puts a value to be merged

Link to this function

put_merge(input, path, value)

put_merge(t(), [String.t()], any()) :: t()

Adds a merge value to the input mutation

Examples

iex> mutation = Riptide.Mutation.put_merge(["a", "b"], 1)
iex> Riptide.Mutation.put_merge(mutation, ["a", "c"], 2)
%Riptide.Mutation{delete: %{}, merge: %{"a" => %{"b" => 1, "c" => 2}}}