josefrichter
Where do you preload associations?
Simple question re. code organization: do you do preloads in context, or in controller/liveview?
Say a basic function in context looks like this
def get_class!(id) do
Repo.get!(Class, id)
|> Repo.preload(:users)
end
But basically sometimes I need the association, sometimes I don’t.
So would you typically do get_class!(id) |> Repo.preload(:users) in controller/liveview? (I have a feeling that basically I should never call Repo… from controller/liveview)
Or would have separate get_class!/1 and get_class_with_assoc!/1 in context?
Or am I overthinking and it doesn’t really matter? Thank you.
Most Liked
stefanchrobot
I avoid calling repo from the controllers. My usual pattern is to do this in the context:
def get_class!(id, preloads \\ []) do
Repo.get!(Class, id)
|> Repo.preload(preloads)
end
aswinmohanme
I have a with_assoc(schema, assoc) function in each context that just passes the association directly to the Repo.preload(assoc) function.
def with_assoc(book, assoc), do: Repo.preload(book, assoc)
I use it directly in the controller/live_view directly
book = Books.get_book_from_slug!(book_slug) |> Books.with_assoc([:author, :draft])
When I want to use the same logic somewhere else, I create a function called get_book_from_slug_with_author_draft(book) and continue.
Gives flexibility without sacrificing readability.
benwilson512
This is a generally hard problem IMHO. One of my great open source regrets is not having enough time to make the Dataloader pattern more accessible outside of GraphQL because it was designed to address the following tension:
On the one hand: Presentation / API layers tend to want / need a lot of flexibility in terms of asking for information
On the other hand: Access control, filtering rules, and other data fetching code is often fraught with business logic, and this lives in the contexts.
If you allow the controllers to just pass in preload parameters you skip the business logic. It’s a tough problem. Two ways come to mind for how to solve this:
The dataloader pattern
Basically you create some sort of mediating entity that your business logic can hook into to enforce rules, and your “client” code can use to compose requests to fetch stuff. This works really well in Absinthe (GraphQL) because there is an actual query document to wire Dataloader into. It’s less ergonomic in traditional controllers and ends up being super callback heavy (at least today).
CQRS style Read Models
You create dedicated database views / tables where you can just query them in a simple way and the data has already been written such that it’s impossible to query data you shouldn’t see. This can quickly add a lot of boilerplate.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #hex









