I’m posting this for developers who are totally new to Phoenix like myself. This is all probably obvious to more skilled Phoenix developers, but I spent a lot of time figuring this out so thought I might save others time by posting the solution.
SOLUTION SUMMARY: This explains how to have an unauthenticated liveview hand off to a set of authenticated liveviews. Authorization is being handled by phx.gen.auth.
APP GOAL: I want to present the user with all of the available groups. If they like a group, I want them to “Save” it to their own repository of saved groups called My Groups. This means that the “Index” and “Show” pages will be unauthenticated and allow anyone to view them. But if the user clicks “Save”, then they need to be authenticated and passed to the apply_action “save” functionality.
WHAT DID NOT WORK: I had autogenerated liveviews for “groups” using phx.gen.live. I also had installed phx.gen.auth and had authentication completely working. To achieve my goal, I thought that the only thing I needed to do was change the router like this:
scope "/", MyApp do
pipe_through :browser
live "/groups", GroupLive.Index, :index
live "/groups/:id", GroupLive.Show, :show
end
scope "/", MyApp do
pipe_through [:browser, :require_authenticated_user]
live "/groups/:id/edit", GroupLive.Index, :edit
live "/groups/:id/save", GroupLive.Index, :save
live "/groups/new", GroupLive.Index, :new
live "/groups/:id/show/edit", GroupLive.Show, :edit
end
Side note: If I ran all of the “group” liveviews as UNauthenticated, then it worked fine. And if I ran all of the “group” liveviews as Authenticated, then it ran fine. It was when I tried to split the functionality that things got tricky.
First, I struggled with how to make the “mount” function in Index only request the authenticated user IF the user had asked to save the “group.” I posted on Elixir Forum and @kokolegorille pointed me in the right direction. He explained that it was better to keep the unauthenticated functionality in one set of liveviews and the authenticated functionality in another set.
Once I separated the functionality, my router looked like this:
scope "/", MyApp do
pipe_through :browser
live "/groups", GroupLive.Index, :index
live "/groups/:id", GroupLive.Show, :show
end
scope "/", MyApp do
pipe_through [:browser, :require_authenticated_user]
live "/mygroups", GroupLive.MyGroups, :index
live “/mygroups/:id/edit", GroupLive.MyGroups, :edit
live “/mygroups/:id/save", GroupLive.MyGroups, :save
live “/mygroups/new", GroupLive.MyGroups, :new
live “/mygroups/:id/show/edit", GroupLive.Show, :edit
end
I added two new files to my group_live directory:
my_groups.ex
my_groups.html.leex
** See note at bottom about big naming convention mistake I made that cost me hours.
THE HAND OFF
The code inside GroupLive.Index remained relatively the same, although I deleted all of the functionality for everything except :index because it was no longer needed.
The tricky part came with “index.html.leex.” First I removed the modal code that was autogenerated. It is for “new” and “edit.” That functionality would not happen in an unauthenticated page, so I deleted that from the top of the html.leex. Next, I needed to add the functionality for a “Save” link.
I needed to call a Router helper to redirect to the MyGroups liveview, but I could not figure out how to do that. I read in hexdocs that “Phoenix automatically generates a module Helpers inside your router which contains named helpers to help developers generate and keep their routes up to date.” I looked EVERYWHERE for that module and could not find it, so I couldn’t figure out where that functionality was coming from. Again, @kokolegorille helped out and pointed me to the tool for figuring out the routes. You want to run the following command: mix phx.routes. When I did that, I found all of the routes for my app:
group_index_path GET /groups Phoenix.LiveView.Plug :index
group_show_path GET /groups/:id Phoenix.LiveView.Plug :show
group_my_groups_path GET /mygroups Phoenix.LiveView.Plug :index
group_my_groups_path GET /mygroups/:id/edit Phoenix.LiveView.Plug :edit
group_my_groups_path GET /mygroups/:id/save Phoenix.LiveView.Plug :save
group_my_groups_path GET /mygroups/new Phoenix.LiveView.Plug :new
group_show_path GET /mygroups/:id/show/edit Phoenix.LiveView.Plug :edit
Here are links to useful documentation:
Router: Phoenix.Router — Phoenix v1.5.8
Routing: Routing — Phoenix v1.5.8
LiveView.Helpers: Phoenix.LiveView.Helpers — Phoenix LiveView v0.15.5
Now that I had the route, I could redirect to my authenticated pages. I changed the code in index.html.leex to this:
<td>
<span><%= live_redirect "Show", to: Routes.group_show_path(@socket, :show, group) %></span>
<span><%= live_redirect "Save", to: Routes.group_my_groups_path(@socket, :save, group) %></span>
</td>
NOTE: you need to use live_redirect NOT live_patch because you are passing functionality to a new liveview.
Inside GroupLive.MyGroups, I added the authentication functionality to mount so that it grabbed the current_user. A lot of the other code was just copied over from Index.ex (the new, edit, delete) functionality.
NAMING CONVENTION MISHAP
Sadly, things still didn’t work. I ran into one more problem that revolved around naming conventions. I named this new liveview “MyGroups” and the file names were originally “mygroups.ex” and “mygroups.html.leex.” NOTE the camel case in the liveview name. I didn’t think anything of it, but it wreaked some havoc. What I didn’t realize is that the liveview name (with a camel case) required that I name the files my_groups.ex and my_groups.html.leex (note the underbar between my_groups). I ran into a series of errors as a result of my files being originally named mygroups.ex and mygroups.html.leex. I finally fixed the file names, BUT I forgot to fix my routes in the Index file. I kept getting the following error:
function MyApp.Router.Helpers.group_my_groups_path/3 is undefined or private
Called with 3 arguments 1. Socket ..., 2. :edit and 3. %Group{...}
I went crazy trying to figure out why it thought the function was undefined. Many thanks to @axelson who caught the typo in my code. My original index.html.leex had the following router path: Routes.group_mygroups_path(@socket, :save, group) BUT it needed to be
Routes.group_my_groups_path(@socket, :save, group). It was the underbar between my_groups that cost me hours of reading documentation and trying to figure out the problem.
Moral of the story: Be really really careful about the naming conventions.