[Beginner question] Router looking into "MyAppWeb.MyAppWeb" rather than just "MyAppWeb"...?

I’m following the Pragmatic Studio Full-Stack Phoenix course and so far everything’s been going smoothly, but I just ran into a weird bug that I’m trying to figure out by myself and I can’t imagine where to begin.

Some liveview routes that were working before stopped working, and even when I try to undo the last changes I did and stop and restart the server, they’re still not working, and I can’t figure out what’s wrong from the error message because it looks so strange to me.

I have this route, for an instance:
live "/estimator", EstimatorLive

Corresponding to the live view I have at lib/raffley_web/live/estimator_live.ex. But now, when I run the server and try to browse to http://localhost:4000/estimator, this is the error I get:

UndefinedFunctionError at GET /estimator

function RaffleyWeb.RaffleyWeb.EstimatorLive.__live__/0 is undefined (module RaffleyWeb.RaffleyWeb.EstimatorLive is not available)

For the life of me I can’t understand why it’s looking for RaffleyWeb.RaffleyWeb.EstimatorLive.__live__/0 rather than RaffleyWeb.EstimatorLive.__live__/0. Can anyone shed some light on what I might’ve screwed up to get this issue…?

please show your router file…

1 Like

This usually happens when you are using an LSP that automatically adds aliases for you.

The way module names are “stitched” together in the router is done with a bit of magic that isn’t really done anywhere else. Since you are a beginner I won’t dive into it, but I’m assuming there is a line near the top of your router that looks like this:

alias MyAppWeb.EstimatorLive

Delete that and you should be fine.

If not then yes, please show your router file.

3 Likes

On my phone, but your problem is probably from a scope “route”, MyAppWeb in your router. Perhaps someone else can provide a more comprehensive example of that issue as we’ve all done it before :joy:

THERE THEY ARE

defmodule RaffleyWeb.Router do
  alias RaffleyWeb.EstimatorLive
  alias RaffleyWeb.RaffleLive

HOW DID THEY GET THERE… I never would’ve found them since I wasn’t editing that part of the file at all… I know what to watch out for now :sob: Thank you so much!

3 Likes

It’s your LSP trying to be helpful. Normally it is helpful just not in the router.

It’s a bit hard to explain but when you have:

scope “route”, MyAppWeb do
  live "/", EstimatorLive
end

it may look like a language feature to break up a module name like that, but it’s actually a Phoenix macro (scope) that combines them into MyAppWeb.EstimatorLive at compile time (EstimatorLive on its own is not a valid module in this context). But before that happens, aliases are expanded which is why you end up with MyAppWeb.MyAppWeb.EstimatorLive. I believe this is more about avoiding compile-time dependencies than it is convenience but ya, I won’t dive into that if you are just starting out :slight_smile:

5 Likes

I’m honestly not a big fan of the approach of defining partial modules from day one, the fact that it doesn’t play well with the language server is even more annoying. I think if I knew only elixir today, I would have also took a long time to understand wtf is going on, the default generated route when creating a new phoenix project makes it easier to understand, but still.

For some reason @sasajuric implemented a similar concept in boundary, maybe he can enlighten us more on this decision?

That probably happened when you used tab-completion when a suggestion popped up.

That one gets me a lot…

I remember Chris talking about it having to do with compile time dependencies, and I’d believe it as I have done some similar tricks in different parts of the router to fool it into thinking an atom wasn’t referring to a module (this was on an existing project that had a crap ton of compile time deps). I tried to do a little demo to prove it but I can’t make it work soooooo you’d have to wait on other responses. I’m guessing Boundary is more for convenience (as I also couldn’t make it give me compile time deps but again I didn’t try too hard) but ya, we just added it to a new project last week and my coworker totally got tripped up by it :sweat_smile: I actually do appreciate the convenience aspect in Boundary, though. Especially if you are dealing with overly stringent Credo rules that force you to do this:

alias __MODULE__.Foo
alias __MODULE__.Bar
alias __MODULE__.Baz

use Boundary, export: [Foo, Bar, Baz]

These issues with compile time dependencies could be gone by now. There have been lot of improvments to that in elixir over time. But it was indeed a real concern in earlier times.

Ya, I wondering if this was the case too and so the syntax is just a relic. It was not too long ago that just naming an atom that referred to a module—ie, without calling a function from it—in a module body/macro would create a dependency but that is no longer the case.

I had never looked closely at the signature for scope and I see that scope("/", Module) is a convenience for scope("/", alias: Module). The latter is a little clearer. At this point status quo doesn’t really bother me at all, but it does cause confusion for beginners.