Filtering has_many relationships in Ecto

This is sot-of a follow up to my last post, about self-referencing many_to_many relationships using Ecto.

I find myself in a scenario where I have a User:

# User model
defmodule MyApp.User do
  use MyApp.Web, :model
  alias MyApp.Contact
  schema "users" do
    has_many :_internal_contacts, MyApp.Contact
    has_many :contacts, through: [:_internal_contacts, :contact]

and an association model Contact through which the User has many contacts (User instances):

defmodule MyApp.Contact do
  use MyApp.Web, :model
  alias MyApp.User
  schema "contacts" do
    field :status, :integer
    belongs_to :user, User, foreign_key: :user_id
    belongs_to :contact, User, foreign_key: :contact_id
  def status_code(status) do
    case status do
      :accepted ->
      :pending ->
      :rejected ->
      _ ->

I created an endpoint on my app that’s supposed to get me only the user’s contacts that have the :accepted status (1). How can this be accomplished?

The Repo module has a set of handy functions that let you preload associations on your models.

user = User |> Repo.get(1) |> Repo.preload(:contacts)

The above will preload all contacts on the user. Pay attention to the fact that it is of type has_many and it goes through the :_internal_contacts property. This is the important cue.

Turns out that the preloads parameter on Repo.preload/3 can be accompanied by a query.

import Ecto.Query
query = from c in Contact, where: c.status == 1 and c.user_id == 1
u = User
    |> Repo.get(1) #2
    |> Repo.preload(_internal_contacts: query) #3
    |> Repo.preload(:contacts) #4

The code above will:

  1. Create a query that will ask for Contacts with status 1 and with user_id 1.
  2. Get a User instance.
  3. Preload the _internal_contacts on that instace using our query (these are Contact instances, not User instances)
  4. Preload the :contacts on our user. And since :contacts goes through: _internal_contacts, :contacts has only valid contacts now (User instances).


Honestly, I don’t know if this is the right approach here (let me know!). I did find this thread on GitHub where the general issue that we’re facing here is described. It seems that there’s an interest to have the ability to filter the relationships on the declaration itself:

It would look something like this:

has_many :comments, MyApp.Comment, foreign_key: :commentable_id,  fn (query) ->
  from c in query, where: c.commentable_type == "articles"

However, right now, the saner approach seems to be the use of composable queries.

Have anything to add to this article or did I miss something? Please let me know on Twitter.

Esta entrada fue publicada en Uncategorized y etiquetada , , , . Enlace permanente.