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

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
water
I’m aware of 2 such projects, are there others? git: GitHub - team-alembic/realworld: A fullstack Phoenix LiveView application with back...
New
itzmidinesh
Hi all, I’m working on a Phoenix project where I need to make external HTTP requests and I’m trying to choose between Req and HTTPoison....
New
pragdave
How do I tell Hologram not to escape HTML when substituting into templates? {Textile.render(@product.long_description)} escapes the HTM...
New
leoschweizer
Hi, we run different LLM models across a cluster, with each node loading a configurable subset. Model-specific queues let nodes consume ...
New
Stef-Verniers
Hello I’ve been coding in Elixir for almost two years now because of my current job where we implemented Elixir in our application. But ...
New
csmr
Hi all, I’m new here and would like to hear peoples views on my Elixir/Phoenix based game-engine! :smiley: Mutonex gamedesign was finish...
New

Other Trending Topics Top

dominicletz
Hey there, I haven’t seen a list of useful Elixir specific skills for the popular AI tools yet so I’m starting one here with my first. ...
New
raa-ziq
Hi everyone :waving_hand: I wanted to share something I’ve been building — NowBlind, a random chat app designed to help people discover ...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
kip
Localize is the next generation localisation library for Elixir. Think of it as ex_cldr version 3.0. The first version will be released ...
New
OndrejValenta
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly) AI dream has many faces, but general...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews