Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Insert a model with has_many association

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
like image 306
Kernael Avatar asked Jul 16 '15 12:07

Kernael


2 Answers

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)
like image 169
David Kuhta Avatar answered Nov 11 '22 14:11

David Kuhta


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.

like image 2
Mohammad Ahmad Avatar answered Nov 11 '22 13:11

Mohammad Ahmad