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 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] timestamps end end
and an association model
Contact through which the
User has many contacts (
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 timestamps end def status_code(status) do case status do :accepted -> 1 :pending -> 0 :rejected -> -1 _ -> 0 end end
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?
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 #1 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:
- Create a query that will ask for
status1 and with
- Get a
- Preload the
_internal_contactson that instace using our query (these are
- Preload the
:contactson our user. And since
:contactshas only valid contacts now (
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" end
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.