Trying to learn how to properly do things in Ecto

Hi, experienced programmer but beginner elixir user here. I’ve been working on a Phoenix app on and off for a few weeks now and have reached the point where I really need to learn the proper way to query Ecto. The problem I’m facing now is that say I have a User, and Users have A’s, and each A has multiple B’s. If I have the User.id, A.id, and B.id, how do I get that exact model from Ecto? Is it no enough to just do something like

def show(conn, %{"id" => Bid}) do
  bModel = B |> Repo.one!(Bid)
  render(conn, "show.json", bModel: bModel)
end

?
I’d love help with this exact problem, but also any general tips or tutorials for learning how I’d properly query Ecto.

Thanks!

1 Like

Hi there,

So what are the A’s and B’s

Are these different models that are associated with the user? Just trying to get a grip on how these relate. Or are these fields defined on the User model?

1 Like

Sorry, these are models associated with the user.

schema "user" do
  :has_many :As, Project.A
end

schema "a" do
  :has_many :Bs, Project.B
end

OK, that makes more sense.

Well first of all, you should complete the association in the associated model.

So:

schema "user" do
  :has_many :As, Project.A
end

schema "a" do
  :belongs_to: :user, Project.User
  :has_many :Bs, Project.B
end

Then you should be able to preload these associations like this(note I just learned this as well so maybe some experienced people can join in here as well)

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

Does that make sense?

1 Like

But what if I don’t want to get the User model with the A’s and B’s preloaded, I just want the B model by itself?

In what way? I’m assumng you want a B model that is associated with a particular user?

I want the B model associated with a particular A that is associated with a particular User. If I have User.id = 1, A.id = 2, and B.id = 3, how do I get the B model with id 3 from Ecto? From what I’ve followed in tutorials, this is how I would get the A

def show(conn, %{"id" => a_id}) do
  current_user = Guardian.Plug.current_resource(conn)

  a = current_user
    |> assoc(:As)
    |> Repo.get(a_id)

 ...
end

And this would get me the A model with whatever ID I gave it. But what if I need to go one level deeper and get a certain B associated with a certain A associated with a certain User? I may just have a flawed understanding of how some of this works.

Wouldn’t

Get me the whole User with As (and presumably Bs) preloaded, and not just the B?

You could just join B to User through A via a query and preload B’s that way, efficient if there are few B’s per User, otherwise a secondary query may be faster.

Would you mind giving me an example of how that’d be done? I have almost no experience using Ecto queries.

Important question: Are you already familiar with SQL or no? Further answers to your question depend upon this because we need to know if we’re merely explaining how you do a particular SQL pattern in ecto, or if we need to explain the relevant SQL patterns as well.

Ahh good point. Yes I know SQL, I just need explaining on the Ecto part.

Similar to how you would do it in Sql, something like (I’m on phone so please excuse oddities ^.^):

from u in User,
  join: a in A, on: a.user_id == u.id,
  join: b in B, on: b.a_id == a.id,
  select: {u, b}

Though if you have B’s in A’s multiple times you may get duplicate B’s but you can fix that be adding something like group_by [u.id, b.id] in there too or something like that if I have the syntax right.

1 Like

Ahh, I figured there would be some fancy shmancy Ecto way of doing it with more abstraction. I guess I’ll go read the Query docs all the way through to make more sense of all of this. These magical macros are the one part about Elixir that is frustrating to learn. I’m never sure what’s part of the language and what’s magic added from some macro.

It is not actually magical, if you look at the source things the query syntax just builds and mutates a simple record of data, of which is passed to, say, the PostgreSQL adaptor, which then takes the data in that record to build a query. It is actually remarkably simple. :slight_smile: