0

I think what I would like to achieve is rather straightforward. I would like to have a form with 5 fields, and a person can create any number of objects from that form (i.e. If you fill in only 1 field, you create 1 object, if you field 2 fields, you create 2 objects etc...)

Interestingly, nothing much comes up when I attempt to google this topic. The only "proper" example I came across was this. Which leads me to where I'm currently at (cause it doesn't work).

Here's my foo_controller.rb

def new
  @foo = []
  5.times do
    @foo << Foo.new
  end
end

def create
  if params.has_key?("foos")
    Foo.create(foo_params(params["foos"]))
  else
    params["foos"].each do |f|
      Foo.create(foo_params(f))
    end
  end
end

private
def foo_params(my_params)
  my_params.permit(:item_one, :item_two)
end

And here's the new.html.erb

<%= form_tag foos_path do %>
  <% @foo.each do |f| %>
    <% fields_for "foos[]", f do |ff| %>
      #all the form stuff, labels, buttons etc
    <% end %>
  <% end %>
<% end %>

Now before I even hit submit, I already "know" that there's one problem. My form fields all have the same ID, which means even "if" I could submit without any problems, only the last entry would end up hitting the database. So...

Problem #1 - How do I have unique form IDs?

The next issue I face is when I hit submit, I get this error Rack::QueryParser::ParameterTypeError (expected Array (got Rack::QueryParser::Params), so basically the params is not receiving the right thing. And that's my next problem...

Problem 2 - What's the proper way to be passing an Array of params?

But that is all assuming that THIS guide's solution works. I followed pretty much every step but came up empty. So i guess my big question/problem is:

How do i create One or Multiple objects of a single model?

2
  • have you gone through Nested Forms section of the Rails documentation Commented Oct 4, 2017 at 1:58
  • @ImranAli i have. I may not have understood it perfectly, but from what i understand, Nested Forms are meant for creating the Child together with the Parent in the Parent's form? Commented Oct 4, 2017 at 2:08

3 Answers 3

2

Looks like you are headed in the right direction.

Problem 1: The IDs

You don't need IDs if you are creating new records. If you want to be able to edit the records with the same form it may be better to generate IDs, but that is more of a decision on how you want to handle create vs. update.

The way this works is Rails looks at the Name of the form elements. If you have empty brackets in the name Rails will render the params into an array:

<input type="text" name="foo[bar][][baz]" value="one" />
<input type="text" name="foo[bar][][baz]" value="two" />
<input type="text" name="foo[bar][][baz]" value="three" />

params => { foo: { bar: ["one", "two", "three"]}}

But, if you have IDs, Rails will render params into a hash:

<input type="text" name="foo[bar][1][baz]" value="one" />
<input type="text" name="foo[bar][2][baz]" value="two" />
<input type="text" name="foo[bar][3][baz]" value="three" />

params => { foo: { bar: {1: "one", 2: "two", 3: "three"}}}

Problem 3: Creating the models

Now the trick is how you handle the creation of the models. If you want to use nested attributes you can do that by setting up #accepts_nested_attributes_for and nesting everything under bar_attributes. This will let you just pass the params hash to the model and have it work some magic.

Alternatively you might want to iterate over the params hash and make each model right in your controller.

Or create a form object to contain all that logic. Up to you.

I would start with the manual iterate-over-params approach and get things working. Then you can refactor and clean things up.

This might look something like this in your controller:

array_of_bars = foo_params[:bar].map do |bar_params|
  Bar.new(baz: bar_params[:baz])
end

Take a look at RailCast's Dynamic Forms and Nested Forms for more details. I revisit those guides numerous times over the last few years and have been happy with the resulting code every time.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for the long and detailed answer. Just a couple of questions. If i were to use nested attributes, I am basically creating a "dummy parent" just to use that right? (Or i am mistaken that nested attributes are only meant for Parent/Child forms). The other question is, in the code you've written, specfically Bar.new(baz: bar_params[:baz]), could you explain why does the syntax include baz:? Rather than just Bar.new(bar_params[:baz]).
Oh, sorry, it was just psudocode from memory. If you are using nested attributes you wouldn't need to create the nested model directly. Rails will do that for you. It will even provide form helpers to set things up so you get the proper bar_attributes or baz_attributes keys.
0

Nested forms is always tricky but there are gems that can do that for you. One of my personal favourites would be this: https://github.com/nathanvda/cocoon

Here's a tutorial using the gem https://www.sitepoint.com/better-nested-attributes-in-rails-with-the-cocoon-gem/

Comments

0

I've been dealing with the same error Rack::QueryParser::ParameterTypeError (expected Array (got Rack::QueryParser::Params). This answer helped me to fix it.

The error is caused by the fact that the records with an ID render params into a hash. The solution: you don't need the fields_for. Removing the fields_for block and just using form tag helpers instead should work, such as:

text_field_tag "foos[][name]", f["name"]

With this approach, there will be no IDs that mess with your params. If you submit and inspect the params, you will see a nice array of hashes.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.