Ecto custom type in Phoenix Form?

I'm using an Ecto custom type in one of my Phoenix application's schemas, like described here (specifically, making use of Postgres Ranges to specify a range of times, like "between 12:00-4:00"). I'm able to insert/retrieve from the database without a problem, but I'm having trouble coming up with a good way to present a form for the user using changesets and Phoenix forms.

So with a schema looks like this (TimeRange is the custom type):

  @primary_key false
  @foreign_key_type :binary_id
  schema "person_messaging_settings" do
    field :can_receive_email, :boolean, null: false
    field :can_receive_sms, :boolean, null: false
    field :allowed_hours, MyApp.Ecto.TimeRange
    belongs_to :person, Person
    timestamps()
  end

I can use inputs_for for the belongs_to association, and ideally I could do something like this in my EEX template:

<%= form_for @changeset, Routes.settings_path(@conn, :update), fn f -> %>

  <!-- other field values -->

  <%= inputs_for f, :allowed_hours, fn ah -> %>
    <%= time_select ah, :upper %>
    <%= time_select ah, :lower %>
  <% end %>
<% end %>

But this complains because inputs_for is strictly for associations.

728x90

1 Answers Ecto custom type in Phoenix Form?

Here's a raw untested idea with virtual fields.

Schema file:

schema "person_messaging_settings" do
  # ...
  field :allowed_hours_from, :time, virtual: true
  field :allowed_hours_to, :time, virtual: true
end

def changeset do
  record
  |> cast(attrs, [..., :allowed_hours_from, :allowed_hours_to])
  |> set_allowed_hours()
  |> validate_required([..., :allowed_hours])
end

defp set_allowed_hours(changeset) do
  case {get_field(changeset, :allowed_hours_from), get_field(changeset, :allowed_hours_to)} do
    {nil, nil} -> changeset
    {nil, _}   -> changeset
    {_, nil}   -> changeset
    {from, to} -> put_change(changeset, :allowed_hours, "#{from}-#{to}")
  end
end

And the form:

<%= form_for @changeset, Routes.settings_path(@conn, :update), fn f -> %>

  <!-- other field values -->

  <%= time_select f, :allowed_hours_from %>
  <%= time_select f, :allowed_hours_to %>
<% end %>

Although I don't know how you would populate the two time_selects when editing a saved timerange (decomposing the :allowed_hours). Perhaps somebody else does. Or you render a regular html input with the correct name and value.

Edit 3... Or would this work?

<%= time_select f, :allowed_hours_from, value: something(f.data.allowed_hours) %>

1 weeks ago