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.
Related
Right now, I'm using the form_for.select and options_for_select rails helpers to create a select box with the data from the model. However, what I really need is a combobox like the one introduced with HTML5:
<input type=text list=browsers >
<datalist id=browsers >
<option> Google
<option> IE9
</datalist>
Is there a rails helper for creating such elements?
No, but it's quite easy to setup your own form builder helper method to achieve such a result, a simple example would be:
app/form_builders/combobox_form_builder.rb
class ComboboxFormBuilder < ActionView::Helpers::FormBuilder
include ActionView::Context # for nested content_tag
include ActionView::Helpers::FormTagHelper #for sanitize_to_id method access
def combobox_tag(name, options, opts= {})
#template.content_tag(:input, :name => name, :id => sanitize_to_id(name), :type => "text", :list => opts[:list_id]) do
content_tag(:datalist, :id => opts[:list_id]) {options}
end
end
end
After restarting your server you can implement your new combobox using the form builder by specifying a builder argument in your form_for call:
<%= form_for #foo, builder: ComboboxFormBuilder do |f| %>
<%= f.combobox_tag(:browser, options_for_select(["Firefox", "Chrome", "IE9"]), :list_id => "list")%>
<% end %>
Output HTML:
<input type="text" name="browser" list="list" id="browser">
<datalist id="list">
<option value="Firefox">Firefox</option>
<option value="Chrome">Chrome</option>
<option value="IE9">IE9</option>
</datalist>
Keep in mind that both IE & Safari do not offer support for the HTML5 Datalist.
<%= form_for #person do |f| %>
<%= f.label :first_name, "First Name" %>:
<%= f.text_field :first_name, list: 'first-name' %>
<datalist id="first-name">
<% Person.all.each do |person| %>
<option value="<%= person.first_name %>"></option>
<% end %>
</datalist>
<%= f.submit %>
<% end %>
You may also want to do distinct:
<% Person.select(:first_name).distinct.each do |person| %>
Just example from my code:
= form_tag controller:'beer', action: 'create' do
= text_field :beer, :name, list: 'beer-name'
%datalist#beer-name
- Beer.all.each do |beer|
%option{value: beer.name}
I have the following model setup. I have Match and Game. Match has_many :games, and Match accepts_nested_attributes_for :games. :games_attributes is attr_accessible by all members. My problem is, fields_for is not generating the correct form, so Match is not accepting the nested attributes for games. Here is my form code:
<%= form_for [#tournament, match], url: tournament_match_path(#tournament, match) do |f| %>
<%= f.fields_for match.games.last do |builder| %>
<%= builder.hidden_field :winner_id, value: 1 %>
<% end %>
<%= f.submit "Win Game", class: "actionButton activeAction" %>
<% end %>
The code generated by this is:
<form id="edit_match_1" class="edit_match" method="post" action="/tournaments/1/matches/1" accept-charset="UTF-8">
<div style="margin:0;padding:0;display:inline"> <!-- rails stuff here --></div>
<input id="match_game_winner_id" type="hidden" value="1" name="match[game][winner_id]">
<input class="actionButton activeAction" type="submit" value="Win Game" name="commit">
</form>
As you can see, the name of match_game_winner_id is incorrect. The name is match[game][winner_id], but the name should be match[games_attributes][0][winner_id]. How can I solve this problem?
I ended up solving this question on my own, and would like to share my findings to help others in a similar situation. As Andrew said in the comments, fields_for expects a symbol, so in order to get the last game, I need to pass a list of symbols corresponding to the methods that get the last game. My form ended up looking like this:
<%= form_for [#tournament, match], url: tournament_match_path(#tournament, match) do |f| %>
<%= f.fields_for :games, :last do |builder| %>
<%= builder.hidden_field :winner_id, value: 1 %>
<% end %>
<%= f.submit "Win Game", class: "actionButton activeAction" %>
<% end %>
I have a nested attributes form with the following and am just learning how to use nested attributes. One problem I'm having is that the child_index values is not incrementing up. I'm getting 3 fields based upon the build in the controller but they all have 0 or 1 depending on what number is set to.
Any ideas on how to get this to increment?
# in controller: 3.times {#item.assets.build}
<% number = 1 %>
<div id='files'>
<%= f.fields_for :assets, :child_index => number do |asset| %>
<p>
number:<%= number %><br />
<%=asset.label :asset, "File ##{number += 1}" %>
<%= asset.file_field :asset %>
</p>
<% end %>
</div>
<%= f.submit %>
edit:
so all of them in html would have the form like:
item[assets_attributes][0][asset]
rather than the desired:
menu_item[assets_attributes][0][asset]
menu_item[assets_attributes][1][asset]
menu_item[assets_attributes][2][asset]
edit #2:
so looking through the source code, I see the following and am wondering if rails is supposed to be doing some auto-swapping and possibly this isn't happening;
<input id="item_assets_attributes_0_asset" name="item[assets_attributes][0][asset]" type="file" />
<input id="item_assets_attributes_0_id" name="item[assets_attributes][0][id]" type="hidden" value="1" />
Looking through the Rails source it's clear that if you specify :child_index there will be no auto-increment. Whether or not that is the proper behavior is debatable. If you completely omit the :child_index when calling fields_for, you should get the indexes you desire.
To get the correct label for each field you could use some JavaScript. If you don't like that, you could set the file number as an attribute of the Asset class.
class Asset < AR
attr_accessor :file_number
end
# in controller: 3.times {|n| #item.assets.build(:file_number => n) }
<div id='files'>
<%= f.fields_for :assets do |asset| %>
<p>
<%=asset.label :asset, "File ##{asset.file_number}" %>
<%= asset.file_field :asset %>
</p>
<% end %>
</div>
<%= f.submit %>
I have a form that updates two different models using fields_for. The data for the elements inside the fields_for are getting submitted but the ones for the original form_for are missing
Here is the form:
<%= form_for #cart, :remote => true do |cart| %>
<%= fields_for #cart.order, :remote => true do |order| %>
<%= order.select :country, options_for_select(#country_options) %>
<%= order.text_field :zip %>
<% end %>
<%= cart.select :shipping_method,options_for_select(#shipping_options) %>
<% end %>
Here is what is contained in the params in the update action:
{"_method"=>"put", "utf8"=>"\342\234\223", "action"=>"update", "order"=>{"zip"=>"48360", "country"=>"US"}, "id"=>"1", "controller"=>"carts"}
Why is the shipping_method field not appearing in the params?
Here is the generated HTML form:
<form accept-charset="UTF-8" action="/carts/3" class="edit_cart" data-remote="true" id="edit_cart_3" method="post"><div style="margin:0;padding:0;display:inline"><input name="utf8" value="✓" type="hidden"><input name="_method" value="put" type="hidden"></div>
<select id="order_country" name="order[country]"><option value="US">United States</option></select>
<input id="order_zip" name="order[zip]" size="30" value="90001" type="text">
<select id="cart_shipping_method" name="cart[shipping_method]"><option value="FedEx Ground" selected="selected">FedEx Ground: $5.00</option></select>
</form>
I think you should maybe try some thing like this:
<%= cart.select :shipping_method, options_from_collection_for_select(#shipping_options, 'id', 'name') %>
Where 'name' is the parameter you want to display. Otherwise I would suggest inspecting the html of the form. If the select items don't have a value attribute, something is wrong with your erb
options_for_select only work for 2xn arrays
Ok you then I see something else that may cause problems, it think it should be:
<%= cart.fields_for #cart.order, :remote => true do |order| %>
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.