So I have a model with this structure:
document {
structure {
id,
...
},
fields {
field {
id,
...
},
value {
...
}
}
}
And I'm trying to put together a form for the fields, where the fields would look like:
<input type="hidden" name="document[fields][0][field][id]" />
<input type="text" name="document[fields][0][value]" />
<input type="hidden" name="document[fields][1][field][id]" />
<input type="text" name="document[fields][1][value]" />
...
This is what I'm currently doing:
<%= document.fields.each_with_index do |df, i| %>
<%= f.fields_for "fields[]", df do |builder| %>
<p>
<%= builder.label df.field.name %>
<%= builder.fields_for :field do |field_builder| %>
<%= field_builder.hidden_field :id, value: df.field.id %>
<% end %>
<%= builder.fields_for :value do |value_builder| %>
<%= render df.field.edit_view, field: df.field, builder: value_builder %>
<% end %>
</p>
<% end %>
<% end %>
But the resulting field names are:
document[fields][field][id]
document[fields][value][value]
In other words, the indices are missing.
Using <%= builder.hidden_field :id, value: df.field.id %> results in the correct format but no index (because the field object has no id yet): document[fields][][id] but that is not an option for value, as there could be multiple fields involved.
Is there a way to do this using the form helpers, or is what I'm doing just too wonky?
Edit: Here are the models involved:
The idea is to have a configurable set of fields for document. The structure determines fields available, and a document_field is a relation between a document and a field with a value contained.
class Field < ApplicationRecord
belongs_to :structure
end
class Document < ApplicationRecord
belongs_to :structure
has_many :document_fields
class DocumentField < ApplicationRecord
belongs_to :document
belongs_to :field
class Structure < ApplicationRecord
has_many :fields
You should configure a nested attributes setup on your models, build those nested relations on the controller, and instantiate an object in you form_for block instead of passing a referencing symbol.
Related
I'm using the Reform gem to make a form object in my current project but the nested fields don't show up in the form. Here's my code:
Shipment Model:
class Shipment < ApplicationRecord
has_one :shipment_detail
end
ShipmentDetail Model:
class ShipmentDetail < ApplicationRecord
belongs_to :shipment
end
Reform Class
class ShipmentForm < Reform::Form
property :shipment_type
property :measure
property :shipment_detail do
property :po_number
property :job_no
end
end
Controller
class ShipmentsController < ApplicationController
def new
#shipment = ShipmentForm.new(Shipment.new)
end
end
Template
<%= form_for #shipment, url: shipments_path, method: :post do |f| %>
<%= f.label :shipment_type %><br />
<%= f.text_field :shipment_type %><br /><br />
<%= f.label :measure %><br />
<%= f.text_field :measure %><br /><br />
<%= f.fields_for :shipment_detail do |d| %>
<%= d.label :po_number %><br />
<%= d.text_field :po_number %><br /><br />
<%= d.label :job_no %>
<%= d.text_field :job_no %><br /><br />
<% end %>
<% end %>
Only fields shipment_type and measure are visible on the form, po_number and job_no are not. What should I do to make them visible?
In Reform you need to use a prepopulator to create a new/blank :shipment_detail section to appear on the form.
http://trailblazer.to/gems/reform/prepopulator.html
prepopulators is when you want to fill out fields (aka. defaults) or add nested forms before rendering.
populators is them code that is run just before validation.
Here is what I used in my code you can get the idea for yours from it:
collection :side_panels, form: SidePanelForm,
prepopulator: ->(options) {
if side_panels.count == 0
self.side_panels << SidePanel.new(sales_order_id: sales_order_id, collection: sales_order.collection)
end
}
Prepopulation must be invoked manually.
Controller#new
#shipment_form = ShipmentForm.new(Shipment.new)
#shipment_form.shipment_detail #=> nil
#shipment_form.prepopulate!
#shipment_form.shipment_detail #=> <nested ShipmentDetailForm #model=<ShipmentDetail ..>>
RE: The edit form
If you create a ShipmentForm in the new action and leave the details section blank and later you want to have these fields appear on the edit action you need to run the prepopulators again on that action too. Just like the new action.
In my code above I have if side_panels.count == 0 line will add in the missing lines on the editing form if there is none there currently.
I have a has_many association between Items and their Components through a table called ComponentItems. ComponentItems contains a column quantity in addition to item_id and component_id. How is it possible to add a number_field to my form that shows the quantity of each component required for an item? The form must contain a number_field for each Item in the database, even if no relationship exists (i.e. #item.component_ids.empty? == true).
class Item < ActiveRecord::Base
has_many :components, through: :component_items
has_many :component_items
end
class Component < Item
has_many :items, through: :component_items
has_many :component_items
end
class ComponentItem < ActiveRecord::Base
belongs_to :item
belongs_to :component
end
I believe I've tried every permutation of model, controller and form_builder possible, except the correct one.
In response to the answer below, here's a form that shows a checkbox and the item code for component items that make up one particular item;
<%= form_for [#item] do |f| %>
<%= f.collection_check_boxes :component_items, Item.active.where.not(sku: #item.sku).sort_by{|n| n.sku}, :id, :sku do |b| %>
<%= b.check_box %> <%= b.label %><br/>
<% end %>
<% end %>
So, ideally I'd replace the check_box with a number_field for quantity. How?
So it seems what I wanted is not so straightforward after all. In the end I opted for using some jQuery for adding extra Components to Items via a separate form. Trying to add/remove components and adjust the quantities was beyond me, so choosing to use separate forms for each user action seemed simpler. It may not be the most user-friendly way of working but it's the best I have.
To edit the quantities I did the following;
<% #item.component_items.each do |x| %>
<%= hidden_field_tag "item[component_items_attributes][][id]", x.id%>
<%= label_tag x.component.sku, x.component.sku.upcase, :class=>"col-md-3 control-label" %>
<%= number_field_tag "item[component_items_attributes][][quantity]", x.quantity, :class=>"col-md-5"%>
<%end %>
and ensured the Item model accepted nested attributes for component_items. Finally, add the nested params array for multiple component_items to items_controller.rb...
def item_params
params.require(:item).permit(
:component_items_attributes =>[:component_id, :item_id, :quantity, :id]
)
end
Note I didn't use fields_for which seemed to generate an extra component_items_attributes array that didn't make any sense at all.
This should work:
#item.components.to_a.sum(&:quantity)
This will throw an error if quantity on some component is nil, so you may try like this to avoid errors:
#item.components.to_a.map(&:quantity).compact.sum
UPDATE
<% #item.component_items.each do |component_item| %>
<%= form_for(component_item) do |f| %>
<div class="field">
<%= f.label :quantity, 'Quantity' %><br />
<%= f.number_field :quantity %>
</div>
<% end %>
<% end %>
Controller: project_sub_types_controller.rb
def new
#svn_repos = ['svn_software','svn_hardware']
#project_sub_type = ProjectSubType.new
#project_sub_type.repositories.build
end
Model: project_sub_type.rb
class ProjectSubType < ActiveRecord::Base
belongs_to :project_type
has_many :repositories, :dependent => :destroy
accepts_nested_attributes_for :repositories
def repositories_attributes=(attributes)
# Process the attributes hash
end
end
View: _form.html.erb
<%= form_for #project_sub_type, :html => {:class => 'project_subtype_form'} do |f| %>
<%= f.label :name, "Project sub type name" %>
<%= f.text_field :name %>
<%= f.fields_for :repositories do |ff| %>
<%= ff.label :select_svn_repositories, "Select SVN repositories" %>
<% #svn_repos.each do |repos| %>
<%= ff.check_box :repos_name, {}, "#{repos}", nil %>
<%= h repos -%>
<% end %>
<%= f.submit "Save"%>
fields_form inspect element :
<input id="project_sub_type_repositories_attributes_0_repos_name" type="checkbox" value="svn_software" name="project_sub_type[repositories_attributes][0][repos_name]">
svn_software
<input id="project_sub_type_repositories_attributes_0_repos_name" type="checkbox" value="svn_hardware" name="project_sub_type[repositories_attributes][0][repos_name]">
svn_hardware
After submitting the form the params = "repositories_attributes"=>{"0"=>{"repos_name"=>"svn_hardware"}}} even after checking both the checkboxes it is using the last selected check_box that is 'svn_hardware'
[EDIT]
Desired Output : My final output should be what the user selects so in this case it should be like this in my after submit params = "repositories_attributes"=>{"0"=>{"repos_name"=>"svn_software"}{"1"=>{"repos_name"=>"svn_hardware"}}
I believe the reason that both have 0 as a prefix is that you have solely specified one repository object, while your array (#svn_repos) contains two items. Because you only build one new object (through #project_sub_type.repositories.build), you create two checkboxes for the same model.
If you, however, were to instead do this:
# controller (inside new method)
#project_sub_type.repositories.build # 1 new object
#project_sub_type.repositories.build # 2 new objects
And then you'd have to iterate over both these objects in your _form partial, and map the names up to the #svn_repos array. I would much prefer this solution though:
# controller (inside new method)
#project_sub_type.repositories.build name: 'svn_software'
#project_sub_type.repositories.build name: 'svn_hardware'
And then iterate over the repositories in the partial, using the name attribute of the model rather than that of an array.
As Nicolay explains, the reason you have a 0 is because you build this #project_sub_type.repositories.build object once. Everything in your code is correct. But if you have to select multiple checkboxes then according to the DOCS
In View: _form.html.erb change
<%= ff.check_box :repos_name, {}, "#{repos}", nil %>
TO
<%= ff.check_box :repos_name, {:multiple => true}, "#{repos}", nil %>
Now you should be able to see the params after submit as below:
=>{"0"=>{"repos_name"=>["svn_software", "svn_hardware"]}}
I have model restaurant and lunch. Where restaurant has_many :lunches and lunch belongs_to :restaurant. Having these relations, I want to show all the available restaurants as checkboxes.
lunch.rb looks like this:
attr_accessible :date, :email, :name, :restaurant_id
belongs_to :restaurant
but I am finding it hard to do it. Can someone explain all the params that are there for in check_box_tag?
I got a snippet of code and modified accordingly, but it doesnt work.
<div class="form_row">
<label for="restaurant_id[]">Restaurants:</label>
<% for restaurant in Restaurant.find(:all) do %>
<br><%= check_box_tag 'restaurant_id[]', restaurant.id %>
<%= restaurant.name.humanize %>
<% end %>
</div>
but when I see the log the restaurant_id is nil.
Here you have the involved form helper doc:
http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-check_box
<%= check_box_tag(:restaurant, restaurant.id) %>
outputs:
<input id="restaurant" name="restaurant" type="checkbox" value=restaurant.id />
The first parameter to check_box_tag, of course, is the name of the input. The second parameter, naturally, is the value of the input. This value will be included in the form data (and be present in params) when the checkbox is checked.
http://guides.rubyonrails.org/form_helpers.html
Source code:
http://apidock.com/rails/ActionView/Helpers/FormHelper/check_box
# File actionpack/lib/action_view/helpers/form_helper.rb, line 929
def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
Tags::CheckBox.new(object_name, method, self, checked_value, unchecked_value, options).render
end
You can't use checkbox tag if you have this association :
restaurant has_many :lunches
lunch belongs_to :restaurant
You should use select tag (singel select) or radio button tag on lunch form because lunch have only one restaurant.
Example using select :
<%= f.label :restaurant_id, 'Restaurant Name' %>
<%= f.collection_select(:restaurant_id, Restaurant.all, :id, :name, :prompt => 'Please select restaurant') %>
I have a parent which has multiple children. I want it so that when I submit my form, a parent is generated in the parent model, and multiple records are created in the child model, one for each child. When I try and submit, I get the following error:
ActiveRecord::AssociationTypeMismatch in ParentsController#create
Child(#) expected, got Array(#)
When I uncomment accepts_nested_attributes_for :children and change f.fields_for :children to f.fields_for :children_attributes, I get a different error:
TypeError in ParentsController#create
can't convert Symbol into Integer
I am at a loss as to what to do. I have checked out the nested model forms railscasts, but they were dealing with generating children fields inside the form, and what I learned from the railscasts didn't seem to work. I am pretty I am doing my builder.text_field :cname's in my form wrong, but I'm not aware of the proper way to do it.
My code:
parent.rb
class Parent < ActiveRecord::Base
has_many :children
#accepts_nested_attributes_for :children
attr_protected :id
child.rb
class Child < ActiveRecord::Base
belongs_to :parent
attr_protected :id
_form.html.erb
<%= form_for #parent, :url => { :action => "create" } do |f| %>
<%= f.text_field :pname %>
<%= f.fields_for :children do |builder| %>
<%= builder.text_field :cname %>
<%= builder.text_field :cname %>
<%= builder.text_field :cname %>
<% end %>
<%= f.submit %>
<% end %>
params content:
{"utf8"=>"✓",
"authenticity_token"=>"FQQ1KdNnxLXolfes9IGiO+aKHJaPCH+2ltDdA0TwF7w=",
"parent"=>{"pname"=>"Heman",
"child"=>{"cname"=>""}},
"commit"=>"Create"}
The problem here is that the generated form in HTML for the children are using the same "place" (same pair key/value) in the params Hash (using the params[:parent][:child][:cname] pair). This is why there is only one param 'name' in the 'child' node in the Params hash.
To avoid that, you can use an array for the input's name:
<input type="text" name="child[][cname]" />
<input type="text" name="child[][cname]" />
The params, when this submitted, will look like this:
params: {
child: [ { cname: 'blabla' }, { cname: 'bonjour' } ]
}
To get the desired result, in your case:
<%= form_for #parent, :url => { :action => "create" } do |f| %>
<%= f.text_field :pname %>
<%= text_field_tag "parent[children][][cname]" %>
<%= text_field_tag "parent[children][][cname]" %>
<%= text_field_tag "parent[children][][cname]" %>
<%= f.submit %>
<% end %>
Should produce something like this:
{
"utf8"=>"✓",
"authenticity_token"=>"FQQ1KdNnxLXolfes9IGiO+aKHJaPCH+2ltDdA0TwF7w=",
"parent"=> {
"pname"=>"Heman",
"children"=> [
{ "cname"=>"SisiSenior" },
{ "cname"=>"Bonjor" },
{ "cname"=>"Blabla" }
]
},
"commit"=>"Create"}
So in your controller, you could use something like this:
#ParentsController
def create
children_attributes = params[:parent].delete(:children) # takes off the attributes of the children
#parent = Parent.create(params[:parent])
children_attributes.each do |child_attributes|
child = #parent.children.create(child_attributes)
end
end