Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ecto: How to preload records with selecting another joined columns

Are there any ways to preload records by selecting another joined columns?

# table structure
# User 1---* Post 1---* PostTag *---1 Tag

# extract definition of scheme
scheme "posts" do
 ...
 has_many :post_tags, PostTag
 has_many :tags, [:post_tags, :tag]
end

Following pseudo-code expresses my goal(but not work).

query = from post in Post,
  join: user in User, on post.user_id == user.id,
  select: %{
    id: post.id,
    title: post.title,
    user_name: user.name, # <= column at joined table
  },
  preload: [:tags]
Repo.all(query)
#=> ** (Ecto.QueryError) the binding used in `from` must be selected in `select` when using `preload` in query:`

I expect the result like this.

[
  %{id: 1, title: "AAA", user_name: "John", tags: [%{name: "elixir"},...]},
  %{id: 2, title: "BBB", user_name: "Mike", tags: [%{name: "erlang"},...]},
  ...
]
like image 472
KONDO Daisuke Avatar asked Feb 01 '18 07:02

KONDO Daisuke


1 Answers

As the error message says, you need to select the binding you gave in from when you are preloading, otherwise Ecto has no place to put the preloaded tags. Here is a simple answer:

query = from post in Post,
  join: user in User, on: post.user_id == user.id,
  select: {post, user.name},
  preload: [:tags]

By returning a tuple, you can have the full post and the user.name on the side. Another approach is to return both post and users as full structs:

query = from post in Post,
  join: user in User, on: post.user_id == user.id,
  preload: [:tags, user: user]

or if you don't want all fields:

query = from post in Post,
  join: user in User, on: post.user_id == user.id,
  preload: [:tags, user: user],
  select: [:id, :title, :user_id, user: [:name]]
like image 126
José Valim Avatar answered Oct 10 '22 10:10

José Valim