Skip to content

Instantly share code, notes, and snippets.

@spiegela
Last active December 27, 2015 13:09
Show Gist options
  • Select an option

  • Save spiegela/7330532 to your computer and use it in GitHub Desktop.

Select an option

Save spiegela/7330532 to your computer and use it in GitHub Desktop.
Dynamo Restful Resource Example
# Run this app from Dynamo root with:
# mix run -r examples/fruit_handler.exs --no-halt
#
# Open a browser at URL http://localhost:3030 see the wonderment of rest.
#
defmodule Fruit do
# Cowboy REST handler resources
#
# * User guide: http://ninenines.eu/docs/en/cowboy/HEAD/guide/rest_handlers
# * Reference: http://ninenines.eu/docs/en/cowboy/HEAD/manual/cowboy_rest
#
require :cowboy_req, as: R # Used for request body parsing.
require :jiffy, as: J
defmodule Collection do
# Display and manage a collection of resources. This includes modifying a
# collection by adding new members via POST
def init(_trans, _req, []) do
{:upgrade, :protocol, :cowboy_rest}
end
# The following callbacks are all optional and have intelligent defaults as
# defined int he links above.
def allowed_methods(req, state) do
{["GET", "OPTIONS", "POST"], req, state}
end
def allow_missing_post do true end
def content_types_provided(req, state) do
# Define content types provided and their associated ProvideResource
# callbacks as atoms
#
{ [
{ "application/json", :get_json },
{ "text/html", :get_html },
{ "text/plain", :get_plain }
], req, state }
end
def content_types_accepted(req, state) do
# Define content types accepted and their associated ProvideResource
# callbacks as atoms
#
{ [
{ "application/json", :post_json}
], req, state }
end
def get_json(req, state) do
# JSON ProvideResource callback
#
{FruitServer.list(:fruit_server) |> J.encode, req, state}
end
def get_html(req, state) do
{FruitServer.list(:fruit_server) |> format_html, req, state}
end
defp format_html(list) do
"<div><h3>Fruit:</h3><ul>#{list_html(list)}</ul></div>"
end
defp list_html(list) do
(lc fruit inlist list, do: "<li>#{fruit}</li>") |> Enum.join
end
def get_plain(req, state) do
# Plain ProvideResource callback
#
{FruitServer.list(:fruit_server) |> format_plain, req, state}
end
defp format_plain(list) do
"Fruit: \n#{list_plain(list)}"
end
defp list_plain(list) do
(lc fruit inlist list, do: " - #{fruit}") |> Enum.join("\n")
end
def post_json(req, state) do
get_fruit_by_body(req) |> FruitServer.add(:fruit_server)
{true, req, state}
end
defp get_fruit_by_body(req) do
{ [ { "fruit", fruit } ] } = R.body(req)
|> elem(1)
|> J.decode
fruit
end
end
defmodule Object do
# Display and manage a single resource.
def init(_trans, _req, []) do
{:upgrade, :protocol, :cowboy_rest}
end
# The following callbacks are all optional and have intelligent defaults as
# defined int he links above.
def allowed_methods(req, state) do
{["GET", "OPTIONS", "PUT", "DELETE"], req, state}
end
def content_types_provided(req, state) do
# Define content types provided and their associated ProvideResource
# callbacks as atoms
{ [
{ "application/json", :get_json },
{ "text/html", :get_html },
{ "text/plain", :get_plain }
], req, state }
end
def content_types_accepted(req, state) do
# Define content types accepted and their associated ProvideResource
# callbacks as atoms
{ [
{ "application/json", :put_json}
], req, state }
end
def delete_resource(req, state) do
get_fruit_index(req) |> FruitServer.remove(:fruit_server)
{true, req, state}
end
def get_json(req, state) do
# JSON ProvideResource callback
{ get_fruit_by_req(req) |> format_json, req, state }
end
defp format_json(fruit) do
{ [{:fruit, fruit}] } |> J.encode
end
def get_html(req, state) do
# HTML ProvideResource callback
{ get_fruit_by_req(req) |> format_html, req, state}
end
defp format_html(fruit) do
"<label>Fruit</label><span>#{fruit}</span>"
end
def get_plain(req, state) do
# Plain ProvideResource callback
{get_fruit_by_req(req) |> format_plain, req, state}
end
defp format_plain(fruit) do
"Fruit: #{fruit}"
end
defp get_fruit_by_req(req) do
get_fruit_index(req)
|> FruitServer.get(:fruit_server)
end
defp get_fruit_index(req) do
R.binding(:index, req)
|> elem(0)
|> String.to_integer
|> elem(0)
end
def put_json(req, state) do
fruit = get_fruit_by_body(req)
get_fruit_index(req) |> FruitServer.change(fruit, :fruit_server)
{true, req, state}
end
defp get_fruit_by_body(req) do
{ [ { "fruit", fruit } ] } = R.body(req)
|> elem(1)
|> J.decode
fruit
end
end
defmodule Router do
use Dynamo
use Dynamo.Router
config :server,
port: 3030,
dispatch: [
{ :_,
[
{"/fruit", Collection, []},
{"/fruit/:index", Object, []}
]
}
]
end
end
f_pid = FruitServer.start_link
Process.register(f_pid, :fruit_server)
FruitServer.add("apple", :fruit_server)
FruitServer.add("banana", :fruit_server)
FruitServer.add("cantaloupe", :fruit_server)
FruitServer.add("date", :fruit_server)
Fruit.Router.start_link
Fruit.Router.run
defmodule FruitServer do
use GenServer.Behaviour
## Public API
def start_link do
{:ok, fruit_server} = :gen_server.start_link FruitServer, [], []
fruit_server
end
def add(item, fruit_server) do
:gen_server.call(fruit_server, {:add, item})
end
def remove(index, fruit_server) do
:gen_server.call(fruit_server, {:remove, index})
end
def list(fruit_server) do
:gen_server.call(fruit_server, :list)
end
def get(index, fruit_server) do
:gen_server.call(fruit_server, {:get, index})
end
def change(index, value, fruit_server) do
:gen_server.call(fruit_server, {:change, index, value})
end
## GenServer API
def init(items) do
{:ok, items}
end
def handle_call({:add, item}, _from, items) do
{:reply, :ok, [item|items]}
end
def handle_call({:remove, index}, _from, items) do
items1 = Enum.with_index(items) |> Enum.reverse
items2 = lc {val, i} inlist items1, index != i, do: val
{:reply, :ok, items2}
end
def handle_call(:list, _from, items) do
{:reply, Enum.reverse(items), items}
end
def handle_call({:get, index}, _from, items) do
fruit = Enum.reverse(items) |> Enum.at(index)
{:reply, fruit, items}
end
def handle_call({:change, index, value}, _from, items) do
{:reply, :ok, Enum.reverse(items) |> List.replace_at(index, value) |> Enum.reverse}
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment