polymorphic_embed
polymorphic_embed copied to clipboard
rendering the different types with `polymorphic_embed_inputs_for`
Hello. I'm trying to tidy up some code for a SQL query builder. Presently, I manually assign the inputs' name and id fields and save it all as JSONB.
How are you distinguishing between types in the frontend? The example in the docs only shows the sms part, when the channel can be either sms or email.
And have you had any luck using polymorphic_embed_inputs_for for an {:array, PolymorphicEmbed}?
Given these schemas:
defmodule Polypipe.EmbedQuery do
defmodule EmbedRule do
use Ecto.Schema
import Ecto.Changeset
embedded_schema do
field :field, :string
field :operator, Ecto.Enum, values: ~w(lt le eq neq ge gt like ilike notlike notilike in notin null notnull)a
field :value, :string
end
def changeset(rule, params) do
rule
|> cast(params, [:field, :value, :operator])
end
end
defmodule EmbedRuleGroup do
use Ecto.Schema
import Ecto.Changeset
import PolymorphicEmbed, only: [cast_polymorphic_embed: 3]
alias Polypipe.EmbedQuery.{EmbedRule, EmbedRuleGroup}
embedded_schema do
field :condition, :string
field :rules, {:array, PolymorphicEmbed},
types: [
rule_group: [module: EmbedRuleGroup, identify_by_fields: [:condition, :rules]],
rule: [module: EmbedRule, identify_by_fields: [:field, :operator]]
],
on_type_not_found: :raise,
on_replace: :delete
end
def changeset(group, params) do
group
|> cast(params, [:condition])
|> cast_polymorphic_embed(:rules, required: true)
end
end
use Ecto.Schema
import Ecto.Changeset
schema "embed_queries" do
field :name, :string
embeds_one :rule_group, __MODULE__.EmbedRuleGroup, on_replace: :delete
end
def changeset(data \\ %__MODULE__{}, params) do
data
|> cast(params, [:name])
|> cast_embed(:rule_group)
end
end
And this basic template (haven't use components yet as I just wanted to see if it works).
<section class="row">
<article class="column">
<h2>Form</h2>
<%= form_for @changeset, Routes.page_path(@conn, :create), fn f -> %>
<%= label f, :name %>
<%= text_input f, :name %>
<%= inputs_for f, :rule_group, fn f_rule_group -> %>
<h2>Rule Group Form</h2>
<%= label f_rule_group, :condition %>
<%= text_input f_rule_group, :condition %>
<%= polymorphic_embed_inputs_for f_rule_group, :rules, :rule, fn f_rule -> %>
<h2>Poly Form</h2>
<%= case f_rule.data do %>
<% %Polypipe.EmbedQuery.EmbedRule{} -> %>
<h3>Rule <%= f_rule.index %></h3>
<%= text_input f_rule, :field %>
<%= text_input f_rule, :operator %>
<%= text_input f_rule, :value %>
<% %Polypipe.EmbedQuery.EmbedRuleGroup{} -> %>
<h3>Rule -> Rule Group <%= f_rule.index %></h3>
<%= label f_rule, :condition %>
<%= text_input f_rule, :condition %>
<%= polymorphic_embed_inputs_for f_rule, :rules, :rule, fn f_rule_rule_group_rule -> %>
<h2>Deep Form</h2>
<h3>Rule <%= f_rule_rule_group_rule.index %></h3>
<%= text_input f_rule_rule_group_rule, :field %>
<%= text_input f_rule_rule_group_rule, :operator %>
<%= text_input f_rule_rule_group_rule, :value %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
</article>
</section>
and this data
%Polypipe.EmbedQuery{
name: "nombre",
rule_group: %Polypipe.EmbedQuery.EmbedRuleGroup{
condition: "and",
rules: [
%Polypipe.EmbedQuery.EmbedRule{
field: "field_a",
operator: :eq,
value: "a"
},
%Polypipe.EmbedQuery.EmbedRuleGroup{
condition: "or",
rules: [
%Polypipe.EmbedQuery.EmbedRule{
field: "field_b",
operator: :eq,
value: "b"
},
%Polypipe.EmbedQuery.EmbedRule{
field: "field_c",
operator: :eq,
value: "c"
}
]
}
]
}
}
This kind of works, but it clearly isn't right because I'm telling it these polymorphic embeds are rules, when really they're a list of rules and rule_groups.
Hey @simonmcconnell,
regarding handling different types, maybe it is helpful to have a look at my changes in #52: https://github.com/mathieuprog/polymorphic_embed/pull/52/files#diff-c25d18a6ba2355e2943f07bd6f1ba5e8902a40acac7ab7210a29fc613fb2b379.
I think we tried using {:array, PolymorphicEmbed} before, and I don't remember all the details, but we ended up with a different schema structure: The main schema A has an embeds_many on a schema B. That schema B has some meta fields common to all polymorphic types, plus a data field that holds the actual polymorphic data. Something like:
def SchemaA do
schema "schema_a" do
# more fields
embeds_many :bs, SchemaB, on_replace: :delete
end
end
def SchemaB do
embedded_schema do
# more fields
field :data, PolymorphicEmbed,
types: [
type_a: TypeA,
type_b: TypeB,
],
on_type_not_found: :raise,
on_replace: :update
end
end
And that works fine in combination with the polymorphic_embed_inputs_for/2 from the PR.
<%= for fb <- inputs_for(form, :bs) do %>
<%= hidden_inputs_for(fb) %>
<%= for fd <- polymorphic_embed_inputs_for fb, :data do %>
<%= hidden_inputs_for(fd) %>
<%= case get_polymorphic_type(fd, SchemaB, :data) do %>
<% :type_a -> %>
<!-- type_a inputs -->
<% :type_b -> %>
<!-- type_b inputs -->
<% end %>
<% end %>
<% end %>
This schema structure might not be the best solution for all use cases, but I would imagine that it could work similarly with an array field. I haven't tried it, though.
The examples are a bit hastily extracted from a way more complex form, there might be some errors hidden in there. I hope that it still helps to give you some ideas. 😅
For distinguishing types see
- https://github.com/mathieuprog/polymorphic_embed/blob/v1.10.0/lib/polymorphic_embed.ex#L344
- https://github.com/mathieuprog/polymorphic_embed/blob/v1.10.0/lib/html/form.ex#L9
{:array, PolymorphicEmbed} works with polymorphic_embed_inputs_for. Maybe requires to tweak input names when working with lists, and lib might need improvements, not sure.
Could you share some feedback on this?