I tried a quick experiment based on http://codeloveandboards.com/blog/2017/10/03/migrating-activerecord-sti-to-ecto/ where depending on the type selected, a particular changeset is used and it’s possible to control the validation of a map field for each scenario. And for the most part it works the way I wanted it to. Except:
- On insert, empty keys are created for the other embedded schemas that weren’t selected.
- On edit, Phoenix forms aren’t rendered with the values present in the DB.
Aside from the debate over whether STI is a good approach, am I missing something in my form definitions that’s causing these problems?
Thanks.
def changeset(%Poll{} = [poll], attrs) do
endpoint
|> validate_required(:poll_type, :poll_attrs)
|> validate_data
end
defp validate_data(changeset) do
changeset
|> build_poll_changeset
|> case do
%{valid?: true} -> changeset
%{errors: errors} -> agg_poll_errors(changeset, errors)
end
end
defp build_poll_changeset(changeset) do
poll_type = get_field(changeset, :poll_type)
poll_attrs = get_field(changeset, :poll_attrs)
cond do
poll_type == Poll.poll_type_event() -> Poll.Event.changeset(poll_attrs)
poll_type == Poll.poll_type_raffle() -> Poll.Raffle.changeset(poll_attrs)
true -> changeset
end
end
defp agg_poll_errors(changeset, errors) do
Enum.reduce(errors, changeset, fn {key, {message, meta}},
acc -> add_error(acc, key, message, meta)
end)
end
And the forms look like:
<div id="poll-wrapper">
<%= label f, :poll_type_event, "Event", class: "poll-tab" %>
<%= radio_button f, :poll_type, "event", checked: true %>
<%= error_tag f, :poll_type_event %>
<%= label f, :poll_type_raffle, "Raffle", class: "poll-tab" %>
<%= radio_button f, :poll_type, "raffle" %>
<%= error_tag f, :poll_type_raffle %>
<div id="poll_type_event_options" class="tab">
<%= label f, :poll_attrs_event_ename, "Name" %>
<%= text_input f, :poll_attrs_event_ename, name: "poll[poll_attrs][ename]" %>
<%= error_tag f, :poll_attrs_event_ename %>
<%= label f, :poll_attrs_event_edesc, "Description" %>
<%= text_input f, :poll_attrs_event_edesc, name: "poll[poll_attrs][edesc]" %>
<%= error_tag f, :poll_attrs_event_edesc %>
</div>
<div id="poll_type_raffle_options" class="tab">
<%= label f, :poll_attrs_raffle_rname, "Name" %>
<%= text_input f, :poll_attrs_raffle_rname, name: "poll[poll_attrs][rname]" %>
<%= error_tag f, :poll_attrs_raffle_rname %>
<%= label f, :poll_attrs_raffle_rdesc, "Description" %>
<%= text_input f, :poll_attrs_raffle_rdesc, name: "poll[poll_attrs][rdesc]" %>
<%= error_tag f, :poll_attrs_raffle_rdesc %>
</div>
</div>