Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rendering the different types with polymorphic_embed_inputs_for #54

Open
simonmcconnell opened this issue Apr 10, 2022 · 2 comments
Open
Labels
feeback needed question Further information is requested

Comments

@simonmcconnell
Copy link

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.

@woylie
Copy link
Contributor

woylie commented May 15, 2022

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. 😅

@mathieuprog mathieuprog added the question Further information is requested label Jul 14, 2022
@mathieuprog
Copy link
Owner

For distinguishing types see

{: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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feeback needed question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants