What I want, is to be able to load the messages field, which is a outbound_messages and inbound_messages together.
At present, I use this:
def preload_messages(%Tag{id: id, messages: nil} = tag) do
from(m in Message,
where:
(m.from_tag_id == ^id and m.direction == "outbound") or
(m.to_tag_id == ^id and m.direction == "inbound"),
order_by: [desc: m.inserted_at]
)
|> Repo.all()
|> then(fn msgs ->
%{tag | messages: msgs}
end)
end
But I’d like to also have preload_messages accepting an ecto query.
I tried this:
def preload_messages(%Ecto.Query{} = query) do
message_query = from(m in Message,
where:
(m.from_tag_id == parent_as(:tags).id and m.direction == "outbound") or
(m.to_tag_id == parent_as(:tags).id and m.direction == "inbound"),
order_by: [desc: m.inserted_at]
)
from(t in query,
as: :tags,
preload: [messages: ^message_query]
)
end
but it does not work as it is not an assoc. I tried using has_many but it also doesn’t work because ecto automatically try to use a foreign_key and I want both foreign_keys to be ORed.
I also tried embed_many but it complains with ** (ArgumentError) cannot preload embedded field :messages without also preloading one of its associations as it has no effect.
You could possibly use a custom select keyword in your query:
from t in Tag,
join: im in Message,
# as: :inbound_message,
on: t.from_tag_id == ^message_id,
join: om in Message,
# as: :outbound_message,
on: t.to_tag_id == ^message_id,
select: %{
tag: t,
messages: im ++ om
}
You might also be able to use your virtual field and select directly into it (just a guess).
Using the tag_id virtual field trick doesn’t work either.
[error] Task #PID<0.5921.0> started from #PID<0.5907.0> terminating
** (Ecto.QueryError) deps/ecto/lib/ecto/association.ex:888: field `tag_id` in `where` is a virtual field in schema Taggy.Conversations.Message in query:
from m0 in Taggy.Conversations.Message,
where: (m0.from_tag_id == parent_as(:tags).id and m0.direction == "outbound") or
(m0.to_tag_id == parent_as(:tags).id and m0.direction == "inbound"),
where: m0.tag_id == ^2,
order_by: [asc: m0.tag_id],
order_by: [desc: m0.inserted_at],
select: {m0.tag_id, merge(m0, %{tag_id: parent_as(:tags).id})}
Oh, I’m guessing that where clause and the order by clause also referencing m0.tag_id were added by Ecto itself. Places where I’m doing this kind of trickery, I’m using a preload function instead of query:
def preload_messages(%Ecto.Query{} = query) do
message_fun = fn tag_ids ->
from(m in Message,
inner_join: t in Tag, on: t.id in ^tag_ids and
((m.from_tag_id == t.id and m.direction == "outbound") or
(m.to_tag_id == t.id and m.direction == "inbound")),
order_by: [desc: m.inserted_at],
select_merge: %{tag_id: t.id}
)
end
from(t in query,
as: :tags,
preload: [messages: ^message_fun]
)
end