Files
elixir-koans/lib/koans/18_genservers.ex
Jay Hayes b598df498e Manually manage process rather than relying on linking
This prevents the race condition that was causing issues between koans
as each is run in its own process.
2022-02-10 07:57:49 -06:00

162 lines
4.5 KiB
Elixir

defmodule GenServers do
use Koans
@intro "GenServers"
defmodule Laptop do
use GenServer
#####
# External API
def init(args) do
{:ok, args}
end
def start(init_password) do
# The __MODULE__ macro returns the current module name as an atom
GenServer.start(__MODULE__, init_password, name: __MODULE__)
end
def stop do
GenServer.stop(__MODULE__)
end
def unlock(password) do
GenServer.call(__MODULE__, {:unlock, password})
end
def owner_name do
GenServer.call(__MODULE__, :get_owner_name)
end
def manufacturer do
GenServer.call(__MODULE__, :get_manufacturer)
end
def laptop_type do
GenServer.call(__MODULE__, :get_type)
end
def retrieve_password do
GenServer.call(__MODULE__, :get_password)
end
def laptop_specs do
GenServer.call(__MODULE__, :get_specs)
end
def change_password(old_password, new_password) do
GenServer.cast(__MODULE__, {:change_password, old_password, new_password})
end
####
# GenServer implementation
def handle_call(:get_password, _from, current_password) do
{:reply, current_password, current_password}
end
def handle_call(:get_manufacturer, _from, current_state) do
{:reply, "Apple Inc.", current_state}
end
def handle_call(:get_type, _from, current_state) do
{:reply, "MacBook Pro", current_state}
end
def handle_call(:get_owner_name, _from, current_state) do
{:reply, {:ok, "Jack Sparrow"}, current_state}
end
def handle_call(:get_specs, _from, current_state) do
{:reply, {:ok, ["2.9 GHz Intel Core i5"], 8192, :intel_iris_graphics}, current_state}
end
def handle_call(:name_check, _from, current_state) do
{:reply, "Congrats! Your process was successfully named.", current_state}
end
def handle_call({:unlock, password}, _from, current_password) do
case password do
password when password === current_password ->
{:reply, {:ok, "Laptop unlocked!"}, current_password}
_ ->
{:reply, {:error, "Incorrect password!"}, current_password}
end
end
def handle_cast({:change_password, old_password, new_password}, current_password) do
case old_password do
old_password when old_password == current_password ->
{:noreply, new_password}
_ ->
{:noreply, current_password}
end
end
end
koan "Servers that are created and initialized successfully returns a tuple that holds the PID of the server" do
{:ok, pid} = GenServer.start_link(Laptop, "3kr3t!")
assert is_pid(pid) == ___
end
koan "The handle_call callback is synchronous so it will block until a reply is received" do
{:ok, pid} = GenServer.start_link(Laptop, "3kr3t!")
assert GenServer.call(pid, :get_password) == ___
end
koan "A server can support multiple actions by implementing multiple handle_call functions" do
{:ok, pid} = GenServer.start_link(Laptop, "3kr3t!")
assert GenServer.call(pid, :get_manufacturer) == ___
assert GenServer.call(pid, :get_type) == ___
end
koan "A handler can return multiple values and of different types" do
{:ok, pid} = GenServer.start_link(Laptop, "3kr3t!")
{:ok, processor, memory, graphics} = GenServer.call(pid, :get_specs)
assert processor == ___
assert memory == ___
assert graphics == ___
end
koan "The handle_cast callback handles asynchronous messages" do
{:ok, pid} = GenServer.start_link(Laptop, "3kr3t!")
GenServer.cast(pid, {:change_password, "3kr3t!", "73x7!n9"})
assert GenServer.call(pid, :get_password) == ___
end
koan "Handlers can also return error responses" do
{:ok, pid} = GenServer.start_link(Laptop, "3kr3t!")
assert GenServer.call(pid, {:unlock, "81u3pr!n7"}) == ___
end
koan "Referencing processes by their PID gets old pretty quickly, so let's name them" do
{:ok, _} = GenServer.start_link(Laptop, "3kr3t!", name: :macbook)
assert GenServer.call(:macbook, :name_check) == ___
end
koan "Our server works but it's pretty ugly to use; so lets use a cleaner interface" do
Laptop.start("EL!73")
assert Laptop.unlock("EL!73") == ___
end
koan "Let's use the remaining functions in the external API" do
Laptop.start("EL!73")
{_, response} = Laptop.unlock("EL!73")
assert response == ___
Laptop.change_password("EL!73", "Elixir")
{_, response} = Laptop.unlock("EL!73")
assert response == ___
{_, response} = Laptop.owner_name()
assert response == ___
:ok = Laptop.stop()
end
end