sodapopcan

sodapopcan

I have a form with arbitrary levels of deeply nested associations. One particular association that lives a couple of levels down can have an upload associated with it. To track these I need to allow_upload based on the indices of each level. Currently this looks like this:

socket.assigns.form.source
|> Ecto.Changeset.get_assoc(:mockups)
|> Enum.with_index()
|> Enum.reduce(socket, fn {mockup, mockup_index}, socket ->
  mockup
  |> Ecto.Changeset.get_assoc(:elements)
  |> Enum.with_index()
  |> Enum.reduce(socket, fn {element, element_index}, socket ->
    element
    |> Ecto.Changeset.get_assoc(:transformations)
    |> Enum.with_index()
    |> Enum.reduce(socket, fn {_transformation, transformation_index}, socket ->
      allow_image_upload(
        socket,
        "transformation-#{mockup_index}-#{element_index}-#{transformation_index}"
      )
    end)
  end)
end)

I’m wondering how others would go about this.

Of course I realize I can abstract out the commonalities into a private function, but I would rather not do that as this is the only place this type of thing is happening and feel it’ll make it less readable for passers-by. I’m wondering if there is a reducer pattern that can do this type of nested thing in a flat pipeline. I have a feeling Pathex can probably come in handy here but interested in other options. I’m thinking of reducing into a 2-tuple of {socket, %{}} and building up the indices in the map then doing a final reduction to build up all the keys and allow_upload them, but that feels like it will be more complex to read.

Anyway, just wondering! As verbose as my example is, I don’t hate it as it’s pretty easy to read. Credo is complaining about the indentation depth, though, and I don’t necessarily disagree with it.

Showing Posts 1 to 10

100phlecs

100phlecs

When you start dealing with nested iterative structures, for is your best bet.

assoc_with_index = fn changeset, assoc ->
  changeset
  |> Ecto.Changeset.get_assoc(assoc)
  |> Enum.with_index()
end

source = socket.assigns.form.source

for {mockup, mockup_index} <- assoc_with_index.(source, :mockups),
    {element, element_index} <- assoc_with_index.(mockup, :elements),
    {_transformation, transformation_index} <- assoc_with_index.(element, :transformations) do
  allow_image_upload(
    socket,
    "transformation-#{mockup_index}-#{element_index}-#{transformation_index}"
  )
end
LostKobrakai

LostKobrakai

It also supports reduce, to allow wiring the socket state through.

100phlecs

100phlecs

It’s a great tool, one of the main reasons I like Elixir.

Yes, if you need to update the socket, then you can tack on reduce:

assoc_with_index = fn changeset, assoc ->
  changeset
  |> Ecto.Changeset.get_assoc(assoc)
  |> Enum.with_index()
end

source = socket.assigns.form.source

for {mockup, mockup_index} <- assoc_with_index.(source, :mockups),
    {element, element_index} <- assoc_with_index.(mockup, :elements),
    {_transformation, transformation_index} <- assoc_with_index.(element, :transformations),
    reduce: socket do
  socket ->
    allow_image_upload(
      socket,
      "transformation-#{mockup_index}-#{element_index}-#{transformation_index}"
    )
end
sodapopcan

sodapopcan OP

Beautiful! So much nicer than what I was thinking.

Thank you both!

Sorc96

Sorc96

This brings me joy! The for comprehension is so nice.

D4no0

D4no0

I would also consider an approach where I would break this down into small functions, so the main function eould be flat.

sodapopcan

sodapopcan OP

I would do that if this became a common pattern. As it stands this is the only place in the app where I’m needing to index associations in order to enable uploads and this logic is already inside a private function, so I don’t want to break things up too much. Part of me actually still prefers my original example because it’s so stupidly simple, but I do appreciate the Credo rule and if I allow myself to up the nesting limit “just this one time”, there will definitely be several future instances of “Ok, this time is for sure the last time!”—I’m weak so I prefer to stick with defaults :smiling_face_with_tear:

dimitarvp

dimitarvp

FWIW the for variant, while shorter, is absolutely opaque and is making it pretty hard to mentally parse and understand what is going on.

I see nothing wrong with your original function but if you really have to act I would either (a) add a Credo exception or (b) make two sub-functions – corresponding to your two sub-levels.

I’d opt for sub-functions. Granted they’ll ruin the nice readability your original long function has but hey, if they are directly below it it should be pretty straightforward to read and grok.

sodapopcan

sodapopcan OP

Ya, I might revert to my original version and completely kill the Credo rule as indenting too deeply isn’t something that happens often (I’m ok with outright removal of linting rules). The more I think about it, it’s really a pretty useless rule unless you are working with an army of developers you can’t trust. My other thought was that this feels like code that someone in the future will probably come along and rewrite anyway :sweat_smile:

I actually think the for version is quite readable and certainly very “pretty”, but I agree the original is more readable even if “ugly”. I do actually always use for whenever I need a Cartesian product of two collections but for some reason as soon as I needed the product of three I was like, “duhhhhhhhhhhh what do I do better ask the internet!”. Maybe that is good signal for me not to use it here, haha. Not to **** on anyone who prefers the for!

Anyway, one thing is for sure that I will definitely choose one of these options hopefully without thinking too much more about it :upside_down_face: :upside_down_face: :upside_down_face:

Thanks for the input, all!

dimitarvp

dimitarvp

I agree that for can be slightly “prettier” than a chain of Enum.reduce-s. If you find that’s the case as well then I would try and make the sub-function version and just compare side by side. Informed choice and all. :slightly_smiling_face:

But yeah, I wasn’t claiming what I said as a fact, I was stating my quick intuitive impression. I learned to trust my gut so I am sharing what would I do.

Obviously go for the variant that makes it least likely to hate yourself when you revisit this code in the future.

Where Next? Top

Trending in Questions Top

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
mnkhod
So i have been using ash framework for a while and i love it. However currently the issue im having with ash framework is the error handl...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews