lib/redis_graph/node.ex

defmodule RedisGraph.Node do
  @moduledoc """
  A Node member of a Graph.

  Nodes have an alias which uniquely identifies them in a Graph. Nodes
  must have an alias. When alias is not provided on initialization,
  a random alias will be set on the Node.

  Nodes may optionally have a list of labels which is analogous to a type definition.
  Nodes can be queried based on their labels, e.g. ``person`` or ``place`` or ``food``.

  Nodes may optionally have properties as well, which is a map of values associated
  with the entity. These properties can be returned by database queries.

  Nodes which are created as the result of a passed query to the graph database
  through `RedisGraph.QueryResult` will also have numeric ids which are
  internal to the graph in the database.
  """
  alias RedisGraph.Util

  @type t() :: %__MODULE__{
          id: integer(),
          alias: atom(),
          labels: [String.t()],
          properties: %{}
        }

  defstruct [:id, :alias, labels: [], properties: %{}]

  @doc """
  Creates a new Node with default arguments.

  ## Example
  ```
  alias RedisGraph.{Node}

  # create a Node
  bob = Node.new()

  ```
  """
  @spec new() :: t()
  def new() do
    new(%{})
  end

  @doc """
  Creates a new Node from provided argument.

  ## Example
  ```
  alias RedisGraph.{Node}

  # create a Node
  bob = Node.new(%{
    alias: :n,
    labels: ["person"],
    properties: %{
      name: "Bob Thomsen",
      age: 22
    }
  })
  ```
  """
  @spec new(map()) :: t()
  def new(map) when is_map(map) do
    node = struct(__MODULE__, map)
    if(is_nil(node.alias), do: set_alias_if_nil(node), else: node)
  end

  @doc "Sets the node's alias to atom if it is `nil`."
  @spec set_alias_if_nil(t()) :: t()
  def set_alias_if_nil(node) do
    if is_nil(node.alias) do
      alias = Util.random_string() |> String.to_atom()
      %{node | alias: alias}
    else
      node
    end
  end

  @doc """
  Compare two Nodes with respect to equality.

  Comparison logic:

  * if ids differ then returns ``false``
  * if aliases differ then returns ``false``
  * if labels differ then returns ``false``
  * if properties differ then returns ``false``
  * otherwise returns ``true``
  """
  @spec compare(t(), t()) :: boolean()
  def compare(left, right) do
    cond do
      left.id != right.id -> false
      left.alias != right.alias -> false
      length(left.labels) != length(right.labels) -> false
      left.labels != right.labels -> false
      map_size(left.properties) != map_size(right.properties) -> false
      not Map.equal?(left.properties, right.properties) -> false
      true -> true
    end
  end
end