lib/redis_graph/relationship.ex

defmodule RedisGraph.Relationship do
  @moduledoc """
  An Relationship component relating two Nodes in a Graph.

  Relationships must have a source node and destination node, describing a relationship
  between two entities in a Graph. Nodes can be represented either as
  Node structures(%RedisGraph.Node{}) or id(integer) of respective node.

  Relationships must have a type which identifies the type of relationship being
  described between entities.

  Relationships, like Nodes, may also have properties, a map of values which describe
  attributes of the relationship between the associated Nodes.
  """
  alias RedisGraph.Util

  @type t() :: %__MODULE__{
          id: integer(),
          alias: atom(),
          src_node: Node.t() | number(),
          dest_node: Node.t() | number(),
          type: String.t(),
          properties: %{optional(String.t()) => any()}
        }

  @enforce_keys [:src_node, :dest_node, :type]
  defstruct [:id, :alias, :src_node, :dest_node, :type, properties: %{}]

  @doc """
  Create a new Relationship from provided argument.
  Argument should be a map and key: `type` with value of String type is required.

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

  john = Node.new(%{
    labels: ["person"],
    properties: %{
      name: "John Doe"
    }
  })

  bob = Node.new(%{
    labels: ["person"],
    properties: %{
      name: "Bob Nilson"
    }
  })

  relationship = Relationship.new(%{
    src_node: john,
    dest_node: bob,
    type: "friend",
    properties: %{
      best_friend: true
    }
  })
  ```
  """
  @spec new(map()) :: t()
  def new(%{type: type} = map) when is_binary(type) do
    relationship = struct(__MODULE__, map)
    if(is_nil(relationship.alias), do: set_alias_if_nil(relationship), else: relationship)
  end

  @spec set_alias_if_nil(t()) :: t()
  def set_alias_if_nil(relationship) do
    if is_nil(relationship.alias) do
      alias = Util.random_string() |> String.to_atom()
      %{relationship | alias: alias}
    else
      relationship
    end
  end

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

  Comparison logic:

  * If the ids differ, returns ``false``
  * If the source nodes differ, returns ``false``
  * If the destination nodes differ, returns ``false``
  * If the types differ, returns ``false``
  * If the properties differ, returns ``false``
  * Otherwise returns `true`
  """
  @spec compare(t(), t()) :: boolean()
  def compare(left, right) do
    cond do
      left.id != right.id -> false
      left.src_node != right.src_node -> false
      left.dest_node != right.dest_node -> false
      left.type != right.type -> false
      map_size(left.properties) != map_size(right.properties) -> false
      not Map.equal?(left.properties, right.properties) -> false
      true -> true
    end
  end
end