riptide v0.4.4 Riptide.Mutation View Source

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 stream of mutations, combines them in batches of size count. Useful when writing a lot of mutations that would be faster written as batches.

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()

View Source
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()

View Source
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)

View Source
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}

Takes a stream of mutations, combines them in batches of size count. Useful when writing a lot of mutations that would be faster written as batches.

Examples

iex> 1..10
...> |> Stream.map(fn index -> Riptide.Mutation.put_merge(["data", to_string(index)], index) end)
...> |> Riptide.Mutation.chunk(5)
...> |> Enum.to_list()
[
  %Riptide.Mutation{
    delete: %{},
    merge: %{"data" => %{"1" => 1, "2" => 2, "3" => 3, "4" => 4, "5" => 5}}
  },
  %Riptide.Mutation{
    delete: %{},
    merge: %{"data" => %{"10" => 10, "6" => 6, "7" => 7, "8" => 8, "9" => 9}}
  }
]
Link to this function

combine(enumerable)

View Source
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)

View Source
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}}

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

Examples

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

inflate(path, mut)

View Source
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)

View Source
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 \\ %{})

View Source
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_delete(path)

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

Creates a new mutation and puts a path to be deleted

Link to this function

put_delete(input, path)

View Source
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)

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

Creates a new mutation and puts a value to be merged

Examples

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

put_merge(input, path, value)

View Source
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}}}