Skip to content

Commit

Permalink
feat: Select fields can represent Ecto.Enums
Browse files Browse the repository at this point in the history
What changed?
=============

We introduce functionality that allows a `ExTeal.Fields.Select` to
represent a `Ecto.Enum` field.

Why was this change made?
=========================

One of the primary values of `Ecto.Enum` is that it allows you to keep
atom values in a field of a schema.  Previously, values stored as atoms
could never be correctly recalled in a Teal select field because it
always selected the current value from the list of options initated in
`Select.options/2`.

These changes allow for the Ecto.Enum to become the source of the
possible options for the select field, and ensures that the two always
coincide.
  • Loading branch information
staylorwr committed May 18, 2021
1 parent b63f689 commit 5823c9f
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 6 deletions.
37 changes: 34 additions & 3 deletions lib/ex_teal/fields/select.ex
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,13 @@ defmodule ExTeal.Fields.Select do
def value_for(field, model, view) when view in [:show, :index] do
value = Map.get(model, field.field)

option_values = all_options_for(field)
option = Enum.find(option_values, &(&1.value == value)) || %{}
Map.get(option, :key, nil)
if field_represents_an_enum?(field, model) do
value
else
option_values = all_options_for(field)
option = Enum.find(option_values, &(&1.value == value)) || %{}
Map.get(option, :key, nil)
end
end

def value_for(field, model, view), do: Field.value_for(field, model, view)
Expand All @@ -139,4 +143,31 @@ defmodule ExTeal.Fields.Select do

@impl true
def filterable_as, do: ExTeal.FieldFilter.Select

@impl true
def apply_options_for(%Field{options: options} = field, model, _type) do
if field_represents_an_enum?(field, model) and Map.fetch(options, :field_options) == :error do
{:parameterized, Ecto.Enum, details} = schema_field_type(field, model)
enum_options = transform_options(details.values)
%{field | options: Map.put(field.options, :field_options, enum_options)}
else
field
end
end

defp field_represents_an_enum?(_field, model) when not is_struct(model), do: false

defp field_represents_an_enum?(field, model) do
case schema_field_type(field, model) do
{:parameterized, Ecto.Enum, _} ->
true

_ ->
false
end
end

defp schema_field_type(field, model) do
model.__struct__.__schema__(:type, field.field)
end
end
32 changes: 31 additions & 1 deletion test/ex_teal/fields/select_test.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule ExTeal.Fields.SelectTest do
use ExUnit.Case
use TestExTeal.ConnCase

alias ExTeal.Fields.Select

Expand Down Expand Up @@ -36,6 +36,20 @@ defmodule ExTeal.Fields.SelectTest do
assert Select.value_for(field, model, :show) == "S"
end

test "returns the value for a field that represents an ecto enum" do
field = Select.make(:role)
model = build(:user, role: :seller)

assert Select.value_for(field, model, :show) == :seller
end

test "returns the value for a field that does not represent an ecto enum" do
field = Select.make(:author) |> Select.options(~w(foo bar))
model = build(:post, author: "foo")

assert Select.value_for(field, model, :show) == "foo"
end

test "returns the default value for edit" do
field = Select.make(:size)

Expand Down Expand Up @@ -157,4 +171,20 @@ defmodule ExTeal.Fields.SelectTest do
field = Select.make(:foo) |> Select.searchable()
assert field.options.searchable
end

describe "apply_options_for/3" do
test "a field that represents an enum has it's options set automatically" do
field = Select.make(:role)
model = build(:user, role: :seller)

updated_field = Select.apply_options_for(field, model, :show)

assert updated_field.options.field_options == [
%{disabled: false, value: :admin, key: :admin},
%{disabled: false, value: :moderator, key: :moderator},
%{disabled: false, value: :seller, key: :seller},
%{disabled: false, value: :buyer, key: :buyer}
]
end
end
end
3 changes: 2 additions & 1 deletion test/support/factories.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ defmodule TestExTeal.Factory do
def user_factory do
%User{
name: "Motel ExTeal",
email: "teal@motel.is"
email: "teal@motel.is",
role: :admin
}
end

Expand Down
5 changes: 5 additions & 0 deletions test/support/migrations.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ defmodule TestExTeal.Migrations do
use Ecto.Migration

def change do
create_query = "CREATE TYPE user_role AS ENUM ('admin', 'moderator', 'seller', 'buyer')"
drop_query = "DROP TYPE user_role"
execute(create_query, drop_query)

create table(:users) do
add(:email, :string)
add(:name, :string)
add(:password_hash, :string)
add(:role, :user_role)
timestamps()
end

Expand Down
3 changes: 2 additions & 1 deletion test/support/resources.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule TestExTeal.UserResource do
use ExTeal.Resource

alias ExTeal.Fields.{ID, ManyToMany, Number, Text}
alias ExTeal.Fields.{ID, ManyToMany, Number, Select, Text}

def model, do: TestExTeal.User

Expand All @@ -17,6 +17,7 @@ defmodule TestExTeal.UserResource do
ID.make(:id),
Text.make(:name),
Text.make(:email),
Select.make(:role),
ManyToMany.make(:preferred_tags, TestExTeal.Tag)
|> ManyToMany.with_pivot_fields([
Number.make(:order),
Expand Down
1 change: 1 addition & 0 deletions test/support/schema.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ defmodule TestExTeal.User do
field(:email, :string)
field(:name, :string)
has_many(:posts, TestExTeal.Post)
field(:role, Ecto.Enum, values: [:admin, :moderator, :seller, :buyer])

many_to_many(:preferred_tags, TestExTeal.Tag, join_through: TestExTeal.PreferredTag)
timestamps()
Expand Down

0 comments on commit 5823c9f

Please sign in to comment.