Update matching list items with Access.filter/1

almirsarajcic

almirsarajcic

1 hour ago

0 comments

Use Access.filter/1 to update only the list elements that match a predicate, without mapping and rebuilding the surrounding structure yourself.

order = %{
  items: [
    %{sku: "A", status: :pending, cents: 1_000},
    %{sku: "B", status: :paid, cents: 2_000},
    %{sku: "C", status: :pending, cents: 500}
  ]
}

update_in(
  order,
  [:items, Access.filter(&(&1.status == :pending)), :cents],
  &div(&1 * 90, 100)
)

# => %{
#      items: [
#        %{sku: "A", status: :pending, cents: 900},
#        %{sku: "B", status: :paid, cents: 2000},
#        %{sku: "C", status: :pending, cents: 450}
#      ]
#    }

The predicate runs against every item in the list. Each match continues through the rest of the access path, so :cents is updated only for pending items. Non-matching elements and the enclosing map keep their original shape.

The same accessor works with get_in/2, get_and_update_in/3, and pop_in/2. If nothing matches, the structure is returned unchanged.

Access.filter/1 expects the value at that point in the path to be a list. It raises when it encounters another data type, so make optional or mixed-shape data explicit before traversing it.

Access.filter/1 docs

Comments (0)

Sign in with GitHub to join the discussion