The following code is an invalid changeset which errors, however it took me a long time to find the cause because the error message was not originally being matched and logged.
I added a case statement to the end of the pipe, is this the best way to pickup errors in pipes?
User.changeset(%User{}, %{username: "username_is_test", password: "test", password_confirmation: "test", email: "test#test.com"})
|> Repo.insert
|> case do
{:ok, result} -> IO.puts("result")
{:error, error} -> IO.inspect error
end
Pipelines and error tuples don't work very well together. You can handle an error at the end of a pipeline with a case as you have, but it only works at the last stage.
For operations returning error tuples, I prefer to use with/else syntax:
with changeset <- User.changeset(%User{}, %{username: "username_is_test", password: "test", password_confirmation: "test", email: "test#test.com"})
{:ok, result} <- Repo.insert(changeset) do
IO.puts("result")
else
{:error, error} -> IO.inspect error
end
You can add as many failable operations as required in the with block, and handle all the error cases with pattern matching in the else block.
https://hexdocs.pm/elixir/Kernel.SpecialForms.html#with/1
You have a couple options. If you don't want to explicitly handle the error conditions, you should use Repo.insert! instead. At least this will raise an exception if the changeset is not valid.
Otherwise, you should be using a case handle handling the {:error, changeset} by checking the changeset.action in your template.
In more complicated pipelines that can error part way through, I've started using the with special form.
with result when not is_nil(result) <- fun1,
{:ok, result} <- fun2(result),
{:ok, result} <- fun3(result) do
success_handling(result)
else
nil -> # handle first failure
{:error, error} -> # handle other errors
_ -> # catch all failure
end
Related
I'm trying to call AMQP.Application:get_channel on start per https://github.com/pma/amqp but am getting 18:58:38.481 [error] CRASH REPORT Process <0.980.0> with 0 neighbours crashed with reason: call to undefined function 'Elixir.AMQP.Application':get_channel(mychan). Any help on this would be much appreciated. The function that's making the call is as follows:
def init(opts) do
{:ok, chan} = AMQP.Application.get_channel(:mychan)
setup_queue(opts.id, chan)
queue_to_consume = #receive_queue <> opts.id
{:ok, _consumer_tag} = Basic.consume(chan, queue_to_consume, nil, no_ack: true)
{:ok, %State{chan: chan, id: opts.id}}
end
I'm currently working on my first big elixir project and wanted to properly utilize testing this time.
However, if I add my Modules to the "normal" supervisor, i cannot start them again with start_supervised! and all tests fail with Reason: already started: #PID<0.144.0>
Here is my code:
(application.ex)
defmodule Websocks.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
#moduledoc false
use Application
def start(_type, _args) do
children = [
{Websocks.PoolSupervisor, []},
{Websocks.PoolHandler, %{}}
# {Websocks.Worker, arg}
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Websocks.Supervisor]
Supervisor.start_link(children, opts)
end
end
Some of my tests:
defmodule PoolHandlerTest do
use ExUnit.Case, async: true
alias Websocks.PoolHandler
doctest PoolHandler
setup do
start_supervised!({PoolHandler, %{}})
%{}
end
test "adding two pools and checking if they are there" do
assert PoolHandler.add(:first) == :ok
assert PoolHandler.add(:second) == :ok
assert PoolHandler.get_pools() == {:ok,%{:first => nil, :second => nil}}
end
and the pool handler:
defmodule Websocks.PoolHandler do
use GenServer
# Client
def start_link(default) when is_map(default) do
GenServer.start_link(__MODULE__, default, name: __MODULE__)
end
# Server (callbacks)
#impl true
def init(arg) do
{:ok, arg}
end
end
(I cut out the stuff i think is not necessary, but the complete code is on github here: github)
Thanks in advance for any help i get!
As #Everett mentioned in the comment - your application will already be started for you when you mix test, so there is no need to start your GenServers again. It seems like you're interacting with the global instance in your test, so if that's what you want, then it should just work.
However, if you'd like to start a separate instance just for your test, you need to start an unnamed one. For example, you could add an optional pid argument to your wrapper functions:
defmodule Websocks.PoolHandler do
# ...
def add(server \\ __MODULE__, value) do
GenServer.call(server, {:add, value})
end
# ...
end
Then, instead of using using start_supervised! like you do, you can start an unnamed instance in your setup and use it in your tests like so:
setup do
{:ok, pid} = GenServer.start_link(PoolHandler, %{})
{:ok, %{handler: pid}}
end
test "adding two pools and checking if they are there", %{handler: handler} do
PoolHandler.add(handler, :first)
# ...
end
Looking for a help with testing terminate/2 callback in my Channel.
Test and setup looks like this:
setup do
:ok = Ecto.Adapters.SQL.Sandbox.checkout(MyApp.Repo)
Ecto.Adapters.SQL.Sandbox.mode(MyApp.Repo, {:shared, self()})
{:ok, socket} = connect(UserSocket, %{token: "some_token"})
{:ok, %{}, socket} = subscribe_and_join(socket, "some_channel", %{})
%{socket: socket}
end
test "terminate/2", %{socket: socket} do
# for avoiding "** (EXIT from #PID<...>) {:shutdown, :closed}"
Process.unlink(socket.channel_pid)
assert close(socket) == :ok
# some additional asserts go here
end
In terminate/2 method I just call a helper module, let's name it TerminationHandler.
def terminate(_reason, _socket) do
TerminationHandler.call()
end
And call/0 method in TerminationHandler contains a DB query. It can look like this i.e
def call() do
users = User |> where([u], u.type == "super") |> Repo.all # line where error appears
# some extra logic goes here
end
This is the error that I get periodically (maybe once in 10 runs)
14:31:29.312 [error] GenServer #PID<0.1041.0> terminating
** (stop) exited in: GenServer.call(#PID<0.1040.0>, {:checkout, #Reference<0.3713952378.42205187.247763>, true, 60000}, 5000)
** (EXIT) shutdown: "owner #PID<0.1039.0> exited with: shutdown"
(db_connection) lib/db_connection/ownership/proxy.ex:32: DBConnection.Ownership.Proxy.checkout/2
(db_connection) lib/db_connection.ex:928: DBConnection.checkout/2
(db_connection) lib/db_connection.ex:750: DBConnection.run/3
(db_connection) lib/db_connection.ex:644: DBConnection.execute/4
(ecto) lib/ecto/adapters/postgres/connection.ex:98: Ecto.Adapters.Postgres.Connection.execute/4
(ecto) lib/ecto/adapters/sql.ex:256: Ecto.Adapters.SQL.sql_call/6
(ecto) lib/ecto/adapters/sql.ex:436: Ecto.Adapters.SQL.execute_or_reset/7
(ecto) lib/ecto/repo/queryable.ex:133: Ecto.Repo.Queryable.execute/5
(ecto) lib/ecto/repo/queryable.ex:37: Ecto.Repo.Queryable.all/4
(my_app) lib/my_app/helpers/termination_handler.ex:4: MyApp.Helpers.TerminationHandler.call/0
(stdlib) gen_server.erl:673: :gen_server.try_terminate/3
(stdlib) gen_server.erl:858: :gen_server.terminate/10
(stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Last message: {:join, Phoenix.Channel.Server}Last message: {:join, Phoenix.Channel.Server}
Would appreciate any responses regarding reasons of this error and possible ways to avoid it.
As stated in the documentation for GenServer.terminate/2:
[...] the supervisor will send the exit signal :shutdown and the GenServer will have the duration of the timeout to terminate. If after duration of this timeout the process is still alive, it will be killed immediately.
That is seemingly your case. DBConnection.checkout/2 seems to be waiting for the available connection to appear and this is lasted beyond the timeout. Hence the owner experiences a brutal kill.
There could be two possible solutions:
increase a timeout of shutdown (I would avoid that)
increase an amount of allowed simultaneous database connections.
The latter is likely needed in any case, since your pool seems to be full. That way the connection would be checked out immediately, and it should return in the timeout interval successfully.
This might help.
defmacro leave_channel(socket) do
quote do
Process.unlink(unquote(socket).channel_pid)
mref = Process.monitor(unquote(socket).channel_pid)
ref = leave(unquote(socket))
assert_reply ref, :ok
assert_receive {:DOWN, ^mref, :process, _pid, _reason}
end
end
defmacro close_socket(socket) do
quote do
Process.unlink(unquote(socket).channel_pid)
mref = Process.monitor(unquote(socket).channel_pid)
close(unquote(socket))
assert_receive {:DOWN, ^mref, :process, _pid, _reason}
end
end
I'd like to find out the current user from the server side. The user_controller module shows how current_user can be returned in response to the client request. But I'm having trouble finding out how to do it from another module on the server.
Below is what I tried but failed with the following error. Basically I copied over the Guardian.Plug code block from the user_controller.ex.
What's the correct way to do it?
(CompileError) web/GraphQLSession.ex:9: undefined function put_status/2
(stdlib) lists.erl:1337: :lists.foreach/2
(stdlib) erl_eval.erl:669: :erl_eval.do_apply/6
.
defmodule App.GraphQLSession do
use Guardian.Hooks
def root_eval(_conn) do
current_user =
case Guardian.Plug.current_resource(_conn) do
nil ->
_conn
|> put_status(:not_found)
|> render(App.V1.SessionView, "error.json", error: "User not found")
user ->
_conn
|> put_status(:ok)
|> render("show.json", user: user)
end
%{author: current_user}
end
end
<<<<< router.ex >>>>
defmodule App.Router do
use App.Web, :router
...
pipeline :api do
plug :accepts, ["json"]
plug Guardian.Plug.VerifyHeader
plug Guardian.Plug.LoadResource
end
scope "/graphql" do
pipe_through :api
forward "/", GraphQL.Plug, [schema: {App.PublicSchema, :schema}, root_value: {App.GraphQLSession, :root_eval} ]
end
<<<< user_controller.ex >> .... This shows how client can retrieve current_user via controller.
defmodule App.V1.UserController do
use App.Web, :controller
alias App.User
plug Guardian.Plug.EnsureAuthenticated, on_failure: { App.V1.SessionController, :unauthenticated_api }
plug :scrub_params, "user" when action in [:create, :update]
def current_user(conn, %{"jwt" => jwt}) do
case Guardian.Plug.current_resource(conn) do
nil ->
conn
|> put_status(:not_found)
|> render(App.V1.SessionView, "error.json", error: "User not found")
user ->
conn
|> put_status(:ok)
|> render("show.json", user: user)
end
UPDATE: After importing Plug.Conn and changing _conn to conn, per David Sulc's advice, the next error I get is as follows:
[error] #PID<0.1026.0> running App.Endpoint terminated
Server: localhost:4000 (http)
Request: POST /graphql
** (exit) an exception was raised:
** (Protocol.UndefinedError) protocol Enumerable not implemented for %Plug.Conn{adapter: {Plug.Adapters.Cowboy.Conn, :...}, assigns: %{error: "User not found", guardian_default_resource: nil, layout:
false}, before_send: [#Function<1.34093945/1 in Plug.Logger.call/2>, #Function<0.30221299/1 in Phoenix.LiveReloader.before_send_inject_reloader/1>], body_params: %{"query" => "mutation CreateMutation(
$input_0:CreateInput!){createQ(input:$input_0){clientMutationId,...F3}} fragment F0 on ...}
}}, cookies: %Plug.Conn.Unfetched{aspect: :cookies}, halted: false, host: "localhost", method: "POST", owner: #PID<0.1026.0>, params: %{"query" => "mutation ....}}}, path_info: [], peer: {{1
27, 0, 0, 1}, 50944}, port: 4000, private: %{App.Router => {[], %{GraphQL.Plug => []}}, :phoenix_endpoint => App.Endpoint, :phoenix_format => "json", :phoenix_pipelines => [:api], :phoenix_route => #Funct
ion<0.58758354/1 in App.Router.match_route/4>, :phoenix_router => App.Router, :phoenix_template => "error.json", :phoenix_view => App.V1.SessionView, :plug_session_fetch => #Function<0.29336444/1 in Plug.
Session.fetch_session/1>}, query_params: %{}, query_string: "", remote_ip: {127, 0, 0, 1}, req_cookies: %Plug.Conn.Unfetched{aspect: :cookies}, req_headers: [{"host", "localhost:4000"}, {"connection", "ke
ep-alive"}, {"content-length", "632"}, {"accept", "*/*"}, {"origin", "http://localhost:4000"}, {"user-agent", "}, {"content-type", "application/json"}, {"referer", "http://localhost:4000/"}, {"accept-encoding", "gzip, deflate"}, {"accept-language", "en-US,en;q=0.8,ko;q=0.6,zh-CN;q=0.4"
}, {"alexatoolbar-alx_ns_ph", "AlexaToolbar/alx-4.0"}], request_path: "/graphql", resp_body: nil, resp_cookies: %{}, resp_headers: [{"content-type", "application/json; charset=utf-8"}, {"cache-control", "
max-age=0, private, must-revalidate"}, {"x-request-id", "ikh03v5kqightov3npgl8bv0do5rv77d"}, {"access-control-allow-origin", "*"}, {"access-control-expose-headers", ""}, {"access-control-allow-credentials
", "true"}], scheme: :http, script_name: ["graphql"], secret_key_base: "x4K=====-00-----lksMUX", state: :sent, status: 404}
(elixir) lib/enum.ex:1: Enumerable.impl_for!/1
(elixir) lib/enum.ex:116: Enumerable.reduce/3
(elixir) lib/enum.ex:1477: Enum.reduce/3
(elixir) lib/enum.ex:1092: Enum.map/2
(rethinkdb) lib/rethinkdb/query/macros.ex:93: RethinkDB.Query.Macros.wrap/1
(rethinkdb) lib/rethinkdb/query/macros.ex:94: anonymous fn/1 in RethinkDB.Query.Macros.wrap/1
(elixir) lib/enum.ex:1092: anonymous fn/3 in Enum.map/2
(stdlib) lists.erl:1262: :lists.foldl/3
I'm learning Elixir myself, but hopefully this will help you along...
Elixir is complaining because the controller imports Plug modules for you (via use App.Web, :controller). This isn't your case in the module you defined, so you need to import it yourself (see line 2).
Also, note that the naming convention of preceding a variable with an underscore if for variables that are to be ignored (and therefore not used). Since your code uses the conn param, it shouldn't be matched as _conn.
defmodule App.GraphQLSession do
import Plug.Conn, only: [put_status: 2]
def root_eval(conn) do
current_user =
case Guardian.Plug.current_resource(conn) do
nil ->
conn
|> put_status(:not_found)
|> render(App.V1.SessionView, "error.json", error: "User not found")
user ->
conn
|> put_status(:ok)
|> render("show.json", user: user)
end
%{author: current_user}
end
end
To answer your updated question there are a few things that you should notice:
Your main error is that you're trying to implement the Enumerable protocol for something that doesn't support it. See the line: ** (Protocol.UndefinedError) protocol Enumerable not implemented. Check any code that is using the Enum.* module. Make sure anything you're passing into the function is enumerable.
Also, Guardian can't find the user you want to retrieve. In the stack trace you can see this: assigns: %{error: "User not found", guardian_default_resource: nil. Try using the api_sign_in function provided by Guardian. You can find more information here. Then you should be able to use Guardian.Plug.current_resource(conn)
I'm brand spanking new to Phoenix and Elixir. So far, it's awesome, but there is a learning curve to be had. Right now, I'm getting this error while following a tut and I can't seem to see what is wrong. (SyntaxError) web/controllers/registration_controller.ex:16: syntax error before: '->'
Registration_controller:
defmodule Restore.RegistrationController do
use Restore.Web, :controller
alias Restore.User
def new(conn, _params) do
changeset = User.changeset(%User{})
render conn, changeset: changeset
end
def create(conn, %{"user" => user_params}) do
changeset = User.changeset(%User{}, user_params)
case Restore.Registration.create(changeset, Restore.Repo) do
{:ok, changeset} ->
# sign in the user
{:error, changeset} ->
# show error message
end
end
end
Error:
Compiling 2 files (.ex)
== Compilation error on file web/controllers/registration_controller.ex ==
** (SyntaxError) web/controllers/registration_controller.ex:16: syntax error before: '->'
(elixir) lib/kernel/parallel_compiler.ex:116: anonymous fn/4 in Kernel.ParallelCompiler.spawn_compilers/1
Sorry if this is a bad question, but for the life of me I can't seem to get it right. Any help would be great thank you!
You need to complete your registration process (add code instead of the comment), there's nothing you're doing with each case, this is an example from an app that uses JWT authentication:
def create(conn, %{"user" => user_params}) do
changeset = User.changeset(%User{}, user_params)
case Repo.insert(changeset) do
{:ok, user} ->
{:ok, jwt, _full_claims} = Guardian.encode_and_sign(user, :token)
conn
|> put_status(:created)
|> render(Restore.SessionView, "show.json", jwt: jwt, user: user)
{:error, changeset} ->
conn
|> put_status(:unprocessable_entity)
|> render(Restore.RegistrationView, "error.json", changeset: changeset)
end