Blog

Elixir: buscar existencia de elementos relacionados en código vs BDD

Elixir: buscar existencia de elementos relacionados en código vs BDD

Estaba revisando este código:

  @inactive_implementation_status [
    :deprecated,
    :versioned
  ]


  defp validate_inactive_implementations(%{data: rule} = changeset) do
    active_implementations? =
      rule
      |> TdDd.Repo.preload(:rule_implementations)
      |> Map.get(:rule_implementations)
      |> Enum.any?(&(!Enum.member?(@inactive_implementation_status, &1.status)))

    if active_implementations? do
      add_error(changeset, :rule_implementations, "active_implementations")
    else
      changeset
    end
  end

Aquí se comprueba si una regla («rule») contiene implementaciones («implementacions», «rule_implementations»). Lo cambié para que la existencia de la relación se compruebe por completo en BDD en lugar de usar Enum.member junto con Enum.any:

defp validate_inactive_implementations(%{data: %{id: rule_id}} = changeset) do
    %{active_implementations?: active_implementations?} =
      Implementation
      |> where([ri], ri.rule_id == ^rule_id and ri.status not in ^@inactive_implementation_status)
      |> select([ri], %{active_implementations?: count(ri) > 0})
      |> Repo.one

    if active_implementations? do
      add_error(changeset, :rule_implementations, "active_implementations")
    else
      changeset
    end
  end

La diferencia de tiempo (excluyendo el if/else), medida en microsegundos con :timer.tc, probando 10 ejecuciones de un test que llama indirectamente a validate_inactive_implementations, es:

Preload + Enum.member + Enum.any  SQL directo  
                           11267         6317
                           13542         6903
                           10584         8762
                           11517         6130
                           10969         8834
                           12267         7878
                           15118         9899
                           11967         9894
                           11831         8691
                           11261         7291

El test es:

test "validates rule with a related inactive implementation can be deleted" do
  %{rule: rule} = insert(:implementation, status: :versioned)

  insert(:implementation,
    deleted_at: DateTime.utc_now(),
    status: :deprecated,
    rule_id: rule.id
  )

  assert {:ok, rule} =
            rule
            |> Rule.delete_changeset()
            |> Repo.update()

  assert %{active: false, deleted_at: deleted_at} = rule
  assert deleted_at !== nil
end