phoenix view module not available - phoenix-framework

My application works and api json requests and for regular html. My router.ex
defmodule MyApp.Router do
use MyApp.Web, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :api do
plug :accepts, ["json"]
end
scope "/api", MyApp do
pipe_through :api # Use the default browser stack
scope "/v1", V1, as: :v1 do
resources "/users", UserController, except: [:new, :edit, :index]
end
end
scope "/", MyApp do
pipe_through :browser # Use the default browser stack
get "/confirm/:token", UserController, :confirm, as: :user_confirm
end
end
my web/controllers/v1/user_controller.ex
defmodule MyApp.V1.UserController do
use MyApp.Web, :controller
def create(conn, %{"user" => user_params}) do
...
conn
|> put_status(:created)
|> put_resp_header("location", v1_user_path(conn, :show, user))
|> render("sign_up.json", user: Map.put(user, :session, result[:session]))
...
end
and my web/controllers/user_controller.rb
defmodule MyApp.UserController do
use MyApp.Web, :controller
alias MyApp.User
def confirm(conn, %{"token" => token}) do
...
render(conn, "confirmed.html")
...
end
end
my web/views/v1/user_view.ex
defmodule MyApp.V1.UserView do
use MyApp.Web, :view
...
end
and my web/views/user_view.ex
defmodule MyApp.UserView do
use MyApp.Web, :view
end
Everything works fine until I added a route and a controller for html.
Now, when I make a request for api json, I get an error
Request: POST /api/v1/users
** (exit) an exception was raised:
** (UndefinedFunctionError) function MyApp.V1.UserView.render/2 is undefined (module MyApp.V1.UserView is not available)
But if I delete web/vews/user_view.ex, then this query works without errors.
How can you correct this error?

These types of errors can usually be resolved by running mix clean. You may also see this type of error during Live code reload in dev. It case, try restarting the Phoenix.Server, and if that does not help, run mix clean

Related

How to check the arguments of the auto generated Phoenix paths

No helper clause for Api.Router.Helpers.v1_user_organization_path
defined for action :show with arity 3. Please check that the function,
arity and action are correct. The following v1_user_organization_path
actions are defined under your router:
* :create
* :index
* :show
* :update
router.ex
defmodule Api.Router do
use Api.Web, :router
pipeline :api do
plug :accepts, ["json"]
end
scope "/", Api do
pipe_through :api
end
scope "/v1", Api.V1, as: :v1 do
pipe_through :api
resources "/users", UserController, only: [:create, :show, :update] do
resources "/organizations", OrganizationController, only: [:create, :update, :index, :show]
end
end
end
and when I do mix phoenix.routes I see the following v1_user_organization_path getting generated. The problem is I don't know how to use it and I don't know what I should pass into it. Is there a way I can check what this generated method accepts?
The error I get is occuring here
organization_controller.ex
def create(conn, %{"user_id" => user_id, "organization" => organization_params}) do
changeset = Organization.changeset(%Organization{}, organization_params)
case Repo.insert(changeset) do
{:ok, organization} ->
conn
|> put_status(:created)
|> put_resp_header("location", v1_user_organization_path(conn, :show, organization))
|> render("show.json", organization: organization)
{:error, changeset} ->
conn
|> put_status(:unprocessable_entity)
|> render(Api.ChangesetView, "error.json", changeset: changeset)
end
end
At put_resp_header("location", v1_user_organization_path(conn, :show, organization))
You can look at the auto generated documentation for the router helper functions using h in iex -S mix and reading the output of mix phoenix.routes for some help. For example, for the following routes:
resources "/posts", PostController do
resources "/comments", CommentController
end
I get:
iex(1)> h MyApp.Router.Helpers.post_comment_path
def post_comment_path(conn_or_endpoint, action, post_id)
def post_comment_path(conn_or_endpoint, action, post_id, params)
def post_comment_path(conn_or_endpoint, action, post_id, id, params)
$ mix phoenix.routes
post_comment_path GET /posts/:post_id/comments MyApp.CommentController :index
post_comment_path GET /posts/:post_id/comments/:id/edit MyApp.CommentController :edit
post_comment_path GET /posts/:post_id/comments/new MyApp.CommentController :new
post_comment_path GET /posts/:post_id/comments/:id MyApp.CommentController :show
post_comment_path POST /posts/:post_id/comments MyApp.CommentController :create
post_comment_path PATCH /posts/:post_id/comments/:id MyApp.CommentController :update
PUT /posts/:post_id/comments/:id MyApp.CommentController :update
post_comment_path DELETE /posts/:post_id/comments/:id MyApp.CommentController :delete
It's not clear from just the function signature which action accepts how many arguments, but if you read the output of mix phoenix.routes, you can see that :show (last column) requires a post_id and an id.
The output of h is also not completely accurate because it doesn't tell you that the arity 4 version also accepts (conn_or_endpoint, action, post_id, id) and not just (conn_or_endpoint, action, post_id, params).
I don't think there's any better auto generated documentation for the generated route functions right now in Phoenix. I usually just look at the output of mix phoenix.routes and pass in conn_or_endpoint followed by the action followed by every :var in the route, optionally followed by a params map.

I18n segments router Phoenix

I have an Elixir / Phoenix app which reacts differently depending on the domain (aka tenant).
A tenant has a specific locale such as "fr_FR", "en_US" and so on.
I want to translate the URIs of the router depending the current locale:
# EN
get "/classifieds/new", ClassifiedController, :new
# FR
get "/annonces/ajout", ClassifiedController, :new
So far I thought it would be possible to do something like that (pseudo code):
if locale() == :fr do
scope "/", Awesome.App, as: :app do
pipe_through :browser # Use the default browser stack
get "/annonces/ajout", ClassifiedController, :new
end
else
scope "/", Awesome.App, as: :app do
pipe_through :browser # Use the default browser stack
get "/classifieds/new", ClassifiedController, :new
end
end
It doesn't work since the router is compiled during the boot of the server, so you have no context of the current connexion (locale, domain, host and so on).
So far my solution (which works) was to create two scopes with two aliases:
scope "/", Awesome.App, as: :fr_app do
pipe_through :browser # Use the default browser stack
get "/annonces/ajout", ClassifiedController, :new
end
scope "/", Awesome.App, as: :app do
pipe_through :browser # Use the default browser stack
get "/classifieds/new", ClassifiedController, :new
end
and a helper associated:
localized_path(conn, path, action)
which takes a path (:app_classified_new_path) and prefix with fr (:fr_app_classified_new_path) if the current locale is "fr" (for example). If the path doesn't exist for the current locale, I fallback to the default locale "en").
It's working great but I see some pain points:
Every use of "something_foo_path()" helpers must be replaced by this new "localized_path()"
The app will accept each localized segments (fr, en, it, whatever) and it's not really what I want (I want to have only the fr segments to work on the tenant "fr"
I should be able to get the current locale/conn info directly in the router (feature/bugfix?) and avoid a hack like that.
Take a look at this article Practical i18n with Phoenix and Elixir. I believe it should give you exactly what you need.
Alternatively, you could play around with some macros to create blocks of translated routes. The code below does not handle all your requirements since you still have to translate the paths internally. However, it may give you some ideas.
defmodule LocalRoutes.Web.Router do
use LocalRoutes.Web, :router
#locales ~w(en fr)
import LocalRoutes.Web.Gettext
use LocalRoutes.LocalizedRouter
def locale(conn, locale) do
Plug.Conn.assign conn, :locale, locale
end
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :api do
plug :accepts, ["json"]
end
scope "/", LocalRoutes.Web do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
end
for locale <- #locales do
Gettext.put_locale(LocalRoutes.Web.Gettext, locale)
pipeline String.to_atom(locale) do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
plug :locale, locale
end
scope "/", LocalRoutes.Web do
pipe_through String.to_atom(locale)
get "/#{~g"classifieds"}", ClassifiedController, :index
get "/#{~g"classifieds"}/#{~g"new"}", ClassifiedController, :new
get "/#{~g"classifieds"}/:id", ClassifiedController, :show
get "/#{~g"classifieds"}/:id/#{~g"edit"}", ClassifiedController, :edit
post "/#{~g"classifieds"}", ClassifiedController, :create
patch "/#{~g"classifieds"}/:id", ClassifiedController, :update
put "/#{~g"classifieds"}/:id", ClassifiedController, :update
delete "/#{~g"classifieds"}/:id", ClassifiedController, :delete
end
end
end
defmodule LocalRoutes.LocalizedRouter do
defmacro __using__(opts) do
quote do
import unquote(__MODULE__)
end
end
defmacro sigil_g(string, _) do
quote do
dgettext "routes", unquote(string)
end
end
end

routing resources with a wild card

I have this routing
scope "/api", MosaicApi do
pipe_through :api
# resources "/cards", CardController, except: [:new, :edit]
resources "/estimates/:product", EstimateController, except: [:new, :edit]
Initially I used the generated CardController and things worked (at least POST/create did), but now I want to generalise as Card is a product type and I have a variety of other products that need to expose the exact same CRUD operations. So I am trying to morph Card* to Estimate*
In EstimateController I now have this
defmodule Api.CardController do
use Api.Web, :controller
alias Api.Card
def create(conn, %{"product" => product}) do
conn
|> render("result.json", product: product)
end
...
What I want to do is pattern match on product to bring into scope the relevant Struct (Card, ...), but I've got stuck as the code above yields this error
undefined function Api.EstimateController.init/1 (module Api.EstimateController is not available)
Api.EstimateController.init(:create)
I'm confused as init is not mentioned in http://www.phoenixframework.org/docs/controllers at all
Other indications that things are mostly good
mix phoenix.routes
page_path GET / Api.PageController :index
estimate_path GET /api/estimates/:product Api.EstimateController :index
estimate_path GET /api/estimates/:product/:id Api.EstimateController :show
estimate_path POST /api/estimates/:product Api.EstimateController :create
The function init/1 is defined in https://github.com/phoenixframework/phoenix/blob/v1.1.4/lib/phoenix/controller/pipeline.ex#L98
def init(action) when is_atom(action) do
action
end
It looks like you are not calling use Phoenix.Controller in your EstimatesController.
Usually this is done with:
use Api.Web, :controller

Guardian and Sessions in Phoenix app

I'm using Guardian on a phoenix app but I can't use the put_session and get_session methods in controllers with Guardian
defmodule Bonsai.LedgerController do
use Bonsai.Web, :controller
alias Bonsai.{Repo, Ledger}
plug Guardian.Plug.EnsureAuthenticated, handler: Bonsai.SessionController
plug :scrub_params, "ledger" when action in [:create]
def index(conn, _params) do
put_session(conn, :sess_val, "A value") # Generates error
ledgers = Repo.all(Ledger)
render(conn, "index.json", ledgers: ledgers)
end
end
There are two issues here.
First of all, you need to fetch the session in your pipeline. This is commonly done in your router:
pipeline :browser do
#...
plug :fetch_session
#...
end
The second issue is that you are calling put_session however the result is not being used. Try this:
def index(conn, _params) do
conn = put_session(conn, :sess_val, "A value")
ledgers = Repo.all(Ledger)
render(conn, "index.json", ledgers: ledgers)
end

What's the equivalent of rake routes in Phoenix?

Ruby on Rails comes with the terminal command rake routes. Is there an equivalent in Phoenix?
The command is $ mix phoenix.routes
As #Martimatix pointed out $ mix phx.routes print all routes for the default router, here's the documentation
This is an example of routes generated for this router.ex file:
defmodule MessageApi.Router do
use MessageApi.Web, :router
pipeline :api do
plug :accepts, ["json"]
end
scope "/api", MessageApi do
pipe_through :api
resources "/messages", MessageController, except: [:new, :edit]
resources "/message_details", MessageDetailController, except: [:new, :edit]
end
end
then routes generated will be:
message_path GET /api/messages MessageApi.MessageController :index
message_path GET /api/messages/:id MessageApi.MessageController :show
message_path POST /api/messages MessageApi.MessageController :create
message_path PATCH /api/messages/:id MessageApi.MessageController :update
PUT /api/messages/:id MessageApi.MessageController :update
message_path DELETE /api/messages/:id MessageApi.MessageController :delete
message_detail_path GET /api/message_details MessageApi.MessageDetailController :index
message_detail_path GET /api/message_details/:id MessageApi.MessageDetailController :show
message_detail_path POST /api/message_details MessageApi.MessageDetailController :create
message_detail_path PATCH /api/message_details/:id MessageApi.MessageDetailController :update
PUT /api/message_details/:id MessageApi.MessageDetailController :update
message_detail_path DELETE /api/message_details/:id MessageApi.MessageDetailController :delete

Resources