coen.bakker

coen.bakker

I read the Hexdocs about contexts in Phoenix. Now that I revisit my own code, across different projects, I notice that I sometimes use Repo functions outside my context modules.

Like so:

# example 1
    item =
      conn.params["id"]
      |> SomeContext.get_item!()
      |> Repo.preload(:users)

# example 2
    item =
      params["id"]
      |> SomeContext.get_item!()
      |> Repo.preload([users: from(u in User, select: u.username)])

Is that an anti-pattern?

My thoughts.

  1. It reads easily the way it is.
  2. But I don’t like that I have to import Ecto.Query, only: [from: 2], warn: false and alias TodayCostory.Repo into my controller/LiveView to be able to use the Repo functions.
  3. In some cases I would get quit a few additional functions inside my contexts. Taking the examples from above: get_item_with_users!() and get_item_with_usernames!(). Seems somewhat unnecessary.
  4. But maybe the benefits of having a context, and using it without compromise, is ultimately greater than the potential downsides. Most importantly, maybe, the benefit of creating greater separation of concerns.

Most Liked

sodapopcan

sodapopcan

I’m firmly in the camp of keeping Repo calls inside contexts and, more specifically, only in context files (or sub-contexts). For example, as you probably already know, they don’t belong in schemas.

I usually do this type of thing to provide options to the client:

def list_posts(opts \\ []) do
  preload = Keyword.get(opts, :preload)

  Post
  |> Repo.preload(preload)
  |> Repo.all()
end

then:

MyApp.Blog.list_posts(preload: :comments)

This takes advantage of ecto ignoring nil options (so you don’t have to pass :preload).

I’m admittedly on the fence about this since it is still exposing parts of ecto to the client, but I haven’t worked on a big enough project to where I can confirm how this plays out. It’s also not so bad since generally you are only specifying relationships as data which the client already knows anyway.

This also doesn’t address preloading with a select, so in this case you’d need to make a custom function for the specific scenario. I, admittedly, never worry about loading more columns than I need as I’ve never worked on anything where those types of optimizations made any kind of noticeable difference (this is especially not as big a problem with LiveView since the extra fields aren’t being sent to the browser). It is, however, something I have thought about more than a few times and would be super interested to hear what other people do.

EDITED to add the default [] for opts, which is pretty critical.

eahanson

eahanson

I typically try to keep my repo function calls in my contexts. I my apps, I’d write your example code like this:

item =
  conn.params["id"]
  |> SomeContext.get_item!()
  |> SomeContext.preload_item_users()

Sometimes the preloads can get complex (eg, preload some deep associations and sort everything correctly), so putting them somewhere outside of the UI code makes them easier to find for later reuse.

zachallaun

zachallaun

I really like the solution/technique you proposed. Regarding this particular worry, I think you’re in the clear by “allowlisting” the repo options that you accept, like you do with :preload here. This isn’t fundamentally any different than defdelegate to export a function from a nested module. You’re essentially just making that specific option a part of your public API. I think where it would be a problem is if you passed opts directly to the repo, which you of course aren’t doing.

Last Post!

sodapopcan

sodapopcan

Honestly I picked this up largely from a post I read here at some point, I just can’t find where, so while I don’t want to take all the credit, I don’t know who to credit.

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
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
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
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
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
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
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
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews