Skip to content

Commit

Permalink
[PLATFORM-543] Refactor Rolodex.Schema.field/3 macro (#42)
Browse files Browse the repository at this point in the history
Rather than define a bunch of `__field__/1` functions on the caller
module as the `field/3` macro is invoked, we only collect field
definition metadata in an accumulator attribute. Later, we serialize
the field definitions together into a formatted map on demand via
`__schema__(:fields)`.

This change will allow users to define fields on their schemas in
for comprehensions or maps, without any compiler warnings about
unmatchable function heads.
  • Loading branch information
bceskavich authored May 6, 2019
1 parent 9376a75 commit 7888a08
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 61 deletions.
20 changes: 7 additions & 13 deletions lib/rolodex/schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ defmodule Rolodex.Schema do

def __schema__(:name), do: unquote(name)
def __schema__(:desc), do: unquote(Keyword.get(opts, :desc, nil))
def __schema__(:fields), do: @fields |> Enum.reverse()

def __schema__(:fields) do
Map.new(@fields, fn {id, opts} -> {id, Field.new(opts)} end)
end
end
end

Expand Down Expand Up @@ -103,12 +106,7 @@ defmodule Rolodex.Schema do
"""
defmacro field(identifier, type, opts \\ []) do
quote do
@fields unquote(identifier)

def __field__(unquote(identifier)) do
field = ([type: unquote(type)] ++ unquote(opts)) |> Field.new()
{unquote(identifier), field}
end
@fields {unquote(identifier), [type: unquote(type)] ++ unquote(opts)}
end
end

Expand Down Expand Up @@ -209,12 +207,8 @@ defmodule Rolodex.Schema do
@spec to_map(module()) :: map()
def to_map(schema) do
desc = schema.__schema__(:desc)

props =
schema.__schema__(:fields)
|> Map.new(&schema.__field__/1)

Field.new(type: :object, properties: props, desc: desc)
fields = schema.__schema__(:fields)
Field.new(type: :object, properties: fields, desc: desc)
end

@doc """
Expand Down
14 changes: 13 additions & 1 deletion test/rolodex/processors/swagger_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,15 @@ defmodule Rolodex.Processors.SwaggerTest do
},
"parent" => %{
"$ref" => "#/components/schemas/Parent"
},
"private" => %{
"type" => "boolean"
},
"archived" => %{
"type" => "boolean"
},
"active" => %{
"type" => "boolean"
}
}
}
Expand Down Expand Up @@ -489,7 +498,10 @@ defmodule Rolodex.Processors.SwaggerTest do
},
parent: %{
"$ref" => "#/components/schemas/Parent"
}
},
private: %{type: :boolean},
archived: %{type: :boolean},
active: %{type: :boolean}
}
}
},
Expand Down
76 changes: 29 additions & 47 deletions test/rolodex/schema_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,52 +17,31 @@ defmodule Rolodex.SchemaTest do
assert User.__schema__(:name) == "User"
assert User.__schema__(:desc) == "A user record"

assert User.__schema__(:fields) == [
:id,
:email,
:comment,
:parent,
:comments,
:comments_of_many_types,
:multi
]
end
end

describe "#field/3 macro" do
test "It generates getters" do
assert User.__field__(:id) ==
{:id, %{type: :uuid, desc: "The id of the user", required: true}}

assert User.__field__(:email) ==
{:email, %{type: :string, desc: "The email of the user", required: true}}

assert User.__field__(:comment) == {:comment, %{type: :ref, ref: Comment}}
assert User.__field__(:parent) == {:parent, %{type: :ref, ref: Parent}}

assert User.__field__(:comments) ==
{:comments, %{type: :list, of: [%{type: :ref, ref: Comment}]}}

assert User.__field__(:comments_of_many_types) ==
{:comments_of_many_types,
%{
desc: "List of text or comment",
type: :list,
of: [
%{type: :string},
%{type: :ref, ref: Comment}
]
}}

assert User.__field__(:multi) ==
{:multi,
%{
type: :one_of,
of: [
%{type: :string},
%{type: :ref, ref: NotFound}
]
}}
assert User.__schema__(:fields) == %{
id: %{type: :uuid, desc: "The id of the user", required: true},
email: %{type: :string, desc: "The email of the user", required: true},
comment: %{type: :ref, ref: Comment},
parent: %{type: :ref, ref: Parent},
comments: %{type: :list, of: [%{type: :ref, ref: Comment}]},
comments_of_many_types: %{
desc: "List of text or comment",
type: :list,
of: [
%{type: :string},
%{type: :ref, ref: Comment}
]
},
multi: %{
type: :one_of,
of: [
%{type: :string},
%{type: :ref, ref: NotFound}
]
},
private: %{type: :boolean},
archived: %{type: :boolean},
active: %{type: :boolean}
}
end
end

Expand Down Expand Up @@ -94,7 +73,10 @@ defmodule Rolodex.SchemaTest do
%{type: :string},
%{type: :ref, ref: NotFound}
]
}
},
private: %{type: :boolean},
archived: %{type: :boolean},
active: %{type: :boolean}
}
}
end
Expand Down
9 changes: 9 additions & 0 deletions test/rolodex_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,15 @@ defmodule RolodexTest do
},
"parent" => %{
"$ref" => "#/components/schemas/Parent"
},
"private" => %{
"type" => "boolean"
},
"archived" => %{
"type" => "boolean"
},
"active" => %{
"type" => "boolean"
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions test/support/mocks/schemas.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
defmodule Rolodex.Mocks.User do
use Rolodex.Schema

@configs [
private: :boolean,
archived: :boolean,
active: :boolean
]

schema "User", desc: "A user record" do
field(:id, :uuid, desc: "The id of the user", required: true)
field(:email, :string, desc: "The email of the user", required: true)
Expand All @@ -22,6 +28,9 @@ defmodule Rolodex.Mocks.User do

# A field with multiple possible types
field(:multi, :one_of, of: [:string, Rolodex.Mocks.NotFound])

# Can use a for comprehension to define many fields
for {name, type} <- @configs, do: field(name, type)
end
end

Expand Down

0 comments on commit 7888a08

Please sign in to comment.