<turbo-stream action="append" target="posts_list"><template>    <div class="postbit" id="238324" data-post-id="238324">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="benwilson512" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/benwilson512/120/1457_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  benwilson512
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>Author of Craft GraphQL APIs in Elixir with Absinthe</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>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:</p>
<p>On the one hand: Presentation / API layers tend to want / need a lot of flexibility in terms of asking for information<br>
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.</p>
<p>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:</p>
<h1><a name="p-238324-the-dataloader-pattern-1" class="anchor" href="#p-238324-the-dataloader-pattern-1" aria-label="Heading link" rel="nofollow"></a>The dataloader pattern</h1>
<p>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).</p>
<h1><a name="p-238324-cqrs-style-read-models-2" class="anchor" href="#p-238324-cqrs-style-read-models-2" aria-label="Heading link" rel="nofollow"></a>CQRS style Read Models</h1>
<p>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.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="238324" data-batch-url="/posts/batch_likers">
                        7
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/where-do-you-preload-associations/45031/12">Post #11</a>
	                </div>
	            </div>
              <div id="likers-container-238324" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="238324"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #11"></div>
  </section>
</div>
    <div class="postbit" id="238363" data-post-id="238363">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="al2o3cr" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/al2o3cr/120/3457_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  al2o3cr
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="josefrichter" data-post="1" data-topic="45031">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josefrichter/48/19529_2.png" class="avatar"> josefrichter:</div>
<blockquote>
<p>Or would have separate <code>get_class!/1</code> and <code>get_class_with_assoc!/1</code> in context?</p>
</blockquote>
</aside>
<p>It depends - the need for preloads “sometimes” could mean you have <em>two</em> concepts and therefore should have two functions.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="238363" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/where-do-you-preload-associations/45031/13">Post #12</a>
	                </div>
	            </div>
              <div id="likers-container-238363" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="238363"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #12"></div>
  </section>
</div>
    <div class="postbit" id="238392" data-post-id="238392">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="aswinmohanme" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/aswinmohanme/120/25377_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  aswinmohanme
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I have a <code>with_assoc(schema, assoc)</code> function in each context that just passes the association directly to the <code>Repo.preload(assoc)</code> function.</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">  def with_assoc(book, assoc), do: Repo.preload(book, assoc)
</code></pre>
<p>I use it directly in the controller/live_view directly</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">    book = Books.get_book_from_slug!(book_slug) |&gt; Books.with_assoc([:author, :draft])
</code></pre>
<p>When I want to use the same logic somewhere else, I create a function called <code>get_book_from_slug_with_author_draft(book)</code> and continue.</p>
<p>Gives flexibility without sacrificing readability.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="238392" data-batch-url="/posts/batch_likers">
                        8
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/where-do-you-preload-associations/45031/14">Post #13</a>
	                </div>
	            </div>
              <div id="likers-container-238392" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="238392"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #13"></div>
  </section>
</div>
    <div class="postbit" id="240389" data-post-id="240389">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="josefrichter" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josefrichter/120/19529_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  josefrichter
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I just came across interesting variation on how to incorporate preloads into parameters (and combine with various other):</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">def list_classes(criteria \\ []) when is_list(criteria) do
    query = from(c in Class, order_by: c.title)

    Enum.reduce(criteria, query, fn
      {:paginate, %{page: page, per_page: per_page}}, query -&gt;
        from q in query,
          offset: ^((page - 1) * per_page),
          limit: ^per_page

      {:sort, %{sort_by: sort_by, sort_order: sort_order}}, query -&gt;
        from q in query, order_by: [{^sort_order, ^sort_by}]

      {:preload, associations}, query -&gt;
        from q in query, preload: ^associations
    end)
    |&gt; Repo.all()
  end
</code></pre> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="240389" data-batch-url="/posts/batch_likers">
                        3
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/where-do-you-preload-associations/45031/15">Post #14</a>
	                </div>
	            </div>
              <div id="likers-container-240389" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="240389"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #14"></div>
  </section>
</div>
    <div class="postbit" id="240396" data-post-id="240396">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="aesmail" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/aesmail/120/32118_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  aesmail
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Interesting approach!</p>
<p>I would extract the code inside the <code>reduce</code> function into its own function with pattern matching maybe.</p>
<p>For example:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">def list_classes(criteria \\ []) do
  query = from(c in Class, order_by: c.title)

  criteria
  |&gt; Enum.reduce(query, &amp;combine_criteria/2)
  |&gt; Repo.all()
end

defp combine_criteria({:paginate, %{page: page, per_page: per_page}}, query) do
  # create pagination
end

defp combine_criteria({:sort, %{sort_by: sort_by}}, query) do
  # sorting
end
</code></pre> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="240396" data-batch-url="/posts/batch_likers">
                        2
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/where-do-you-preload-associations/45031/16">Post #15</a>
	                </div>
	            </div>
              <div id="likers-container-240396" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="240396"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #15"></div>
  </section>
</div>
    <div class="postbit" id="240398" data-post-id="240398">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="soup" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/soup/120/31425_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  soup
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I use something like that. I forget where I got most of the code from,<br>
or at least the scaffolding of the idea.</p>
<p>Here’s a rough dump of it that I might as well share. I can not promise<br>
to the quality of it, use at your own risk. It’s not perfect, it’s probably not<br>
even good really, it’s a crutch. It’s kind of like a poormans <code>GraphQL</code> mixed<br>
with a less expressive <code>Ecto.Query</code>. The worst of all worlds.</p>
<p>Usage looks roughly like:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">Posts.list_posts(
  by_category: "unhinged-rants",
  posted_between: {last_year, now},
  assocs: [:author, :comments]
)
</code></pre>
<p>It lets you define broad ranges of queries in a pretty flexible manner.<br>
It doesn’t cover everything, you sometimes need to write<br>
custom one-shots. You can also use the <code>Post.Queries</code> module directly to<br>
pipe that into <code>Repo.exists()</code> etc.</p>
<p>What I <em>do</em> like about it is, since the API is basically defined as functions,<br>
you get pretty good code suggestions, as well as “no matching function” errors,<br>
including the list of possible choices, when you muck up. You can also make<br>
your API as confusing as you like, with <code>assocs: [:authors, {:comments, magic_number_8}, "desc"]</code> etc, since you’re just pattern matching on something.<br>
(More practically, I have a few fields that can be <code>true | false | [true, false]</code> or <code>date | {date_start, date_end}</code> and the interface feels pretty<br>
fluid).</p>
<p>Managing nested associations can be awkward, it’s best to set a hard limit.<br>
Maybe getting comment authors via a post is ok, but getting comment author<br>
likes is a bit “too” nested. As I said, it is also kind of a poormans<br>
everything and you will definitely hit queries that are painful to run, or<br>
unexpectedly limit join results because of a where clause.</p>
<p>I think running something like GraphQL as your internal API might be the best<br>
bet in the end. I find controllers/views often have pretty<br>
fluid-but-also-duplicated query requirements and you just end up needing<br>
something that supports that. I am not sure if Absinthe really supports being<br>
used as an internal API though. The docs mention one way with<br>
<code>absinthe_phoenix</code>, but it’s pretty slim and <code>absinthe_phoenix</code>’s docs seem to<br>
be more about exposing a GraphQL API via Phoenix? Truthfully I just haven’t had<br>
the time to run through it though.</p>
<p>Anyway, here’s the code, may contain gore, viewer discretion is advised.</p>
<p>The macro, which you use to define your “queryable” terms.</p>
<pre><code>defmodule YourApp.Queryable do
  @moduledoc """
  Helper macros for defining a "Queries" interface.

  Call Queryable.setup() then

  ```
  Queryable.has_filter(:filter_name, fn query, filter_value -&gt;
    query
    |&gt; where ...
  end)
  ```

  ```
  Queryable.has_assoc(:parts, fn query -&gt;
    query
    |&gt; preload(:parts)
  end)
  ```

  which produces

  ```
  Queries.filter_name(query, val)
  Queries.with_parts(query)
  ```

  and

  ```
  list_x(filter_name: 1, assocs: [:parts])
  ```
  """

  @doc """
  Required to setup option chaining functions. 

  Accepts an optional "preflight" function for altering options, defaults to

  ```
  fn opts -&gt; opts end
  ```
  """
  defmacro setup() do
    f =
      quote do
        fn opts -&gt; opts end
      end

    quote_setup(f)
  end

  defmacro setup(func) do
    quote_setup(func)
  end

  defp quote_setup(preflight_opts) do
    quote do
      def apply_opts(query, opts) do
        opts = unquote(preflight_opts).(opts)

        # force assocs to be done first so we can rely on "has_named_binding"
        # in queries if we want to do checks only if the data was requested.
        opts =
          case Keyword.has_key?(opts, :assocs) do
            true -&gt;
              {assocs, opts} = Keyword.pop(opts, :assocs)
              [{:assocs, assocs} | opts]

            false -&gt;
              opts
          end

        do_filter(query, opts)
      end

      defp do_filter(query, []) do
        query
      end

      defp do_filter(query, [{:assocs, assocs} | rest]) do
        query
        |&gt; do_assoc(assocs)
        |&gt; do_filter(rest)
      end

      defp do_assoc(query, []) do
        query
      end
    end
  end

  # func = {:fn [line: 4] [...]}
  defmacro has_filter(name, func) when is_atom(name) and is_tuple(func) do
    # has_filter(:by_id, fn q, id -&gt; where(q, id: ^id))
    quote do
      # def by_id(query, val)
      def unquote(name)(query, val) do
        unquote(func).(query, val)
      end

      # def do_filter(query, [{:by_id, val} | rest])
      defp do_filter(query, [{unquote(name), val} | rest]) do
        query
        |&gt; unquote(name)(val)
        |&gt; do_filter(rest)
      end
    end

    # |&gt; tap(fn m -&gt;
    #   m
    #   |&gt; Macro.to_string()
    #   |&gt; IO.puts()
    # end)
  end

  # {:fn [line: 4] [...]}
  defmacro has_assoc(name, func) when is_atom(name) and is_tuple(func) do
    # has_assoc(:seller, fn q, id -&gt; ... from(..., preload: ...)
    quote do
      # the actual joiner
      defp with_join(query, unquote(name)) do
        unquote(func).(query)
      end

      # def with_seller(query)
      def unquote(:"with_#{name}")(query) do
        with_join(query, unquote(name))
      end

      # def do_assoc(query, [:seller | other])
      defp do_assoc(query, [unquote(name) | rest]) do
        query
        |&gt; with_join(unquote(name))
        |&gt; do_assoc(rest)
      end
    end

    # |&gt; tap(fn m -&gt;
    #   m
    #   |&gt; Macro.to_string()
    #   |&gt; IO.puts()
    # end)
  end
end
</code></pre>
<p>Then define the “Queries” for a model:</p>
<pre><code>defmodule YourApp.Posts.Queries do
  import Ecto.Query, warn: false
  import YourApp.Ecto.Query
  alias YourApp.Posts.Post
  require YourApp.Queryable, as: Q

  @type query_options :: [
          by_ids: [integer(), ...] | nil,
          by_slug: String.t() | nil,
          assocs: [:comments, :authors, ...] | nil,
          by_search: String.t() | nil
        ]

  def posts() do
    base()
  end

  Q.setup()

  Q.has_filter(:by_id, fn query, id when is_integer(id) or is_binary(id) -&gt;
    query
    |&gt; where([post: post], post.id == ^id)
  end)

  Q.has_filter(:by_slug, fn query, slug when is_binary(slug) -&gt;
    query
    |&gt; where([post: post], post.slug == ^slug)
  end)

  Q.has_filter(:by_search, fn
    query, search -&gt;
      search =
        search
        |&gt; to_tsquery_format()

      query
      |&gt; where(
        [post: p],
        tsvector_search(
          [
            tsvector(p.title, weight: "A"),
            tsvector(p.body, weight: "B")
          ],
          tsquery(^search)
        )
      )
      |&gt; Ecto.Query.order_by(
        [post: p],
        {:desc,
         tsvector_rank(
           [
             tsvector(p.title, weight: "A"),
             tsvector(g.body, weight: "B")
           ],
           tsquery(^search)
         )}
      )
  end)

  Q.has_assoc(:comments, fn query -&gt;
    if has_named_binding?(query, :comments) do
      query
    else
      from([post: post] in query,
        left_join: comments in assoc(group, :comments),
        as: :comments,
        preload: [comments: comments]
      )
    end
  end)

  Q.has_assoc(:author, fn query -&gt;
    if has_named_binding?(query, :author) do
      query
    else
      from([post: post] in query,
        left_join: author in assoc(post, :author),
        as: :author,
        preload: [author: author]
      )
    end
  end)

  defp base() do
    from(_ in Post, as: :post)
  end
end
</code></pre>
<p>And define an API inlet:</p>
<pre><code>defmodule YourApp.Posts do

  import Ecto.Query, warn: false
  alias YourApp.Repo

  alias YourApp.Posts.Post
  alias YourApp.Posts.Queries

  @spec list_posts(Queries.query_options()) :: [Post.t(), ...]
  def list_posts(opts \\ []) do
    Queries.posts()
    |&gt; Queries.apply_opts(opts)
    |&gt; Repo.all()
  end

  @spec get_post(integer() | String.t(), Queries.query_options()) ::
          {:ok, Post.t()} | {:error, atom()}
  def get_post(id, opts \\ []) when is_integer(id) or is_binary(id) do
    Queries.posts()
    |&gt; Queries.by_id(id)
    |&gt; Queries.apply_opts(opts)
    |&gt; Repo.fetch_one()
  end
end
</code></pre>
<p>I have also tried the forms:</p>
<pre><code>Posts.posts()
|&gt; Posts.by_category("x")
|&gt; Posts.with_comments()
|&gt; Posts.list()
</code></pre>
<p>and</p>
<pre><code>Posts.list_posts(fn query -&gt;
  query
  |&gt; Post.by_category("x")
  |&gt; Post.with_comments()
end)
</code></pre>
<p>but have found the macro to be nicest to use in the end. I actually don’t<br>
really <em>love</em> any of them though.</p>
<p>So to answer the original question, I write my preloaders in their own <code>Queries</code> module (generally) per-model and then write my “queries” directly in the view/controller, but they are abstracted behind the data context.</p>
<p>I kind of wrote that macro out one day, then just got on with using it. I have been meaning to return and polish it up, as I think it could probably be reasonably transportable between projects. It can run into namespace issues with stuff like sorting though, the developer needs to make decisions and be consistent with what “names” they give to query fields.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="240398" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/where-do-you-preload-associations/45031/17">Post #16</a>
	                </div>
	            </div>
              <div id="likers-container-240398" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="240398"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #16"></div>
  </section>
</div>
    <div class="postbit" id="240417" data-post-id="240417">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="josefrichter" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josefrichter/120/19529_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  josefrichter
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="josefrichter" data-post="15" data-topic="45031" data-full="true">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josefrichter/48/19529_2.png" class="avatar"> josefrichter:</div>
<blockquote>
<p>I just came across interesting variation on how to incorporate preloads into parameters (and combine with various other):</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">def list_classes(criteria \\ []) when is_list(criteria) do
    query = from(c in Class, order_by: c.title)

    Enum.reduce(criteria, query, fn
      {:paginate, %{page: page, per_page: per_page}}, query -&gt;
        from q in query,
          offset: ^((page - 1) * per_page),
          limit: ^per_page

      {:sort, %{sort_by: sort_by, sort_order: sort_order}}, query -&gt;
        from q in query, order_by: [{^sort_order, ^sort_by}]

      {:preload, associations}, query -&gt;
        from q in query, preload: ^associations
    end)
    |&gt; Repo.all()
  end
</code></pre>
</blockquote>
</aside>
<p>Just for completeness, the code comes from Pragmatic Studio and their wonderful LiveView Pro course. I just added the associations bit there. It’s great to rewatch the course as I noticed a couple of details that were beyond my skills the first time around. Pragmatic Studio has the best courses in the history of humankind <img src="https://forum.elixirforum.com/images/emoji/apple/slightly_smiling_face.png?v=15" title=":slightly_smiling_face:" class="emoji" alt=":slightly_smiling_face:" loading="lazy" width="20" height="20"></p>
<aside class="onebox allowlistedgeneric" data-onebox-src="https://pragmaticstudio.com/phoenix-liveview">
  <header class="source">
      <img src="https://pragmaticstudio.com/favicon-32x32.png" class="site-icon" alt="" width="32" height="32">

      <a href="https://pragmaticstudio.com/phoenix-liveview" target="_blank" rel="noopener nofollow ugc">pragmaticstudio.com</a>
  </header>

  <article class="onebox-body">
    <div class="aspect-image" style="--aspect-ratio:690/359;"><img src="https://pragmaticstudio.com/open-graph-image.jpg" class="thumbnail" alt="" width="690" height="359"></div>

<h3><a href="https://pragmaticstudio.com/phoenix-liveview" target="_blank" rel="noopener nofollow ugc">The Pragmatic Studio</a></h3>

  <p>Premium video courses for software developers. Real apps. Real code. Really good stuff!</p>


  </article>

  <div class="onebox-metadata">
    
    
  </div>

  <div style="clear: both"></div>
</aside>
 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="240417" data-batch-url="/posts/batch_likers">
                        2
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/where-do-you-preload-associations/45031/18">Post #17</a>
	                </div>
	            </div>
              <div id="likers-container-240417" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="240417"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #17"></div>
  </section>
</div>
    <div class="postbit" id="240426" data-post-id="240426">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="krasenyp" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/krasenyp/120/39733_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  krasenyp
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Am I the only one having a problem with <code>Repo.preload</code>? It does an extra query when you can use a join in more than 90% of the time.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="240426" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/where-do-you-preload-associations/45031/19">Post #18</a>
	                </div>
	            </div>
              <div id="likers-container-240426" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="240426"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #18"></div>
  </section>
</div>
    <div class="postbit" id="240432" data-post-id="240432">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="josefrichter" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josefrichter/120/19529_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  josefrichter
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>You’re not the only one <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"> I wonder if that’s always the case though? Isn’t ecto smart enough to combine <code>Repo.all() |&gt; Repo.preload(..)</code> into one query at least in some situations?</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="240432" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/where-do-you-preload-associations/45031/20">Post #19</a>
	                </div>
	            </div>
              <div id="likers-container-240432" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="240432"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #19"></div>
  </section>
</div>
    <div class="postbit" id="240433" data-post-id="240433">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="LostKobrakai" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/LostKobrakai/120/3072_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  LostKobrakai
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>It’s not and that’s a good thing. Ecto by default does one query per association (in parallel), transfering the least amout of data from the db to elixir. Doing less queries will “do less queries” but will also result in more duplicated data transfered (joins duplicate data most cases). It’s not a guarantee that doing that switch will actually be more performant. Therefore if you want things to be preloaded from joins ecto requires you to explicitly state that in the query.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="240433" data-batch-url="/posts/batch_likers">
                        6
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/where-do-you-preload-associations/45031/21">Post #20</a>
	                </div>
	            </div>
              <div id="likers-container-240433" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="240433"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #20"></div>
  </section>
</div>
</template></turbo-stream><turbo-stream action="replace" target="load-more-container"><template><div id="load-more-container" class="load-more-container">
    <a class="load-more-button" data-turbo-stream="true" href="/topics/45031/load_more?page=3">Load more posts (4 remaining)</a>
</div></template></turbo-stream>