Newbie needing help with Phoenix project web organization

I find myself increasingly confused by the conventional Phoenix project organization.

The documentation on views states:

Phoenix assumes a strong naming convention from controllers to views to the templates they render. PageController requires a PageView to render templates in the lib/hello_web/templates/page/ directory.

So what exactly is a “Page”? Am I expected to have ALL my routes go through “PageController” regardless of my application’s different “modules/contexts/concerns/domains”. And I am to have only the one View module for all my templates?

Certainly that’s not suitable for anything but the smallest of projects. So then, how am I to split things up for a large project? That is to say what am I replacing “Page” with?

When analyzing this problem with my application it seems to me there are three cross-cutting concerns: Context, Format, and Function. Context is the abstract “modular” divisions of my applications, e.g. admin, sales, inventory, accounts, etc. Format is just the type of file/data being generated and delivered, such as HTML for web pages, Latex or Postscript (converted to PDFs) for reports, and JSON APIs. Function are more or less REST endpoints, like show, list, or edit.

I find that when generating a template my view often has to accommodate all thee of these is some fashion. But I feel like I only have one variable to differentiate on, namely “Page”. So what should “Page” be? … By Context (e.g. “SalesView”), by Format (e.g, “LatexView”) or by Function (e.g. “ListView”)? And how to best accommodate the two concerns not subdivided, to keep the code clean and manageable?

I think this is a relatively simple confusion. To answer your question: no, not at all. PageController is a simple boilerplate controller that exists to show a “static” page when you first boot your app. Sometimes people keep it around to show an /about page on their site, or /privacy, or similar other non domain oriented pages.

Actual routes / views / etc related to your domain would be broken up in the normal way. If you have /products you’ll have a ProductController and so forth.

3 Likes

I can understand this by going through the docs alone. For me the best way to really understand these concepts is to use the generators to create live (mix phx.gen.live) or normal views (mix phx.gen.html). I’m sure you could step outside of what gets created but the generators provide some useful boilerplate and guard rails.

6 Likes

Thanks @benwilson512. That’s what I assumed, but I ran into a wall with this “strong connection” between Controller <-> View. And it totally through me off. Controllers tie closely to the router, so divvying up controllers to different files is a pretty abstract choice – no real issues (other than a large file) even if it’s all one module.

But Views… they are more concrete in that they tie very strongly to templates, and different templates have different needs – function names might clash, and, in general, why make a function available to a template that it doesn’t belong in? So I started second guessing if I actually understood Phoenix’s organizational design.

It seems crazy to me to create a controller for every view. (Why even have separate modules for them if that’s the case?). So my plan now is to use put_view() rather liberally, to compensate.

Just so we are clear on what Phoenix’s traditional structure is: You have a controller, and it is associated with a view by default. That view then is used as the foundation for all of the templates related to the controller, but only that controller. So concretely if you had a ProductController you’d have a ProductView, and then index.html, show.html, edit.html and new.html templates. The reason that you have a single view for those templates is that there is usually a lot of shared functions given that the same %Product{} entity is used throughout.

3 Likes

Right! These docs do read a little confusingly, but are correct. Parse it as:

  • Phoenix assumes a strong naming convention
  • For example, PageController conventionally requires…

But not, Phoenix requires you route everything through only the PageController.

1 Like

Honestly, that “for example” might be a welcome OSS contribution to the docs, if you’re so inclined!

2 Likes