Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .formatter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[
import_deps: [:ecto, :phoenix],
inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"],
line_length: 120,
subdirectories: ["priv/*/migrations"]
]
2 changes: 2 additions & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
elixir 1.15.8
erlang 25.3.2.15
1 change: 1 addition & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use Mix.Config
# config :logger, level: :info
#

config :phoenix, :json_library, Poison
# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
Expand Down
10 changes: 5 additions & 5 deletions lib/ja_resource.ex
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
defmodule JaResource do
@type record :: map() | Ecto.Schema.t
@type records :: module | Ecto.Query.t | list(record)
@type record :: map() | Ecto.Schema.t()
@type records :: module | Ecto.Query.t() | list(record)
@type params :: map()
@type attributes :: map()
@type id :: String.t
@type validation_errors :: {:error, Ecto.Changeset.t}
@type id :: String.t()
@type validation_errors :: {:error, Ecto.Changeset.t()}

@moduledoc """
When used, includes all restful actions behaviours. Also a plug.
Expand Down Expand Up @@ -40,6 +40,6 @@ defmodule JaResource do
end

@behaviour Plug
defdelegate init(opts), to: JaResource.Plug
defdelegate init(opts), to: JaResource.Plug
defdelegate call(conn, opts), to: JaResource.Plug
end
18 changes: 10 additions & 8 deletions lib/ja_resource/attributes.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ defmodule JaResource.Attributes do
end

"""
@callback permitted_attributes(Plug.Conn.t, JaResource.attributes, :update | :create) :: JaResource.attributes
@callback permitted_attributes(Plug.Conn.t(), JaResource.attributes(), :update | :create) :: JaResource.attributes()

defmacro __using__(_) do
quote do
Expand All @@ -44,7 +44,7 @@ defmodule JaResource.Attributes do

def permitted_attributes(_conn, attrs, _), do: attrs

defoverridable [permitted_attributes: 3]
defoverridable permitted_attributes: 3
end
end
end
Expand All @@ -63,14 +63,16 @@ defmodule JaResource.Attributes do
end

defp parse_relationships(%{"relationships" => rels}) do
Enum.reduce rels, %{}, fn
({name, %{"data" => nil}}, rel) ->
Enum.reduce(rels, %{}, fn
{name, %{"data" => nil}}, rel ->
Map.put(rel, "#{name}_id", nil)
({name, %{"data" => %{"id" => id}}}, rel) ->

{name, %{"data" => %{"id" => id}}}, rel ->
Map.put(rel, "#{name}_id", id)
({name, %{"data" => ids}}, rel) when is_list(ids) ->
Map.put(rel, "#{name}_ids", Enum.map(ids, &(&1["id"])))
end

{name, %{"data" => ids}}, rel when is_list(ids) ->
Map.put(rel, "#{name}_ids", Enum.map(ids, & &1["id"]))
end)
end

defp parse_relationships(_) do
Expand Down
43 changes: 32 additions & 11 deletions lib/ja_resource/create.ex
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,27 @@ defmodule JaResource.Create do
end

"""
@callback handle_create(Plug.Conn.t, JaResource.attributes) :: Plug.Conn.t | Ecto.Changeset.t | JaResource.record | {:ok, JaResource.record} | {:error, JaResource.validation_errors}
@callback handle_create(Plug.Conn.t(), JaResource.attributes()) ::
Plug.Conn.t()
| Ecto.Changeset.t()
| JaResource.record()
| {:ok, JaResource.record()}
| {:error, JaResource.validation_errors()}

@doc """
Returns a `Plug.Conn` in response to errors during create.

Default implementation sets the status to `:unprocessable_entity` and renders
the error messages provided.
"""
@callback handle_invalid_create(Plug.Conn.t, Ecto.Changeset.t) :: Plug.Conn.t
@callback handle_invalid_create(Plug.Conn.t(), Ecto.Changeset.t()) :: Plug.Conn.t()

@doc """
Returns a `Plug.Conn` in response to successful create.

Default implementation sets the status to `:created` and renders the view.
"""
@callback render_create(Plug.Conn.t, JaResource.record) :: Plug.Conn.t
@callback render_create(Plug.Conn.t(), JaResource.record()) :: Plug.Conn.t()

defmacro __using__(_) do
quote do
Expand All @@ -63,7 +68,7 @@ defmodule JaResource.Create do
import Plug.Conn

def handle_create(_conn, attributes) do
__MODULE__.model.changeset(__MODULE__.model.__struct__, attributes)
__MODULE__.model().changeset(__MODULE__.model().__struct__, attributes)
end

def handle_invalid_create(conn, errors) do
Expand All @@ -78,7 +83,7 @@ defmodule JaResource.Create do
|> Phoenix.Controller.render(:show, data: model)
end

defoverridable [handle_create: 2, handle_invalid_create: 2, render_create: 2]
defoverridable handle_create: 2, handle_invalid_create: 2, render_create: 2
end
end

Expand All @@ -92,27 +97,43 @@ defmodule JaResource.Create do
def call(controller, conn) do
merged = JaResource.Attributes.from_params(conn.params)
attributes = controller.permitted_attributes(conn, merged, :create)

conn
|> controller.handle_create(attributes)
|> JaResource.Create.insert(controller)
|> JaResource.Create.respond(conn, controller)
end

@doc false
# If the application recompiles ja_resource without also recompiling ecto, this will fail.
# If you get an error in ja_resource similiar to "could not load module Ecto.Changeset due to reason :nofile"
# clean the ecto dependency and try again:
# mix deps.clean ecto
# or
# MIX_ENV=test mix deps.clean ecto
Code.ensure_loaded!(Ecto.Changeset)

def insert(%Ecto.Changeset{} = changeset, controller) do
controller.repo().insert(changeset)
end
if Code.ensure_loaded?(Ecto.Multi) do
def insert(%Ecto.Multi{} = multi, controller) do
controller.repo().transaction(multi)
end

Code.ensure_loaded!(Ecto.Multi)

def insert(%Ecto.Multi{} = multi, controller) do
controller.repo().transaction(multi)
end

def insert(other, _controller), do: other

@doc false
def respond(%Plug.Conn{} = conn, _old_conn, _), do: conn
def respond({:error, errors}, conn, controller), do: controller.handle_invalid_create(conn, errors)
def respond({:error, _name, errors, _changes}, conn, controller), do: controller.handle_invalid_create(conn, errors)

def respond({:error, errors}, conn, controller),
do: controller.handle_invalid_create(conn, errors)

def respond({:error, _name, errors, _changes}, conn, controller),
do: controller.handle_invalid_create(conn, errors)

def respond({:ok, model}, conn, controller), do: controller.render_create(conn, model)
def respond(model, conn, controller), do: controller.render_create(conn, model)
end
5 changes: 3 additions & 2 deletions lib/ja_resource/delete.ex
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,21 @@ defmodule JaResource.Delete do
end

"""
@callback handle_delete(Plug.Conn.t, JaResource.record) :: Plug.Conn.t | JaResource.record | nil
@callback handle_delete(Plug.Conn.t(), JaResource.record()) :: Plug.Conn.t() | JaResource.record() | nil

defmacro __using__(_) do
quote do
use JaResource.Repo
use JaResource.Record
@behaviour JaResource.Delete
def handle_delete(conn, nil), do: nil

def handle_delete(conn, model) do
model
|> __MODULE__.repo().delete
end

defoverridable [handle_delete: 2]
defoverridable handle_delete: 2
end
end

Expand Down
11 changes: 6 additions & 5 deletions lib/ja_resource/model.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,17 @@ defmodule JaResource.Model do
@inferred_model JaResource.Model.model_from_controller(__MODULE__)
def model(), do: @inferred_model

defoverridable [model: 0]
defoverridable model: 0
end
end

def model_from_controller(module) do
[_elixir, app | rest] = module
|> Atom.to_string
|> String.split(".")
[_elixir, app | rest] =
module
|> Atom.to_string()
|> String.split(".")

[controller | _ ] = Enum.reverse(rest)
[controller | _] = Enum.reverse(rest)
inferred = String.replace(controller, "Controller", "")

String.to_atom("Elixir.#{app}.#{inferred}")
Expand Down
20 changes: 11 additions & 9 deletions lib/ja_resource/plug.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule JaResource.Plug do
import Plug.Conn
alias Phoenix.Controller
alias JaResource.{Index,Show,Create,Update,Delete}
alias JaResource.{Index, Show, Create, Update, Delete}
@behaviour Plug

@moduledoc """
Expand Down Expand Up @@ -44,18 +44,20 @@ defmodule JaResource.Plug do
@available [:index, :show, :create, :update, :delete]

def init(opts) do
allowed = cond do
opts[:only] -> opts[:only] -- (opts[:only] -- @available)
opts[:except] -> @available -- opts[:except]
true -> @available
end
allowed =
cond do
opts[:only] -> opts[:only] -- opts[:only] -- @available
opts[:except] -> @available -- opts[:except]
true -> @available
end

[allowed: allowed]
end

def call(conn, opts) do
action = Controller.action_name(conn)
action = Controller.action_name(conn)
controller = Controller.controller_module(conn)

if action in opts[:allowed] do
conn
|> dispatch(controller, action)
Expand All @@ -65,8 +67,8 @@ defmodule JaResource.Plug do
end
end

defp dispatch(conn, controller, :index), do: Index.call(controller, conn)
defp dispatch(conn, controller, :show), do: Show.call(controller, conn)
defp dispatch(conn, controller, :index), do: Index.call(controller, conn)
defp dispatch(conn, controller, :show), do: Show.call(controller, conn)
defp dispatch(conn, controller, :create), do: Create.call(controller, conn)
defp dispatch(conn, controller, :update), do: Update.call(controller, conn)
defp dispatch(conn, controller, :delete), do: Delete.call(controller, conn)
Expand Down
13 changes: 9 additions & 4 deletions lib/ja_resource/record.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,26 @@ defmodule JaResource.Record do
end

"""
@callback record(Plug.Conn.t, JaResource.id) :: Plug.Conn.t | JaResource.record
@callback record(Plug.Conn.t(), JaResource.id()) :: Plug.Conn.t() | JaResource.record()

defmacro __using__(_) do
quote do
unless JaResource.Record in @behaviour do
use JaResource.Records
@behaviour JaResource.Record

def record(conn, id) do
def record(conn, id) when is_binary(id) do
int_id = String.to_integer(id)
record(conn, int_id)
end

def record(conn, id) when is_integer(id) do
conn
|> records
|> records()
|> repo().get(id)
end

defoverridable [record: 2]
defoverridable record: 2
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/ja_resource/records.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ defmodule JaResource.Records do

Return value should be %Plug.Conn{} or an %Ecto.Query{}.
"""
@callback records(Plug.Conn.t) :: Plug.Conn.t | JaResource.records
@callback records(Plug.Conn.t()) :: Plug.Conn.t() | JaResource.records()

defmacro __using__(_) do
quote do
Expand All @@ -40,7 +40,7 @@ defmodule JaResource.Records do

def records(_conn), do: model()

defoverridable [records: 1]
defoverridable records: 1
end
end
end
Expand Down
7 changes: 4 additions & 3 deletions lib/ja_resource/repo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ defmodule JaResource.Repo do
@doc false
def default_repo do
quote do
if Application.get_env(:ja_resource, :repo) do
def repo, do: Application.get_env(:ja_resource, :repo)
defoverridable [repo: 0]
@ja_resource_repo Application.compile_env(:ja_resource, :repo)
if @ja_resource_repo do
def repo, do: @ja_resource_repo
defoverridable repo: 0
end
end
end
Expand Down
7 changes: 5 additions & 2 deletions lib/ja_resource/serializable.ex
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ defmodule JaResource.Serializable do

See http://github.com/AgilionApps/ja_serializer for option format.
"""
@callback serialization_opts(Plug.Conn.t, map, struct | list) :: Keyword.t
@callback serialization_opts(Plug.Conn.t(), map, struct | list) :: Keyword.t()

defmacro __using__(_) do
quote do
Expand All @@ -54,14 +54,17 @@ defmodule JaResource.Serializable do

def serialization_opts(_conn, %{"fields" => f, "include" => i}, _model_or_models),
do: [include: i, fields: f]

def serialization_opts(_conn, %{"include" => i}, _model_or_models),
do: [include: i]

def serialization_opts(_conn, %{"fields" => f}, _model_or_models),
do: [fields: f]

def serialization_opts(_conn, _params, _model_or_models),
do: []

defoverridable [serialization_opts: 3]
defoverridable serialization_opts: 3
end
end
end
Expand Down
Loading