Rails 5 - Create Multiple Objects of 1 Model - ruby-on-rails

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?

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.

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/

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.

Related

How to create checkboxes using scaffold in rails?

I have to create a feature to manage my contracts. Everything was going fine working with the strings etc.. but I have to create checkboxes in my scaffold for an example
()Toll
()Chemist
And I want to save this if the boxes are checked.
I tried put this code into the Form file, it appears, but don't save the data when checked, I guess that is not the correct way, because there is nothing created in the database.
<input type="checkbox" name="tag_ids[]" value="1" />
I'm planning to generate all the checkboxes as strings when generating the scaffold and try something else.
Does anyone have a better idea how can I accomplish this, could be the easiest way as possible is not for real development. Thanks for all.
first you should check if the vars are whitelisted,
second you should try the rails helper for this:
<%= form.check_box "tag_ids[]", "Chemist", false, id:"1"%>
with this syntax the input will be stored in an array, so you can add multiple checkboxes for the same variable. the input will than be stored ["Chemist","Toll"]
Later you can split these easily by .split
You can add the following to your form
<% Tag.all.each do |tag| %>
<label>
<%= f.check_box_tag "contract[tag_ids][]", tag.id, #contract.tags.include?(tag) %>
<%= tag.name %>
</label>
<% end %>
This can then be passed through your parameters as long as your strong parameters allow them eg:
params.require(:contract).permit(.... , tag_ids: [])
see here for a proper discussion: https://kolosek.com/rails-join-table/
Also if you are interested I'd look at using simple_form (https://github.com/plataformatec/simple_form) for your rails forms. It makes all of this really easy.

How to tell options_for_select field where to save selected option value?

I'm returning to Rails after almost 5 years away and building out a personal project. In my _form.html.erb file, I'm trying to use a field, but the data never gets saved where I think it will be.
<select>
<%= options_for_select([['black'], ['blue'], ['red']], :selected => :color) %>
</select>
In my index, when I try to use model.color I get nothing returned. I'm sure its something basic I'm not getting, but for some reason Google searches and example code doesn't look like exactly like what I got. I'm not sure what option to pass to tell the form where to save the selected value.
You need to give the select tag a name (e.g. <select name="foo"> populates params[:foo], <select name="foo[bar]"> populates params[:foo][:bar]; alternatively use the select_tag/select form helper methods – see https://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-select_tag and https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-select).
Be aware: If you haven't used Rails for several years, chances are you don't know about strong parameters which you need to use if you want to do direct assignment (e.g. User.new(params[:user]) doesn't work as it used to in older Rails versions – you need to use strong parameters here). Details: https://edgeguides.rubyonrails.org/action_controller_overview.html#strong-parameters.
You need to assign the input a name in order to do anything meaningful with it - This is not Rails specific. Its universally true for web development. Form data in HTTP is just a bunch of key value pairs and the name attribute sets the key.
In Rails you should use the form builders and input helpers unless you have a very good reason not to - it is after driven by convention over configuration. They will handle assigning the name attribute for you:
<%= form_with(model: #thing) do |f| %>
<%= f.select :color, [['Black','black'], ['Blue','blue'], ['Red', 'red']] %>
# ...
<% end %>
This renders something like:
<select name="things[color]">
<option value="black">Black</option>
# ...
</select>
And would give you the following params hash:
{
thing: {
color: 'black'
}
}
They also provide data bindings between your model and the form so that the inputs are not reset when the user submits an invalid form.
I would really start by reading the rails guides as you have a lot to catch up on.

Manually Creating Form Fields in Rails, How does rails auto populate fields when editing?

I am creating a form in Rails by hand, ie:
<input type="number" name="funds_application[product_revenues_attributes][1][amount]">
This works fine when submitting data, but when I am editing a record the fields do not auto populate. Does anyone know how rails does this? Do I need to use a <%= text_field_tag %> to get that part of it working.
This is a small part of a larger form where I am using Simple_form, and the rest works as expected. I find complex forms sort of mind melting in rails, what does it want?
Do you have to do it by hand? Can you still use form helpers?
Anyways, the way Rails does it is that you always give the form an instance of the model you are working with, and call the getter on its attributes. For a new instance, they'll be blank. For a saved instance, they'll have values. For instance, if you had a User model with a login and name attribute, you could do #user = User.new in your controller, and in your form do (using helper tags):
<%= text_field_tag "login", #user.login %>
<%= text_field_tag "name", #user.name %>
And if you had an actual user (#user = User.first), you could still use it with that view.
So no, you don't have to use the form tags, because the underlying principle is always giving an instance of the model you are working with, and deciding what defaults to use if an attribute is nil/blank.
So if you always had an object to work with, and yet still wanted to do it manually, you could type:
<input type="number" name="funds_application[product_revenues_attributes][1][amount]" value=#my_object.amount>
Or whatever the field really is. That way, it gets some default value, but if the object already has something for that attribute, it will output it.
If you're building the form yourself, you can populate the fields yourself from the object passed in, with code along these lines:
<input type="number" name="funds_application[product_revenues_attributes][1][amount]" value="<%= #model.value %>">
The most railsy way to do it is with the form_for construct, though. The guide on this is pretty good.

Maintaining the nested levels while serializing a field generated using fields_for

Thanks to help from people earlier, I am getting a hang of how to serialize a nested hash into a single column. While I was able to generate the form fields at multiple levels and get values of the fields back in to a string, I am unable to retain the different levels for the hash.
My hash looks like the following:
My code looks like:
<% categoryvalue.each do |categoryproperty, categorypropertyvalue| %>
<div>
<%= f.fields_for :categories, categoryproperty do |categoryattribs| %>
<%= categoryattribs.label categoryproperty %> <br/>
<%= categoryattribs.text_field categoryproperty, :value => categorypropertyvalue %> <br/>
<% end %>
</div>
<% end %>
The final hash string in my example takes data for two categories and must look similar to the following string when it gets assigned to :categories should look like the following:
{"0" => {"Active"=>"yes", "totalproducts"=>"100", "segment"=>"Premium"}, "1" => "Active"=>"yes", "totalproducts"=>"190"}}
However, the string is coming in the following form:
{"Active"=>"yes", "totalproducts"=>"100", "segment"=>"Premium", "Active"=>"yes", "totalproducts"=>"190"}
Is there a way to differentiate the attributes of one category from another and have two separate hashes within the main hash? Right now everything gets flattened out to a single level. This is evident in how the id and names for input fields are generated. See the sample below:
<input id="product_categories_Active" name="product[categories][Active]" size="30" type="text" value="%" />
<input id="product_categories_Active" name="product[categories][Active]" size="30" type="text" value="lbs" />
What I actually want is product[categories][0][Active] and product[categories][1][Active].
Any suggestions on how to approach this?
Just in case someone stumbles on this question having a similar problem, I wanted to share the final solution.
You have to use text_field_tag, select_tag etc. instead of the fields_for helper. Use a loop (.each_with_index do |key, index|) to iterate through your hash. Then basically generate a fully qualified name for each field in the format "product_categories_0_active".
I will try to write a tutorial or blog on this once I am out of the woods on my project but I think most people will figure it out from here.
Hope it helps.

how to use an array in form_for?

I've got a user model which contains a field called, lets say, text1. In the users/new form I want to have 4 individual text boxes for this text1. In my model I will take values from these boxes and concatenate them together as comma separated values and store them in the DB. To give you an understanding, this is what I want.
<input type="text" name="user[text1][]" />
<input type="text" name="user[text1][]" />
<input type="text" name="user[text1][]" />
<input type="text" name="user[text1][]" />
How do I get this using form_for helper method? For now please don't worry yourself about the accessor method in the model, that is all taken care of. Thanks a ton.
Add few virtual attributes to your User model
class User < ActiveRecord::Base
attr_accessor :text1_part1
attr_accessor :text1_part2
attr_accessor :text1_part3
attr_accessor :text1_part4
def before_validation
self.text1 = "#{self.text1_part1}#{self.text1_part2}#{self.text1_part3}#{self.text1_part4}"
end
# make sure you fill the correct values for
# the text parts for an existing record.
# This can be done by implementing `after_find` or overriding
# `text1=`.
end
In your view code use the new attributes instead of text1
<% form_for(:user) do |f| %>
#some code
<%= f.text_field :text1_part1>
<%= f.text_field :text1_part2>
<%= f.text_field :text1_part3>
<%= f.text_field :text1_part4>
#some code
<%= f.submit "Save">
<% end %>
The previous answer gives a solution, so I am just providing some background as to why what you are asking for does not work they way you might hope.
You can indeed create a form with multiple input fields of the same name, and that data will be posted. However, when rails receives the post it automatically parameterizes the post data and/or url parameters. It essentially splits on the & and assigns the key/value pairs to the params hash. The outcome of this is that params[:user][:text1] (from your example) will have the value of the last instance of the user[text1] it encountered, since it is simply a value assignment to an existing key. You might want to dig into ActiveRecord multiparameter assignments to get an idea of how datetime attributes work, since they are similar to your use-case.
I am working on something similar and it sounds like maybe serialization is what you are looking for. Unfortunately I don't have my issues solved yet, so I can't provide anything more concrete.

Resources