silverdr

silverdr

Building on the example from another thread:
https://forum.elixirforum.com/t/many-to-many-once-again-extra-data-on-the-join-table/
User has many Chatroom through UsersChatrooms.

Now, I would like to get names and ids of given user’s chatrooms. I surely can do for example:

user = Repo.preload(user, :chatrooms)

but this will load “everything” from both final and join tables, while I’d rather do something like Rails’ pluck or at least select so that only columns I am interested in are queried. What are the “correct” ways of doing this with Ecto associations?

Alternatively already while fetching the user record

First 10 of 16 Posts Switch mode

LostKobrakai

LostKobrakai

You can use ecto query to select what you need. There’s not really a high level API for that in ecto.

query = 
  from cr in Chatroom, 
    join: ucr in assoc(:users_chatrooms), 
    on: ucr.user_id == ^user_id and ucr.chatroom_id == cr.id,
    select: map(cr, [:id, :name])

Repo.all(query)

or

user
|> Ecto.assoc(:chatrooms)
|> select([cr], map(cr, [:id, :name])
|> Repo.all()
silverdr

silverdr OP

Ah, so basically this means that one has to build an appropriate query himself. I presume the same would apply if I didn’t have the User struct already and wanted to fetch the user along with its selected associated data, right?

dimitarvp

dimitarvp

Yep, more or less. And it’s important to remember that Ecto is explicit: it tries very hard not to do things you didn’t ask it to do.

silverdr

silverdr OP

TNX.

I can understand and even relate to that on some occasions. On the majority of them though it feels similar to what I felt many years ago when I had to finish application written by somebody else, using “Zend framework”. My main thought and takeaway from that experience was something like “what on Earth do I need this whole Zend framework for, if all I do with it is translating SQL into framework’s syntax, which is going to be then translated back into SQL anyway”. In the majority of cases like the fairly trivial one here, I’d be happy to write a one-liner:

user.chatrooms.pluck(:id, :name)

and be done with it, trusting the library to do The Right Thing™, instead of forcing me to think in SQL terms first, and then how to express those in the library’s own syntax/DSL. So yes, there are pros and cons. And yes, there are cases where fine-grained, low-level tuning is important but in lots of typical ones it isn’t. At least IMHO :wink:

dimitarvp

dimitarvp

I sympathize with the need. You can take it one step further by making helpers for it in your context/domain modules, e.g. with the second coding snippet that @LostKobrakai showed you like so:

defmodule MyApp.Users do
  def with_chatroom_names(user) do
    user
    |> Ecto.assoc(:chatrooms)
    |> select([cr], map(cr, [:id, :name])
    |> Repo.all()
  end
end

And it becomes intuitive to read the consuming modules when they do stuff like Users.with_chatroom_names(user) |> do_other_stuff() …IMO anyway; naming is hard).

I was a Rails dev too, long ago (for 6.5 years). I still sometimes miss bits and pieces from it but as time went by I gravitated towards preferring to get my hands a little more dirty for the added peace of mind that when looking at the app’s code then what I see is exactly what I get.

But again, if you find that clunky, nothing stops you from emulating a few favourite Rails-like idioms in your projects, or even spin them off to internal company libraries (I’ve done so in the past).

silverdr

silverdr OP

:smiley: That’s precisely what I eventually did!

I worked with numerous languages/frameworks but of all of them my relationship with Elixir/Phoenix is so far probably the most “intense” and of “love-hate” kind. I believe thanks to this I have a glimpse of what people with bipolar disorder feel :slight_smile:

LostKobrakai

LostKobrakai

Ecto is far more geared towards giving developers the ability to deal with the sql. It’s not an ORM and doesn’t try to be. One could probably built one on top of ecto. I can see this not being great when trying to get things of the ground, but it’s really great once you need to maintain that thing flying.

silverdr

silverdr OP

That’s what I keep telling myself :wink: and others :wink: And – BTW – I think this is an excellent analogy. That’s exactly how I feel: V1 reached[1] long time ago but Vr still not even close, despite the end of runway uncomfortably well in sight.

[1] - rewriting it in something else no longer an option, especially that it was me who insisted on biting the bullet and diving into this brave new world :wink:

sodapopcan

sodapopcan

Well if that ain’t some hyperbole yet I know exactly what you mean :sweat_smile:

My super hot take as a fellow long-time rails developer is that the sooner you forget all conveniences of rails and any other OO-based web framework you’ve used, the sooner you will find happiness. You will maybe even start to feel that you are in a better place! By your last post, it does seem like you are getting there :slight_smile:

Another thing to keep in mind is that Ecto is very lightweight compared to a memory hog like ActiveRecord. I would say that the true selling point of .pluck is that it bypasses ActiveRecord object creation and returns your results in language primitives. In the case of Ecto, this is all it knows how to do!!!

Where I’m going with this is that say you already have something general like this:

# Chatroom Schema, or wherever you personally store your queries
def for_user(query, user) do
  from q in query,
    where: q.user_id == ^user.id
end

# Chatrooms Context
def for_user(user)
  Chatroom
  |> Chatroom.with_user(user)
  |> Repo.all()
end

Then it’s not the end of the world to do this:

user
|> Chatrooms.for_user()
|> Enum.map(fn %{id: id, name: name} -> {id, name} end)

…especially if your chatrooms table doesn’t have a lot of columns.

You could even pull that last line into a helper:

def plunk(collection, fields) do
  Enum.map(collection, fn entity ->
    List.to_tuple(Enum.map(fields, &entity[&1]))
  end)
end

Now you can do:

user |> Chatrooms.for_user() |> plunk([:id, :name])

…and hey, that kinda almost sorta mostly loosely sorta kinda looks like Rails!

Do I recommend doing this? Not realllllly, but maybe? It would be nice for use in iex. I’m really just sharing where my mind went when I was in your position, especially in regards to unlearning muscle-memory from ORMs and that Ecto is a very different beast in more ways than it makes you deal with SQL more directly.

silverdr

silverdr OP

The first thing I don’t want to do is I don’t want to query the fields I am not interested in. For that I could use select and since on top of that, in the given case I am interested only in values then pluck gives me both in one go :wink:

:smiley: Indeed. But that’s never been my point (or goal). I have nothing against things looking different than Rails (or Laravel, or… ) as long as they add [similar] value. My main problem like the one I described above about Zend long time ago is that I need to think in SQL. That’s not an issue per se. I know SQL more than enough for this. But then I have to laboriously (especially when things become much more complex than the example here) translate SQL into whatever I am coding in so that it can then translate it back into more or less the same SQL I initially came up with. This doesn’t feel good. For the same reasons in Rails I’ve never been a fan of [directly] using Arel for example. If I am to write complex Arel stuff, remembering or looking up how to express this or that SQL in Arel then I rather write SQL and spare both me and the lib the trouble of translating everything back and forth. Yes, I know that I can write plain SQL in Rails (and Arel, and Ecto and others). And I sometimes do but I prefer doing this on relatively rare occasions, like for optimising complex queries for example. Or querying something that couldn’t be/wasn’t modelled as ER.

Anyway - thanks for the input. The major takeaway I think is

:smiley:

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
jonnycharles
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
dli
Before I dive in myself, did anyone successfully sprinkle Hologram into their existing LiveView app? Looking for hints regarding: Addi...
New
roeland
Kia ora, We have been using elixir-google-api to connect to Google Drive. However, with the updates to Tesla due to CVEs this is now bro...
New
bottlenecked
Hi all, I wanted to ask how the community is dealing with post-release steps. Today we have Ecto migrations, which make sure that the db...
New
rahultumpala
Hello, I have an Elixir backend that implements a custom protocol over TCP. I want to load test the backend and assess the performance o...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New

We're in Beta

About us Mission Statement