I need to create a multi-edit form in rails, like so:
<form>
<input type='text' name='input1'></input>
<input type='text' name='input2'></input>
<input type='text' name='input3'></input>
<input type='text' name='input4'></input>
<input type='text' name='input5'></input>
<br>
<input type='text' name='input1'></input>
<input type='text' name='input2'></input>
<input type='text' name='input3'></input>
<input type='text' name='input4'></input>
<input type='text' name='input5'></input>
<br>
<input type='text' name='input1'></input>
<input type='text' name='input2'></input>
<input type='text' name='input3'></input>
<input type='text' name='input4'></input>
<input type='text' name='input5'></input>
<br>
... and so on, then the "<submit>" button will be at the very end. One click of the submit button at the end should collect all the values and parse them in the controller.
I just need to know how to generate the multi-edit form in the view. Also, each row is unique; I'd also need to know how to assign a unique identifier to each of the input tags I guess; I do have a unique ID value I could use.
This is trivial to accomplish, but we need more information. How are these fields related to your models? Is this one model with many fields, many instances of a model or something else?
What you want to do in this situation is use a form builder. It will generate input fields according to a naming convention that will be parsed into a much more useful format when it gets to the controller. Since I have no information about your models, I will use a hypothetical example:
class Post < ActiveRecord::Base
attr_accessible :title, :body, :author, :published_at
end
Create the form using the form_for helper. It will give you a formbuilder object to create the input fields.
<% form_for :post do |f| -%>
<p>
<%= f.label :title %>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :body %>
<%= f.text_area :body %>
</p>
<p>
<%= f.label :author %>
<%= f.text_field :author %>
</p>
<p>
<%= f.label :published_at %>
<%= f.datetime_select :published_at %>
</p>
<% end -%>
The key benefit of using helpers is the name attribute of the inputs it generates. Since body belongs to a form for post it will be given the name attribute post[body]. These attributes will be parsed into the following hash:
:post => {
:title => "This is the title",
:body => "this is the body",
:author => "John Doe",
:published_at => "Mon Nov 15 2010 19:23:40 GMT-0600 (CST)"
}
This means you don't need to manually copy fields into a model. You can just pass it directly to the Model#new method:
#post = Post.new(params[:post])
and then do your validation checks. This convention becomes indispensable when you start nesting models inside one another.
See here for a more thorough guide to form helpers.
Related
Rails 3.2
This is my second attempt at solving this problem.
I inherited an application for processing customer tickets. The main ticket view, is large and complicated, so it's split up into multiple sections.
I am trying to add a new section to enter customers information.
I have a scaffolding for CustomerInfo, with a controller and a model, in the normal locations:
controllers/customer_infos_controller.rb and models/customer_info.rb.
I put the views under views/tickets/sections
In my views/tickets/show.html.slim, I have:
- #customer_info = customer_info #ticket
h4.form-header Customer Information
.form-section.attachments
- if #customer_info.nil?
= render partial: 'tickets/sections/customer_info', locals: {ticket: #ticket }
In my , views/tickets/sections/_customer_info.html.slim, I have:
= form_for(CustomerInfo.new, url: customer_info_path) do |f|
- f.hidden_field :ticket_id, :value => ticket.id
.form-horizontal-column.customer-info
.form-group
= f.label :first
= f.text_field :first, maxlength: 50
.form-group
= f.label :last
= f.text_field :last, maxlength: 50
.actions = f.submit 'Save'
.clear
In my routes.rb, I have:
post '/customer_infos' => 'customer_infos#create', as: 'customer_info'
When I run the application, the form displays properly. But, when I enter the first name, and last name, the ticket gets updated.
Looking at the log file, I see that when the Save button in the Customer Information section, I see:
Processing by TicketsController#update as HTML
In the log, I also saw the params:
{
"ticket":
{
.......................
},
"customer_info":
{
"first":"John",
"last":"Doe"
},
"commit":"Save"
}
rake routes gives me:
customer_info POST /customer_infos(.:format) customer_infos#create
This is the HTML:
<div class="form-horizontal-column customer-info">
<div class="form-group">
<label for="customer_info_first">First *</label>
<input id="customer_info_first" type="text" size="50" name="customer_info[first]" maxlength="50">
</div>
<div class="form-group">
<label for="customer_info_last">Post tax total *</label>
<input id="customer_info_last" type="text" size="50" name="customer_info[last]" maxlength="50">
</div>
<div class="actions">
<input type="submit" value="Save" name="commit">
</div>
</div>
This is the portion:
<form accept-charset="UTF-8" action="http://xxx.xxxx.xxxx.com/admin/tickets/159384" class="form form-horizontal tickets-form context-form" data-admin="true" enctype="multipart/form-data" id="edit_ticket_159384" method="post">
So, there is no customer_info portion, with the proper action and method.
Why is the tickets controller update method is invoked, instead of the CustomerInfo create method? I also don't see the ticket_id param in the params.
I'm using Rails 4.2.3 with MySql 5.5.37. I recently change the name and type of my column and now when I submit my Rails form that form field, "selection_id," is not getting captured. Below is my Rails form ...
<div class="field">
<%= f.label :day %><br>
<%= f.hidden_field :day, :validate => true %>
<div id="datepicker"></div>
</div>
<div class="field">
<%= f.label :selection_id %><br>
<div class="styled-select"><%= collection_select(:user_selection, :selection_id, #selections, :id, :description, {:prompt => true}) %></div>
</div>
<div class="field">
<%= f.label :total %><br>
<%= f.text_field :total, :size => 4, :validate => true %>
</div>
And here is the HTML that gets output to the browser ...
<div class="field">
<label for="user_selection_day">Day</label><br>
<input validate="true" type="hidden" value="02/07/2016" name="user_selection[day]" id="user_selection_day" />
<div id="datepicker"></div>
</div>
<div class="field">
<div class="field_with_errors"><label for="user_selection_selection_id">selection</label></div><br>
<div class="styled-select"><div class="field_with_errors"><select name="user_selection[selection_id]" id="user_selection_selection_id"><option value="">Please select</option>
<option value="3">option 1</option>
<option value="4">option 2</option></select></div></div>
</div>
<div class="field">
<label for="user_selection_total">Total</label><br>
<input size="4" validate="true" type="text" value="1" name="user_selection[total]" id="user_selection_total" />
</div>
I can see this data getting submitted in Chrome ...
user_selection[day]:02/19/2016
user_selection[selection_id]:3
user_selection[total]:9
but on my controller side, when I try and output the params, only two of the three are printing out. This "puts"
def create
#current_user = User.find(session["user_id"])
...
puts user_selection_params
prints
{"day"=>"02/19/2016", "total"=>"9"}
Why is this other field getting lost and how can I fix it?
You are having this problem because #object_id is a method defined on Object, the class that all Ruby objects extend. In other words, it's reserved by Ruby itself and can't be used when naming database fields / model attributes. You're going to have to rename that column/association to something else.
As a side note, it's not idiomatic Ruby to include the word "Object" in your class name. Arguably the class should just be named User, instead of UserObject.
Reference: http://ruby-doc.org/core-2.3.0/Object.html#method-i-object_id
Please don't use object_id in your model.
There should be no column named object_id in the database.
object_id is a default methods that all (except BasicObject in Ruby 1.9) objects have ...
(see docs).
I have a nested form that is based off of the example in this rails cast:
http://railscasts.com/episodes/196-nested-model-form-revised
For my application, I'm trying to let people upload multiple files in a nested form. Each step can have many design files, and steps accept nested attributes for design files:
Step.rb:
class Step < ActiveRecord::Base
attr_accessible :design_files_attributes
has_many :design_files
accepts_nested_attributes_for :design_files, :allow_destroy => :true
...
end
DesignFile.rb:
class DesignFile < ActiveRecord::Base
belongs_to :step
...
end
I'm running into issues when I try to add an additional file after another file has already been added. This is what the form looks like in html:
<div class="upload_new_files">
<fieldset class="design_file_upload_fieldset">
<label for="step_design_files_attributes_0_design_file_path">test.pdf</label>
<input id="step_design_files_attributes_0__destroy" name="step[design_files_attributes][0][_destroy]" type="hidden" value="false">
<i class="icon-trash"></i> Remove File
<input id="step_design_files_attributes_0_step_id" name="step[design_files_attributes][0][step_id]" type="hidden" value="451">
<input id="step_design_files_attributes_0_project_id" name="step[design_files_attributes][0][project_id]" type="hidden" value="120">
<input id="step_design_files_attributes_0_user_id" name="step[design_files_attributes][0][user_id]" type="hidden" value="15">
<hr>
</fieldset>
<fieldset class="design_file_upload_fieldset">
<input id="step_design_files_attributes_70265135732560_design_file_path" name="step[design_files_attributes][70265135732560][design_file_path]" onchange="javascript: addField(this);" type="file" style="display: none;">
<input id="step_design_files_attributes_70265135732560__destroy" name="step[design_files_attributes][70265135732560][_destroy]" type="hidden" value="false">
<i class="icon-trash"></i> Remove File
<input id="step_design_files_attributes_70265135732560_step_id" name="step[design_files_attributes][70265135732560][step_id]" type="hidden" value="446">
<input id="step_design_files_attributes_70265135732560_project_id" name="step[design_files_attributes][70265135732560][project_id]" type="hidden" value="120">
<input id="step_design_files_attributes_70265135732560_user_id" name="step[design_files_attributes][70265135732560][user_id]" type="hidden" value="15">
<hr>
</fieldset>
</div>
But when I submit the form, the design_files_attributes aren't in the parameters. I'm expecting something like this:
Parameters: {"step"=>{ "design_files_attributes"=>{"0"=>{"id"=>"17" ...}}, "70265135732560"=>{...}...}
But, instead, I'm getting this:
Parameters: {"step"=> {"design_files_attributes"=>{"0"=>{"id"=>"17" ...}}
What isn't my new entry being submitted in the form parameters?
Edit
Here is the full params that are being submitted to the controller; I didn't include it above because it includes a lot of other things, but I have it here in case it's useful:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"...=", "step"=>{"name"=>"New Design File", "description"=>"", "question_attributes"=>{"description"=>"", "decision_attributes"=>{"description"=>"", "question_id"=>""}}, "design_files_attributes"=>{"0"=>{"_destroy"=>"false", "step_id"=>"451", "project_id"=>"120", "user_id"=>"15", "id"=>"17"}}, "last"=>"0", "published_on_date"=>"02/04/2014", "published_on_time"=>"05:02 PM", "timeZone"=>"EST"}, "commit"=>"Update Step", "project_id"=>"120", "id"=>"3"}
_step_form.html.erb
<%= semantic_form_for [#project, #step], :html => {:multipart => true} do |f| %>
<%= f.fields_for :design_files, multipart: true do |design_file_form| %>
<%= render 'design_file_fields', f: design_file_form %>
<% end %>
<p class="add_design_file" style="display:none;"><%= link_to_add_fields raw("<icon class='icon-plus-sign' style='color:green;'></icon> Add Another Design File"), f, :design_files %></p>
<% end %>
_design_file_fields.html.erb
<fieldset class="design_file_upload_fieldset">
<% if f.object.new_record? %>
<%= f.file_field :design_file_path, :onChange=>"javascript: addField(this);" %>
<% else %>
<%= f.label :design_file_path, f.object.design_file_path.to_s.split('/').pop() %>
<% end %>
<%= f.hidden_field :_destroy %>
<%= link_to raw('<i class="icon-trash"></i> Remove File'), "#", class: "remove_fields" %>
<%= f.hidden_field :step_id, :value => #step.id %>
<%= f.hidden_field :project_id, :value => #project.id %>
<%= f.hidden_field :user_id, :value => current_user.id %>
<hr>
</fieldset>
steps.js
// add a new design file field to the form
function addField(input) {
console.log('add design field input');
// remove the filefield of recently uploaded file and replace with existing file styling
var filename = $(input).val().split('\\').pop();
$(input).parent('fieldset').prepend('<label>'+filename+'</label>');
$(input).hide();
$('.add_fields').click();
}
application_helper.rb
def link_to_add_fields(name, f, association)
new_object = f.object.send(association).klass.new
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize + "_fields", f: builder)
end
link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end
Wow, the answer ended up being totally unrelated to what I had posted! The issue was with the form itself, which had an extra <% end %> element, causing the design files fields_for not being incorporated into the form. I'll leave this response in case it's useful for anyone else.
I would like to use the form_for helper multiple times for the same model in the same page. But the input fields use the same ID attribute (in the HTML), so clicking on the label of a field in another form will select the same input in the first form.
Is there a solution besides settings all attributes manually via :for => "title_#{item.id}" and :id => "title_#{item.id}"?
Using Rails 3.0.9
You can use :namespace => 'some_unique_prefix' option. In contrast to :index, this will not change the value used in the name attribute.
It's also possible to use an array, e.g. when you have nested forms or different forms that happen to have some fields in common: :namespace => [#product.id, tag.id] or :namespace => [:product, #product.id]
I found the answer myself, one can pass a :index option to form_for. That string will be used in the id and for attributes:
<%= form_for #person, :index => #person.id do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.submit %>
<% end %>
will parse
<form accept-charset="UTF-8" action="/person/11" class="edit_person" id="edit_person_11" method="post">
<!-- Hidden div for csrf removed -->
<label for="person_11_name">Name</label>
<input id="person_11_name" name="person[11][name]" size="30" type="text" />
<input name="commit" type="submit" value="Update Person" />
</form>
Notice it'll change the name of the inputs as well.
I believe you can add this param:
:html => { :id => 'id_i_want' }
Notice the following result when I submit blank :title and :description fields
Error div tags only wraps the title text field and not the description text area. http://www.freeimagehosting.net/uploads/c14b4a2d74.png
The validations are in the controller:
class Question < ActiveRecord::Base
validates_presence_of :title
validates_presence_of :description
And, the form is generated with those names:
-form_for(#question) do |f|
= f.error_messages
= f.label :title
= f.text_field :title, :size => 50, :onchange => remote_function(:url => {:action => :display_tag_suggestions}, :with => 'Form.Element.serialize(this)')
#suggestions
= f.label :description
= f.text_area :description
...
But, for some reason, only :title gets wrapped in the error div tags:
<form action="/questions" class="new_question" id="new_question" method="post">
<div style="margin:0;padding:0"><input name="authenticity_token" type="hidden" value="6HQaiu1D0gBQcKw2pLeZP6Jvn0FSClPD5Sk9HwegzPg=" /></div>
<div class="errorExplanation" id="errorExplanation">
<h2>2 errors prohibited this question from being saved</h2>
<p>There were problems with the following fields:</p>
<ul>
<li>Title can't be blank</li>
<li>Description can't be blank</li>
</ul>
</div>
<label for="question_title">Title</label>
<div class="fieldWithErrors"><input id="question_title" name="question[title]" onchange="new Ajax.Request('/questions/display_tag_suggestions', {asynchronous:true, evalScripts:true, parameters:Form.Element.serialize(this) + '&authenticity_token=' + encodeURIComponent('6HQaiu1D0gBQcKw2pLeZP6Jvn0FSClPD5Sk9HwegzPg=')})" size="50" type="text" value="" /></div>
<label for="question_description">Description</label>
<textarea cols="40" id="question_description" name="question[description]" rows="20"></textarea>
...
I don't think that behavior is expected. The problem most people have is that it's wrapping things with divs, which won't display properly. My problem is that fields aren't being wrapped with divs to begin with!
I haven't made any (conscious) changes to how errors are handled, so I'm not sure why it's not working properly.
What version of Haml are you using? Some older versions had compatibility trouble with error wrapping.