I have the following models :
# Foo model
schema "foo" do
field :name, :string
has_many: :bars, App.Bar
end
# App model
schema "bar" do
field :name, :string
belongs_to: foo, App.Foo
end
And this form:
# form.html (Foo)
<%= form_for @changeset, @action, fn f -> %>
<%= text_input f, :name, class: "form-control" %>
<%= submit "Submit", class: "btn btn-primary" %>
<% end %>
Inside this form, how can I add text fields to populate my new Foo
with Bars
?
The following doesn't work because bars
is not preloaded :
<%= text_input f, :bars, class: "form-control" %>
Am I on the right track ? If so how can I preload Bars
in the form?
Update, controller :
def new(conn, _params) do
changeset = %Foo{} |> Repo.preload(:bars) |> Foo.changeset
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"foo" => foo_params}) do
changeset = %Foo{} |> Repo.preload(:bars) |> Foo.changeset(foo_params)
if changeset.valid? do
Repo.insert!(changeset)
conn
|> put_flash(:info, "Foo created successfully.")
|> redirect(to: foo_path(conn, :index))
else
render(conn, "new.html", changeset: changeset)
end
end
The preloading seems to work, but I get an Argument error
when reaching
<%= text_input f, :bars, class: "form-control" %>
:
[error] #PID<0.280.0> running App.Endpoint terminated
Server: 192.168.48.202:4000 (http)
Request: GET /
** (exit) an exception was raised:
** (ArgumentError) argument error
:erlang.bit_size([])
(phoenix_html) lib/phoenix_html/tag.ex:66: anonymous fn/2 in Phoenix.HTML.Tag.tag_attrs/1
(elixir) lib/enum.ex:1261: Enum."-reduce/3-lists^foldl/2-0-"/3
(phoenix_html) lib/phoenix_html/tag.ex:35: Phoenix.HTML.Tag.tag/2
(app) web/templates/foo/form.html.eex:16: anonymous fn/1 in App.FooView.form.html/1
(phoenix_html) lib/phoenix_html/form.ex:181: Phoenix.HTML.Form.form_for/4
(app) web/templates/foo/form.html.eex:1: App.FooView."form.html"/1
(app) web/templates/foo/new.html.eex:3: App.FooView."new.html"/1
Check out Jose's post on Working with Associations and Embeds for a robust example using ToDoLists and ToDoItems (specifically the section titled "Nested associations and embeds"). The example below is a derivative example reflecting your combination of Foos with Bars.
To start off, you're on the right track with:has_many: :bars, App.Bar
Modify your form to reflect:
# form.html (Foo)
<%= form_for @changeset, @action, fn f -> %>
<%= text_input f, :name, class: "form-control" %>
<%= inputs_for f, :bars, fn i -> %>
<div class="form-group">
<%= label i, :name, "Bar ##{i.index + 1}", class: "control-label" %>
<%= text_input i, :name, class: "form-control" %>
</div>
<% end %>
<%= submit "Submit", class: "btn btn-primary" %>
<% end %>
This utilizes the inputs_for/4
function from Phoenix.HTML.Form
to generate fields for your :bars association. Here we've labeled each sequentially, "Bar #1" and "Bar #2" as well as provided text_input
tags for each.
Now, that you have to adjust your controller's new
and create
actions to reflect the inclusion of some bars (let's say two for example):
def new(conn, _params) do
changeset = %Foo{} |> Foo.changeset(%Foo{bars: [%MyApp.Bar{}, %MyApp.Bar{}]})
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"foo" => foo_params}) do
changeset = %Foo{} |> Foo.changeset(foo_params)
case Repo.insert(changeset) do
conn
|> put_flash(:info, "Foo created successfully.")
|> redirect(to: foo_path(conn, :index))
else
render(conn, "new.html", changeset: changeset)
end
end
Your edit
and update
actions will need to preload the bars:
foo = Repo.get!(Foo, id) |> Repo.preload(:bars)
If your model has nested association you can use inputs_for to attach nested data to the form. For example, please see here in Nested Inputs Section.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With