4

Let's say I have a map that looks like this

An organization has an array of properties and the properties has an array of "access_tokens"

organization = %{
  name: "Org",
  properties: [
    %{
      name: "prop1",
      access_tokens: [%{id: "at-1", name: "one-1"}, %{id: "at-2", name: "one-2"}]
    },
    %{
      name: "prop2",
      access_tokens: [%{id: "at-3", name: "two-1"}, %{id: "at-4", name: "two-2"}]
    }
  ]
}

Now I have this map, for one particular access token, the id for this one matches one of the access tokens for the 2nd property:

access_token = %{id: "at-3", name: "new name"}

What is the best way to iterate over to update the organization%{} map with the new access token? What I want to do is find the access token that matches based on id and replace it with the new one that has the new name.

I have some round-about ways of doing this, but what is a clean way to do this in Elixir.

1 Answer 1

15

You do not need to iterate, just use Access, Access.filter/1, and Access.all/0:

put_in(
  organization,
  [:properties,
   Access.all(),
   :access_tokens,
   Access.filter(&match?(%{id: "at-3"}, &1)),
   :name],
  "new_name")

I always wonder, how this one of the most powerful things in the language is extremely underrated.

Sign up to request clarification or add additional context in comments.

7 Comments

@7stud No one single language I am aware of provides such an elegant way of accessing deeply nested elements with an ability to filter them on each level and there is a great documentation available. Note, that if prop1 also had the matched element, it’d be updated too.
there is a great documentation -- Then perhaps you could explain why you always use put_in() instead of update_in(), which would seem to be the more logical choice.
Super cool, thank you! That is so useful! That works, but it looks like it's not working if "at-3" is a variable put_in(organization,[:properties, Access.all(), :access_tokens, Access.filter(&match?(%{id: access_token.id}, &1)), :name], "new_name") Is there a way to get around that?
I'm not sure where I would find this in the docs, but it seems that the Elixir convention is to use put anywhere you want to say "this key is now associated with this value" and an update anywhere you want to say "I want to do this operation on the value associated with this key (increment a counter, prepend to a list, etc.)". In this example, put_in makes more sense because we want name: "new_name" in our map. We would use update_in if we wanted a pair more like name: "two-1-new_name". Essentially I'd use put_in anywhere you'd throw out the argument to the update_in callback.
@dylanjha Where match?(pattern, expr) is a macro, it won't like trying to match on your new_at.id. You could save that as a variable and pin it: &match?(%{id: ^new_id}, &1), or you could filter with a simpler function: & &1 == new_at.id.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.