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

Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
Onor.io
I have what I’ve heard referred to as a “lookup table” in my database. This is a way of assigning codes to common values. One common lo...
New
Trolleger
What approach to take when sending live updates to “random” users Hi! I have a question, I have a little chat app, and when I create a DM...
New
matt-savvy
Anyone here using Honeybadger? My Honeybadger account is being overwhelmed with noise from some bots. Seeing a lot of Bandit.HTTPError...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New

Other Trending Topics Top

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
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews