Control structure `with else` problem

If the else behaviour changes depending on the type of failure, it is generally considered clearer to put those behaviours inside of their own functions and then use those functions inside the with statement.

For example, your code can be refactored to this:

with %App.Room{} = room <- get_room(1),
    true <- test_fn("good") do
  IO.inspect(room)
end

defp get_room(room_id) do
  case App.Repo.get(room_id) do
    nil ->
      IO.puts("Room does not exist")
      nil

    room ->
     room
  end
end

defp test_fn(good?) do
  case good? do
   "good" -> 
     true

   _ -> 
    IO.puts("Not good")
    false
  end
end

or this

with {:ok, room} <- get_room(1),
    {:ok, true} <- test_fn("good") do
  IO.inspect(room)
else
  {:error, reason} -> IO.puts(reason)
end

defp get_room(room_id) do
  case App.Repo.get(room_id) do
   nil ->
      {:error, "Room does not exist"}

   room ->
     {:ok, room}
  end
end

defp test_fn(good?) do
    case good? do
      "good" -> 
        {:ok, true}

      _ -> 
        {:error, "Not Good"}
    end
end

Some references: