I have a model that has an arbitrary number of children entities. For simplicity lets call the entities Orders and Items. I would like to have a create Orders form where I input the order information, as well as add as many items as I want. If I click the "Add another item" button, a new set of form elements will be added to input the new data, amounts, etc..
I could hack this out in pure javascript, but I'm pretty sure there has to be a more magical, railsish way to do it, maybe with a partial view or something. I'm just a little too new to rails to know what it is.
What is the best way to dynamically add the new form elements, and then to access them in the create controller?
Can't beat this Railscasts.com tutorial provided by Ryan Bates.
Episode 196: Nested Model Form, pt. 1
Here's an example that works with just a single level of nesting
Models
models/company.rb
class Company < ActiveRecord::Base
has_many :people, :dependent => :destroy
accepts_nested_attributes_for :people, :allow_destroy => true
end
models/person.rb
class person < ActiveRecord::Base
belongs_to :company
end
Controllers
companies_controller.rb
def new
#company = Company.new
3.times { person = #company.people.build }
end
Views
views/companies/_form.html.erb
<% form_for #company do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<%= f.fields_for :people do |builder| %>
<%= render "people_fields", :f => builder %>
<% end %>
<p><%= f.submit "Submit" %></p>
<% end %>
views/companies/_people_fields.html.erb
<p>
<%= f.label :name, "Person" %>
<%= f.text_field :name %>
<%= f.check_box :_destroy %>
<%= f.label :_destroy, "Remove" %>
</p>
Related
I'm trying to create a database with two types of data.
1) Apartment buildings with their own attributes (ex. address)
2) Units (belonging to buildings) with their own attributes (ex. price, size)
I was wondering if I could have a page with a form for both databases?
Ex. A form to create a new building, and add the new unit information directly on the page.
<%= form_for(#building) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :address %>
<%= f.text_field :address %>
<%= f.label :contact %>
<%= f.email_field :contact %>
<br>
<%= form_for(#unit) do |f| %>
<%= f.label :bedrooms %>
<%= f.text_field :bedrooms %>
<%= f.label :price %>
<%= f.text_field :price %>
<%= f.label :building_id %>
<%= f.text_field :building_id %>
<br>
<%= f.submit "Create building", class: "btn btn-large btn-primary" %>
<% end %>
<% end %>
But i understand this only creates the new building, not the units associated with them.
You cannot have a form inside a form - you will have to use what's called nested attributes.
I assume a building has many units, and a unit belongs to a building. Then your code needs to be as follows:
Building
class Building < ActiveRecord::Base
attr_accessible :units_attributes
has_many :units, :dependent => :destroy
accepts_nested_attributes_for :units, :reject_if => lambda { |a| a[:bedrooms].blank? }, :allow_destroy => true
end
Unit
class Unit < ActiveRecord::Base
belongs_to :building
end
Form
Note the fields_for form helper (pretty self explanatory):
Put the following inside your form:
<%= f.fields_for :units do |builder| %>
<%= builder.label :bedrooms %>
<%= builder.text_field :bedrooms %>
# etc
<% end %>
When you reload the page, you will see that your form probably doesn't contain any fields for units just yet. This is because the building instance inside the form does not have any units yet - do the following inside your controller to see unit fields:
3.times { #building.units.build }
Now you should see three sets of unit fields inside the form. If you fill them in and submit the form, they will be saved as children of that building - if you leave them blank, they won't. :reject_if => lambda { |a| a[:bedrooms].blank? } inside the building model takes care of that: If the bedrooms field is left blank, the unit will not be saved.
This is all you need!
If this was a bit too fast, just watch this railscast.
Also, check out this awesome gem called nested_forms, which gives you links to add and remove nested form fields on the fly (allowing you to get rid of that cumbersome extra line in your controller).
I am n00b as rails is concerned. I am trying yo create a single multimodel form in my first rails3 project. Details are given below:
class Item < ActiveRecord::Base
# attr_accessible :title, :body
has_many :item_reviews, :dependent => :destroy
accepts_nested_attributes_for :item_reviews
end
and
class ItemReview < ActiveRecord::Base
# attr_accessible :title, :body
belongs_to :item
end
So as clear, an item can have multiple reviews but when I am creating an item, I want at least 1 review for it. So I want to get item and first review in single form while item creation.
I am using following view:
<%provide(:title,'Create')%>
<h1> Add an Item review</h1>
<div class="row">
<div class="span6 offset3">
<%= form_for (#item) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<% f.fields_for :item_reviews, #item.item_reviews do |ff| %>
<%= ff.label :shop_address %>
<%= ff.text_field :shop_address %>
<% end %>
<%= f.submit "Submit", class: "btn btn-large btn-primary" %>
<% end %>
</div>
</div>
<% f.fields_for :item_reviews, #item.item_reviews do |ff| %> will not work because there is not item_review associated with #item currently (#item = Item.new) Until I save #item, I can't create new item_review. What should I do in that case.
I know one possibility is model independent form but can't I use something above to make life easy.
PS: I am using bootstrap, just in case if that helps.
There is some way to achieve an instance with item reviews. The key is to create an instance with some of nested instances without actual saving
#item = Item.new
#item.item_reviews.build
and then in your form
form_for #item do |f|
...
f.fields_for :item_reviews do |ff|
with this code an instance of review is present and you can render form
I am trying to understand how to make a nested form of my models but I am struggeling with understanding how and what I need to do it. I have been reading the Rails documentation and looked at the railscast but they just mention the accepts_nested_attributes_for method etc without explaining. Can someone please help?
Per API of Rails it's said:
Nested attributes allow you to save attributes on associated records through the parent...
Example: it shows how we can manage posts through Member, fields_for is used to manage associated fields in a form, passing it the name of the associated model and then loop through all of the associated post records and create a form builder for each of them.
#controller
def new
#member = Member.new
end
#model
class Post < ActiveRecord::Base
belongs_to :member
end
class Member < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts
end
#form
<%= form_for #member do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<%= f.fields_for :posts do |builder| %>
<p>
<%= builder.label :account %><br />
<%= builder.text_area :account %>
</p>
<% end %>
<p><%= f.submit "Submit" %></p>
<% end %>
Rails API: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
I am using Rails 3.1 and am working on a discussion forum. I have a model called Topic, each of which has many Posts. When the user makes a new topic, they should also make the first Post as well. However, I am not sure how I can do this in the same form. Here is my code:
<%= form_for #topic do |f| %>
<p>
<%= f.label :title, "Title" %><br />
<%= f.text_field :title %>
</p>
<%= f.fields_for :post do |ff| %>
<p>
<%= ff.label :body, "Body" %><br />
<%= ff.text_area :body %>
</p>
<% end %>
<p>
<%= f.submit "Create Topic" %>
</p>
<% end %>
class Topic < ActiveRecord::Base
has_many :posts, :dependent => :destroy
accepts_nested_attributes_for :posts
validates_presence_of :title
end
class Post < ActiveRecord::Base
belongs_to :topic
validates_presence_of :body
end
... but this doesn't seem to be working. Any ideas?
Thanks!
#Pablo's answer seems to have everything you need. But to be more specific...
First change this line in your view from
<%= f.fields_for :post do |ff| %>
to this
<%= f.fields_for :posts do |ff| %> # :posts instead of :post
Then in your Topic controller add this
def new
#topic = Topic.new
#topic.posts.build
end
That should get you going.
A very good explanation from Ryan Bates here and here
For your particular case: you are using a model (:post), instead of an association (:posts) when you call fields_for.
Also check for the proper use of <%= ... %>. In rails 3.x the bahaviour of the construct changed. Block helpers (form_for, fields_for, etc) dont need it, and inline helpers (text_field, text_area, etc) do need it.
Update: answered here.
There are lots of good questions and answers here and on the interweb about getting nested_form, collection_select, accepts_nested_attributes_for and fields_for to play nicely together, but I'm still stumped. Thanks in advance if you can help me.
Aim: To generate a new isbn record. An isbn can have many contributors. I am successfully using the ryanb nested_form gem to dynamically produce new contributor fields on a form, as required. One of these fields uses a collection_select drop down of all the name records in Contributor. When the new record is created, the many contributor ids need to be written to the join table (contributors_isbns).
I have got bits of this working, but only to the point where I can save a single contributor ID to the new record in the isbns table. I can't seem to get anywhere in writing any data to the join table.
I have three models. Contributors and Isbns have a many to many relationship, which I've done using has_many :through. An isbn can have many contributors, and a contributor can have many isbns. They are joined via contributors_isbn.
isbn.rb
attr_accessible :contributor_id
has_many :contributors, :through => :contributors_isbns
has_many :contributors_isbns
accepts_nested_attributes_for :contributors
accepts_nested_attributes_for :contributors_isbns
contributor.rb
attr_accessible :isbn_id
has_many :contributors_isbns
has_many :isbns, :through => :contributors_isbns
accepts_nested_attributes_for :isbns
contributors_isbn.rb
class ContributorsIsbn
attr_accessible :isbn_id, :contributor_id
belongs_to :isbn
belongs_to :contributor
accepts_nested_attributes_for :contributors
In the isbns controller:
def new
#isbn = Isbn.new
#title = "Create new ISBN"
1.times {#isbn.contributors.build}
#isbn.contributors_isbns.build.build_contributor
end
(obviously I can't make my mind up on which build method to use.)
In the isbns new.html.erb view:
<%= nested_form_for #isbn, :validate => false do |f| %>
<h1>Create new ISBN</h1>
<%= render 'shared/error_messages', :object => f.object %>
<%= render 'fields', :f => f %>
<div class="actions">
<%= f.submit "Create" %>
</div>
<% end %>
In the _fields partial, a version with a very plain text_field:
<%= field_set_tag 'Contributor' do %>
<%= f.link_to_add "Add Additional Contributor", :contributors %>
<li>
<%= f.label 'Contributor Sequence Number' %>
<%= f.text_field :descriptivedetail_contributor_sequencenumber%>
</li>
<%= f.fields_for :contributors_isbns, :validate => false do |contrib| %>
<li>
<%= contrib.label :id, 'contributors_isbns id' %>
<%= contrib.text_field :id %>
</li>
<% end %>
<li>
<%= f.label 'Contributor Role' %>
<%= f.text_field :descriptivedetail_contributor_contributorrole %>
</li>
<% end %>
And here, a fancier version which doesn't work either:
<%= f.fields_for :contributors_isbns, :validate => false do |contributors| %>
<li>
<%= f.label :personnameinverted, 'Contributor Name' %>
<%= f.collection_select(:contributor_id, Contributor.all, :id, :personnameinverted ) %>
</li>
<% end %>
This code uses the answer from here. Both result in a 'Missing block" error on the nested_form_for #isbn line.
Thanks so much again in advance.
Update: here is some info about the nested_form gem which might come in handy for looking at this sort of problem. And here's a [2009 but still relevant post][4] on accepts_nested_attributes_for.
Update 2: well, here's a thing. I've been poking around on a cut-down version of this in two different models, not using collection_select or has_many through, but just on a simple belongs_to / has_many association. The parent model is Contract and the child model is Istc. I couldn't even create a record through the nested form. However, after looking in the stack and googling the error message "Warning. Can't mass-assign protected attributes" I've just added :istcs_attributes to my :attr_accessible line and now I can add records. A rather crucial bit missing, and a case of RTFM, as it's right there in the gem readme. I'll update later to see if this works on the more complicated has_many through association.
Update 4: [Here][5] is another useful post about how to deal with a nil record error message.
Update 5: Slight detour - When I dynamically added a new set of fields to the form, one one of the child records was being created. Duh - I had the "Add" link inside the child forms area. Here's the before:
<%= f.fields_for :istcs do |istc_form| %>
<h4> Istc</h4>
<%= istc_form.label "istc name" %>
<%= istc_form.text_field :title_title_text %>
<%= istc_form.link_to_remove "[-] Remove this istc"%>
<%= f.link_to_add "[+] Add an istc", :istcs %>
<% end %>
and here's the after:
<%= f.fields_for :istcs do |istc_form| %>
<h4> Istc</h4>
<%= istc_form.label "istc name" %>
<%= istc_form.text_field :title_title_text %>
<%= istc_form.link_to_remove "[-] Remove this istc"%>
<% end %>
<%= f.link_to_add "[+] Add an istc", :istcs %>
Update 6, post-answer:
Oh noes. The collection_select isn't working. It's adding new contributor records, not using an existing one from the contributor model. Someone else had this problem too. Any ideas?
Huzzah! Here's the code which makes all this work. Bit verbose but didn't want to leave anything out. My main learnings:
you need to make the child attributes attr_accessible in the parent model
you need to make the parent and child ids attr_accessible in the join table model
it makes life easier if you build at least one child instance in the parent controller.
contributor.rb model
class Contributor < ActiveRecord::Base
attr_accessible #nothing relevant
has_many :contributors_isbns
has_many :isbns, :through => :contributors_isbns
isbn.rb model
class Isbn < ActiveRecord::Base
attr_accessible :contributors_attributes, :contributor_id, :istc_id #etc
belongs_to :istc
has_many :contributors, :through => :contributors_isbns
has_many :contributors_isbns
accepts_nested_attributes_for :contributors #if you omit this you get a missing block error
contributors_isbn model
class ContributorsIsbn < ActiveRecord::Base
belongs_to :isbn
belongs_to :contributor
attr_accessible :isbn_id, :contributor_id
isbn controller
def new
#isbn = Isbn.new
#title = "Create new ISBN"
1.times {#isbn.contributors.build}
end
new.html.erb
<td class="main">
<%= nested_form_for #isbn, :validate => false do |f| %>
<h1>Create new ISBN</h1>
<%= render 'shared/error_messages', :object => f.object %>
<%= render 'fields', :f => f %>
<div class="actions">
<%= f.submit "Create" %>
</div>
<% end %>
_fields.html.erb
<%= field_set_tag 'Identifier Details' do %>
<li>
<%= f.label 'Title prefix' %>
<%= f.text_field :descriptivedetail_titledetail_titleelement_titleprefix %>
</li>
<li>
<%= f.label 'Title without prefix' %>
<%= f.text_field :descriptivedetail_titledetail_titleelement_titlewithoutprefix %>
</li>
<li>
<%= f.label 'ISTC' %>
<%= f.collection_select(:istc_id, Istc.all, :id, :title_title_text, :prompt => true) %>
</li>
<% end %>
<%= field_set_tag 'Contributor' do %>
<li>
<%= f.label 'Contributor Sequence Number' %>
<%= f.text_field :descriptivedetail_contributor_sequencenumber%>
</li>
<%= f.fields_for :contributors, :validate => false do |contributor_form| %>
<li>
<%= contributor_form.label :personnameinverted, 'Contributor Name' %>
<%= contributor_form.collection_select(:isbn_id, Contributor.all, :id, :personnameinverted ) %>
</li>
<%= contributor_form.link_to_remove "[-] Remove this contributor"%>
<% end %>
<%= f.link_to_add "[+] Add a contributor", :contributors %>
<li>
<%= f.label 'Contributor Role' %>
<%= f.text_field :descriptivedetail_contributor_contributorrole %>
</li>
<% end %>