How do I access an external API and render it's response

I’m putting together a prototype that accesses the Spotify API, and searches for music. Used the sample LiveView project that Phoenix creates, as a starting point.

I was wondering how (and why) the sample code works.

  defp search(query) do
    if not SpotifyapiWeb.Endpoint.config(:code_reloader) do
      raise "action disabled when not in development"
    end

    #app <- Application.started_applications()
    Logger.info "In search, #{inspect(Application.started_applications())}"
    for {app, desc, vsn} <- Application.started_applications(),
        app = to_string(app),
        String.starts_with?(app, query) and not List.starts_with?(desc, ~c"ERTS"),
        into: %{},
        do: {app, vsn}
  end

This handles autocomplete queries, but I can’t quite wrap my mind around how it calls the Hexdocs endpoint and how this works.

It doesn’t call Hexdocs - it uses the list of started applications available from the runtime (Application.started_applications/0) and if one matches it redirects the browser to Hexdocs:

1 Like

Ahh, I see what you mean.

Is the matching happening in this line?

I couldn’t quite figure out what

do {app, vsn}

actually did with the output. It just looks like it outputs an object with two values. I wonder if I’m missing something…

I’d take a look at this article explaining Elixir’s for - there’s quite a lot going on between for and do {app,vsn} in the code sample you posted, and this article does an excellent job of explaining it: Elixir's for-loops go beyond "comprehension" | Hashrocket

for takes three main arguments:

  • a list of expressions, similar to a with, some using <- and some using =, as well as boolean expressions
  • an into option which specifies the shape of the result. See Enum.into and the Collectable protocol for more detail
  • a do expression, which is used to produce the final result.

Here are some examples.

for x <- 1..10,
  do: Integer.to_string(x)
# result:
# ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]

In its simplest form, a for looks a lot like Enum.map.

Just like in a with, the left-hand side of <- can fail to match - in that case, the for produces no output for that value of the enumerable:

for x when x < 6 <- 1..10,
  do: Integer.to_string(x)
# result:
# ["1", "2", "3", "4", "5"]

for x when rem(x,2) == 0 <- 1..10,
  do: Integer.to_string(x)
# result:
# ["2", "4", "6", "8", "10"]

Just like in a with, = can be used to bind names. Note that unlike <-, match failures will escape:

for x <- 1..10,
  {:ok, y} = x,
  do: Integer.to_string(x)
# result:
# ** (MatchError) no match of right hand side value: 1

Finally, there’s a shorthand for filtering values like Enum.filter; if an expression returns false or nil the element is omitted from the result. This is desired in cases like the initial example, where elements that return false when evaluating String.starts_with?(app, query) and not List.starts_with?(desc, ~c"ERTS") - but beware of the gotcha called out in @mindok’s link:

for person <- people,
    name = person.name,
    active = person.active do
  "#{name} status is: #{active}"
end
# => ["John status is: true"]

for person <- people,
    name = person.name do
  "#{name} status is: #{person.active}"
end
# => ["John status is: true", "Patricia status is: false"]

Here, the expression active = person.active binds the name active, but also (when person.active is false) causes for to skip the element.

3 Likes