Editing associated Ecto models in a single form

Hey folks.

I have a User model with an associated UserSettings model. Currently I have a user-editable settings form that works fine. Now I want to add an admin interface wherein administrators can edit both the user’s settings, along with other admin-only values directly on User.

I’m a little confused about how to go about this, though. I see inputs_for, but this seems to use embedded schemas rather than associations. Are there any examples of how to create forms for an association, and how to reference them from elsewhere?

Thanks.

1 Like

inputs_for isn’t an ecto thing at all, it’s a Phoenix.HTML thing, it just creates a new map at the given key containing the form elements within it, it’s perfectly suited for associations. :slight_smile:

3 Likes

Oh, interesting. The phienix.html.form docs specifically call out nested
or embedded associations, and I wasn’t sure if embedded/nested
associations were different from belongs_to and friends.

Thanks for the clarification.

Just useful examples those are, they are entirely generic and don’t relate to ecto at all actually. :slight_smile:

I use inputs_for quite a lot to build up entire tree’s of form data.

OK, I think I almost have it, but now I’m trying to implement this
without duplication. I have this basic user settings form. Here, PBF
is the Phoenix bootstrap-forms package that builds fields for forms with
Bootstrap styling, errors, etc.:


<%= form_for @changeset, Routes.settings_path(@conn, :update), [method: 
"post"], fn f -> %>
   <%= PBF.text_input f, :first_name, input: [autofocus: true] %>
   <%= PBF.text_input f, :last_name %>
   <%= PBF.text_input f, :organization %>
   <%= PBF.submit f, "Save" %>
<% end %>

I want to factor out the fields into something like:


<%= PBF.text_input f, :first_name, input: [autofocus: true] %>
<%= PBF.text_input f, :last_name %>
<%= PBF.text_input f, :organization %>

Then refer to that partial from either within a form, or within an
embedded input. So either the user can change those fields from their
settings screen, or an admin sees those fields alongside other
admin-only fields on the admin screen for a user. Is that possible? I
either need some sort of container component that I can pass f in
either from the form or the input, or some other mechanism I’m not
thinking of yet. :slight_smile:

I recognize these are only 3 fields, but I don’t want to potentially
have logic and fields spread out over multiple files if I can avoid it.

Thanks!

It’s very possible. Just loop over your areas for each and do an inputs_for inside that, you can pass forms like anything else via a render call. :slight_smile:

I’m sorry, I’m confused. What I want to do is put something like this:


<%= PBF.text_input f, :first_name, input: [autofocus: true] %>
<%= PBF.text_input f, :last_name %>
<%= PBF.text_input f, :organization %>

into something like this:


<%= form_for @changeset, @action, fn f -> %>
   <%= if @changeset.action do %>
     <div class="alert alert-danger">
       <p>Oops, something went wrong! Please check the errors below.</p>
     </div>
   <% end %>

   <!-- I want the settings inputs here, but not as a separate form. 
inputs_for should work here I imagine. -->

   <%= PBF.email_input f, :email, input: [autofocus: true] %> <!-- These 
settings are admin-only. -->

And then again here:


<%= form_for @changeset, Routes.settings_path(@conn, :update), [method: 
"post"], fn f -> %>
   <!-- I took the fields from here. inputs_for wouldn't work here I 
imagine since I want those inputs directly in this form at the 
top-level. -->

   <%= PBF.submit f, "Save" %>

So I’m trying to put the same FormData fields both in a top-level form
where they’re modifying a model directly, and in an embedded form where
they’re modifying an association. If I could just dump the string
content of a file directly inline somehow, that’d be exactly what I want
to achieve, though I’d rather make it a function somehow. Can I
parameterize an .eex file somehow? I’d rather leave those fields in
something like an HTML template so designers can work with them more
easily, though I guess I can move them to a view if there isn’t a way.

Thanks for your help.

I’m not even sure you need inputs_for for that, just call <%= render ... whatever info here to pass in %> and put those elements in that other eex file, you’ll then be able to use it in both places. :slight_smile:

Right, but wouldn’t:


<%= PBF.text_input f, :first_name, input: [autofocus: true] %>
<%= PBF.text_input f, :last_name %>
<%= PBF.text_input f, :organization %>

as a single file fail to compile? f isn’t defined anywhere in that
file, and defining it in a form_for or an inputs_for isn’t equivalent.

One thing that I may not have made clear: first_name, last_name, and
organization are defined on a UserSettings model separate from User. One
form is meant to edit UserSettings, while the other edits User and its
:settings association which is embedded.

I’m sorry, I seem to be doing a bad job of explaining this. I may just
copy the three lines, though I really wish there were some way to just
wrap the above code in an inputs_for so the variable is defined, and not
save to the association when it is being edited directly. I appreciate
the value of precompiled eex but sometimes I wish I had a safe escape
hatch into string templates. :slight_smile:

Pass f in and use it there as @f instead. :slight_smile:

1 Like

Oh, duh. :slight_smile:

Works beautifully now. Thanks for your help!

1 Like