Please check this code:
defmodule InfinitePollTask do
def poll(opts \\ [])
# function body code here
poll(new_opts)
end
end
I want to write a unit test for the function body code, assuming the function body perform some important computation using opts and produce a new_opts for the next iteration.
I'd just pull the computation out into a separate function that returns new_opts, and test that:
defmodule InfinitePollTask do
def poll(opts \\ [])
poll(do_poll(opts))
end
def do_poll(opts)
# important computation
end
end
defmodule InfinitePollTaskTest do
use ExUnit.Case
test "some case" do
assert InfinitePollTask.do_poll(some_opts) == some_result_opts
end
end
Related
There is a program of three modules. The Print module receives a number from the keyboard, passes it to another module, receives the response, and displays it on the screen. The Proc1 and Proc2 modules receive a number, perform calculations, and send the result back.
defmodule Launch do
#moduledoc """
Documentation for `Launch`.
"""
#doc """
"""
def start() do
children = [
%{
id: Print,
start: {Print, :print, []}
},
%{
id: Proc1,
start: {Proc1, :proc1, []}
},
%{
id: Proc2,
start: {Proc2, :proc2, []}
}
]
Supervisor.start_link(children, strategy: :one_for_one)
end
end
defmodule Print do
def print() do
num =
IO.gets("Input number: ")
|> String.trim()
|> String.to_integer()
if num >= 0 do
send(Proc1, {self(), num})
else
send(Proc2, {self(), num})
end
receive do
num -> IO.puts(num)
after
500 ->
print()
end
print()
end
end
defmodule Proc1 do
def proc1() do
receive do
{pid, num} ->
send(pid, 100/num)
proc1()
_e ->
IO.puts("Error")
end
end
end
defmodule Proc2 do
def proc2() do
receive do
{pid, num} ->
send(pid, 1000/num)
proc2()
_e ->
IO.puts("Error")
end
end
end
I am trying to run all processes under the supervision of a single Supervisor. But there is a problem-only the first "child" is started, the other "children" are not started. In the example above, the Print process will start, but Proc1 and Proc2 will not start. How do I run all processes under one Supervisor? Important note: the Print process must get the addresses of the Proc1 and Proc2 processes for communication.
There are many issues with the code you’ve posted.
Registered processes
To be able to use process name as Process.dest() in a call to Kernel.send/2, one should start the named process.
Supervisor.start_link/2
Supervisor.start_link/2 expects a list of tuples, with modules and functions that immediately return, having the process started as a side effect. These functions are called, and there would not be any magic: if this is an infinitely recursive function, the execution flow would be deadlocked inside, waiting for the message in receive/1.
Supervisor performs some magic by automatically monitoring and restarting children for you, but it does nothing to spawn the separate processes. GenServer encapsulates this functionality and provides a handy way to not bother about spawning processes.
Solution
What you might do, is to spawn all three processes, manually monitor them, and react on {:DOWN, ref, :process, pid, reason} message respawning the died process. This is exactly what Supervisor effectively does under the hood for children.
Launch
defmodule Launch do
def start() do
proc1 = spawn(&Proc1.proc1/0)
proc2 = spawn(&Proc2.proc2/0)
print = spawn(fn -> Print.print(proc1, proc2) end)
Process.monitor(proc1)
Process.monitor(proc2)
Process.monitor(print)
receive do
msg -> IO.inspect(msg)
end
end
end
Print
defmodule Print do
def print(pid1, pid2) do
num =
IO.gets("Input number: ")
|> String.trim()
|> String.to_integer()
if num >= 0 do
send(pid1, {self(), num})
else
send(pid2, {self(), num})
end
receive do
num -> IO.puts(num)
end
print(pid1, pid2)
end
end
The other two modules are fine.
Here is how it will look like in iex
iex|1 ▶ c "/tmp/test.ex"
#⇒ [Launch, Print, Proc1, Proc2]
iex|2 ▶ Launch.start
Input number: 10
10.0
Input number: 1000
0.1
Input number: a
#⇒ {:DOWN, #Reference<0.3632020665.3980394506.95298>,
# :process, #PID<0.137.0>,
# {:badarg,
# [
# {:erlang, :binary_to_integer, ["a"], []},
# {Print, :print, 2, [file: '/tmp/test.ex', line: 22]}
# ]}}
Now instead of printing this out, respawn the failed process, and you will get a bare implementation of the supervised intercommunicating processes. For all_for_one strategy that could be achieved with:
receive do
{:DOWN, _, _, _, _} ->
Process.exit(print, :normal)
Process.exit(proc1, :normal)
Process.exit(proc2, :normal)
start()
end
I'm trying to use Plug.Test to test error handling implemented with Plug.ErrorHandler -- with assert conn.status == 406 and alike.
I have the defp handle_errors (containing a single send_resp statement) and it seems to be called, however, my tests fail with the same exception still (as if handle_errors has no effect).
A reference to a sample advanced Plug (not Phoenix) app will also be appreciated.
Try something like this (not tested):
defmodule NotAcceptableError do
defexception plug_status: 406, message: "not_acceptable"
end
defmodule Router do
use Plug.Router
use Plug.ErrorHandler
plug :match
plug :dispatch
get "/hello" do
raise NotAcceptableError
send_resp(conn, 200, "world")
end
def handle_errors(conn, %{kind: _kind, reason: reason, stack: _stack}) do
send_resp(conn, conn.status, reason.message)
end
end
test "error" do
conn = conn(:get, "/hello")
assert_raise Plug.Conn.WrapperError, "** (NotAcceptableError not_acceptable)", fn ->
Router.call(conn, [])
end
assert_received {:plug_conn, :sent}
assert {406, _headers, "not_acceptable"} = sent_resp(conn)
end
Use assert_error_sent/2 to assert that you raised an error and it was wrapped and sent with a particular status. Match against its {status, headers, body} return value to assert the rest of the HTTP response met your expectations.
response = assert_error_sent 404, fn ->
get(build_conn(), "/users/not-found")
end
assert {404, [_h | _t], "Page not found"} = response
I'm using https://github.com/kenn/active_flag and https://github.com/chanzuckerberg/sorbet-rails
This is what its rbi looks like:
module ActiveFlag
extend ActiveSupport::Concern
end
class ActiveFlag::Definition
def column; end
def human(key, options = nil); end
def humans; end
def initialize(column, keys, klass); end
def keys; end
def maps; end
def pairs; end
def set_all!(key); end
def to_array(integer); end
def to_i(arg); end
def to_value(instance, integer); end
def unset_all!(key); end
end
class ActiveFlag::Railtie < Rails::Railtie
end
class ActiveFlag::Value < Set
def method_missing(symbol, *args, &block); end
def raw; end
def set!(key, options = nil); end
def set(key); end
def set?(key); end
def to_human; end
def to_s; end
def unset!(key, options = nil); end
def unset(key); end
def unset?(key); end
def with(instance, definition); end
end
module ActiveFlag::ClassMethods
def flag(column, keys); end
end
class ActiveRecord::Base
extend ActiveFlag::ClassMethods
end
The last bit (extending AR::Base) I added, the rest srb rbi gems generated automatically.
To actually use Active Flag, I then do this in my model:
flag :visible_to, [:employee, :manager, :admin]
visible_to is an integer column. Sorbet Rails has already typed it as such:
sig { returns(Integer) }
def visible_to; end
sig { params(value: Integer).void }
def visible_to=(value); end
sig { returns(T::Boolean) }
def visible_to?; end
I could change this definition, but it's an autogenerated file and I assume the changes will get lost. So the next thing I tried was adding a sig directly above where the method gets used:
sig { returns(ActiveFlag::Value) }
def visible_to; end
flag :visible_to, [:employee, :manager, :admin]
The problem here is that Sorbet fails because the method I've "defined" doesn't return anything. Which I know is fine, because it'll get overriden when flag is called (it uses define_method internally), but I don't know how to communicate this to Sorbet.
Returning value that does not conform to method result type https://srb.help/7005
54 | def visible_to; end
^^^^^^^^^^^^^^^^^^^
Expected ActiveFlag::Value
Method visible_to has return type ActiveFlag::Value
54 | def visible_to; end
^^^^^^^^^^^^^^
Got NilClass originating from:
54 | def visible_to; e
So my question is. What's the best way to tell Sorbet that this method will return an ActiveFlag::Value once it gets defined, ideally without making a manual change to an autogenerated file?
btw. I tried to see how types for enum in Rails are handled... it doesn't look like that's been done on sorbet-typed yet. Possibly for the same reason.
When you want to override a generated rbi file you can create a second rbi file for the same class in your workspace and move the methods in there. Sorbet will handle merging multiple definitions files for you. We keep these files in a separate sorbet/custom directory next to the generated files; others keep the rbi files next to the rb files in their application directory.
As to enums, Sorbet does not natively support enum literal types so that's likely why.
You could implement a custom plugin for sorbet-rails to generate methods added by active_flag and remove the sigs generated wrongly. Here is the documentation for it:
https://github.com/chanzuckerberg/sorbet-rails/blob/master/README.md#extending-model-generation-task-with-custom-plugins
I'm trying to make a login in elixir, but when i put this code:
def changeset(model, params \\ :empty) do
model
|> cast(params, ~w(email), [])
|> validate_format(:email, ~r/#/)
end
I keep getting this error:
== Compilation error on file web/models/user.ex ==
** (CompileError) web/models/user.ex:25: definitions with multiple clauses and default values require a header. Instead of:
def foo(:first_clause, b \\ :default) do ... end
def foo(:second_clause, b) do ... end
one should write:
def foo(a, b \\ :default)
def foo(:first_clause, b) do ... end
def foo(:second_clause, b) do ... end
def changeset/2 has multiple clauses and defines defaults in one or more clauses
web/models/user.ex:25: (module)
(stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
(elixir) lib/kernel/parallel_compiler.ex:117: anonymous fn/4 in Kernel.ParallelCompiler.spawn_compilers/1
I understand that i have to put a header, but i don´t know exactly how to fix it, anyone could explain me please?
Your code likely looks something like this:
def changeset(model, params \\ :empty) do
# ...
end
def changeset(model, %{"some" => value}) do
# ...
end
Elixir complains about this, because having multiple clauses with default values might result in ambiguities. To prevent such situations on a syntactic level, Elixir enforces using a separate function header for default values.
As the error message suggests, you need to add a separate function header defining the default value, then write your other clauses without default values:
# This is a function header, whose only purpose is to set
# default values that apply to all subsequent clauses
def changeset(model, params \\ :empty)
def changeset(model, params) do
# ...
end
def changeset(model, %{"some" => value}) do
# ...
end
I'm trying to create a REST API using Phoenix with no Ecto or brunch.
What's the syntax for creating a post function in the router/controller with parameters, but not using Ecto?
For example in Ruby/Sinatra it would look something like this:
post "/v1/ipf" do
#weight1 = params[:weight1]
#weight2 = params[:weight2]
#weight3 = params[:weight3]
#weight4 = params[:weight4]
#goal1_percent = params[:goal1_percent]
#goal2_percent = params[:goal2_percent]
# etc...
end
Update
Based on Nick's answer, here's what I ended up with:
rest_api/web/router.ex:
defmodule RestApi.Router do
use RestApi.Web, :router
pipeline :api do
plug :accepts, ["json"]
end
scope "/", RestApi do
pipe_through :api
scope "/v1", V1, as: :v1 do
get "/ipf", IPFController, :ipf
end
end
end
rest_api/web/controllers/v1/ipf_controller.ex:
defmodule RestApi.V1.IPFController do
use RestApi.Web, :controller
import IPF
def ipf(conn, params) do
{weight1, _} = Integer.parse(params["weight1"])
{weight2, _} = Integer.parse(params["weight2"])
{weight3, _} = Integer.parse(params["weight3"])
{weight4, _} = Integer.parse(params["weight4"])
{goal1_percent, _} = Float.parse(params["goal1_percent"])
{goal2_percent, _} = Float.parse(params["goal2_percent"])
results = IPF.ipf(weight1, weight2, weight3, weight4, goal1_percent, goal2_percent)
render conn, results: results
end
end
rest_api/web/views/v1/ipf_view.ex:
defmodule RestApi.V1.IPFView do
use RestApi.Web, :view
def render("ipf.json", %{results: results}) do
results
end
end
Ecto and Brunch don't really have anything to do w/Phoenix handling a POST. Brunch is a web asset build tool, and Ecto is a database layer.
To add this new route, you just need to add an entry in the router for the new route:
post "/v1/spf", SPFController, :spf
And then create the controller:
defmodule MyModule.SPFController do
def spf(conn, params) do
# do whatever
end
end
That's it.