In my Ruby on Rails application I want to allow the adding/editing of a nested model which itself has an associated model.
model Survey
string title
has_many questions
model Question
string question
belongs_to category
model Category
string name
For the sake of argument let's assume that the user should always have to enter a new category when entering a question (I couldn't come up with a better example, sigh).
In my model/survey/edit.html.erb I have a working setup for adding questions and saving them. However when I added the Category model to the picture, I now face the problem that when adding a new Question, there is no corresponding Category name-field displayed. I suspect this is because even though I do call Question.new, I do not call question.category.build - and I have no idea where/how to do that.
My edit.html.erb:
<h1>Editing Survey</h1>
<%= render :partial => 'form' %>
My _form.html.erb:
<% form_for(#survey) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :title %><br />
<%= f.text_field :title %>
</p>
<div id="questions">
<% f.fields_for :questions do |q| %>
<%= render :partial => 'question', :locals => { :pf => q } %>
<% end %>
</div>
<%= add_a_new_child_link("New question", f, :questions) %>
<% end %>
My _question.html.erb:
<div class="question">
<%= pf.label :question %>
<%= pf.text_field :question %>
<% pf.fields_for :category do |c| %>
<p>
<%= c.label :name, "Category:" %>
<%= c.text_field :name %>
</p>
<% end %>
</div>
A quick fix for your situation is to use virtual attributes.
EG, in your Question model:
def category_name=(new_name)
if category then
category.name = new_name
else
category = Category.new(:name => new_name)
end
end
def category_name
return category.name if category
""
end
In your _question, there no need to use nested form. Just add something like:
<%= pf.text_field :category_name %>
I didn't test it, but you probably caught the ideea.
Related
I'm having issues with the implementation of a nested form for a model with has_many through relationship
I have 3 models: Reservation, Table and Collection (Join Model for these two)
In my Reservation Controller I have these two methods:
def new
#reservation = Reservation.new
#tables = Tables.all
#tables.size.times {#reservation.collections.build}
end
and
def reservation_params
params.require(:reservation).permit(:name, collections_attributes: [ :table_id, :units_sold],
tables_attributes: [:units, :title])
end
my form view is as following:
<%= form_for [current_user, #reservation] do |f| %>
<header>
<h1>Make Reservation</h1>
</header>
<%= f.text_field :name, placeholder: "Name" %>
<%= f.fields_for :collections do |builder| %>
<%= render 'nested_form', :f => builder %>
<% end %>
<%= f.submit "Save" %>
<% end %>
and my _nested_form.html.erb file:
<%= f.number_field :units_sold, placeholder: "Units" %>
<%= check_box_tag :table_id %>
My problem is, whenever I save a new entry on the database he assigns the same table_id to all collections association, e.g:
I want to receive the Parameters hash such as:
"collections_attributes"=>{"0"=>{"units_sold"=>"5", "table_id"=>"1"}, "1"=>{"units_sold"=>"6", "table_id"=>"2"}}}
Instead what I'm getting is:
"collections_attributes"=>{"0"=>{"units_sold"=>"5", "table_id"=>"1"}, "1"=>{"units_sold"=>"6", "table_id"=>"1"}}}
How do I fix this for it to give the correct table_id for each collection?
It would be better if you provide your models' snippet that shows their associations.
Anyway, according to your form; One Reservation has many tables through collections.
change:
<%= check_box_tag :table_id %>
to
<% #tables.each do |table| %>
<li>
<%= f.radio_button :table_id %>
<%= f.label :table_id, table.name %>
</li>
<% end %>
By this, you will be able to choose one table from all for each collection using radio button.
Hope this will help!
I as Rails Beginner created an simple demo app to experiment with nested forms.
But somehow my code shows strange byproducts:
My only aim was to create new treatments for patients on the patients show page, and now
it show input fields with yet created treatments and some other crazy stuff!! What did i wrong? My steps so far:
rails new hama
cd hama
rails g scaffold Patient name:string
rails g model Treatment content:string
rake db:migrate
Patient model:
attr_accessible :name, :treatments_attributes
has_many :treatments, :dependent => :destroy
accepts_nested_attributes_for :treatments
Treatment model:
attr_accessible :content
belongs_to :patient
In patient/show:
<b>Name:</b>
<%= #patient.name %>
</p>
<p>
<b>Treatments:</b>
<%= #patient.treatments.each do |treatment| %>
<%= treatment.content %>
<% end %>
</p>
<%= form_for #patient do |f| %>
<%= f.fields_for :treatments do |ff| %>
<%= ff.text_field :content %>
<% end %>
<%= f.fields_for :treatments do |ff| %>
<%= ff.text_field :content %>
<% end %>
<%= f.submit %>
<% end %>
And in Patient controller:
def show
#patient = Patient.find(params[:id])
treatment = #patient.treatments.build
respond_to do |format|
format.html # show.html.erb
format.json { render json: #patient }
end
end
Are you talking about where it shows all the internals of your Treatment objects?
Change this:
<%= #patient.treatments.each do |treatment| %>
to this:
<% #patient.treatments.each do |treatment| %>
Using <%= %>, with the =, means to output the result of that Ruby line on to the page. Without it, it's just code that Ruby runs.
Firstly you should remove the = from this line:
<%= #patient.treatments.each do |treatment| %>
You don't want to display the output of the each. The loop contents provide the output. Just use:
<% #patient.treatments.each do |treatment| %>
All the other fields are output since that's what your code asks for. This part of your code is showing all of the same fields twice:
<%= f.fields_for :treatments do |ff| %>
<%= ff.text_field :content %>
<% end %>
<%= f.fields_for :treatments do |ff| %>
<%= ff.text_field :content %>
<% end %>
If there are two specific input fields for a treatment, then there needs to be two different attributes.
I try to create a furniture object, which is in relation by a has_many_and_belongs_to with stores, this is my model:
class Furniture < ActiveRecord::Base
attr_accessible :area, :description, :name, :size
has_and_belongs_to_many :stores
end
My problem is that I don't know how create a new furniture, because i try to associate furniture with one or more store with check box, but I obtain this error: undefined method merge for #<Store:0x007ff16ae27e40>.
These are my view with form and my controller with new and create action:
View:
<%= form_for #furniture do |f| %>
<%= f.label :name %>
<%= f.text_field :name %> <br><br>
<%= f.label :description %>
<%= f.text_field :description %> <br><br>
<%= f.label :size %>
<%= f.text_field :size %> <br><br>
<% #store.each do |store| %>
<div>
<%= f.check_box :stores, store %>
<%= store.name %>
</div>
<% end %>
<%= f.submit %>
<% end %>
Controller:
def new
#furniture = Furniture.new
#store = Store.order('name ASC')
end
def create
#furniture = Furniture.create(params[:furniture])
redirect_to admins_path
end
How can I solve it?? Have you some suggestion to create a new object with this relation ship??
Thank you very much
EDIT:
I have a join table between furniture and store
The has_and_belongs_to association adds a method collection_singular_ids= that for the current case will be #furniture.store_ids=. According to the docs
The collection_singular_ids= method makes the collection contain only the objects identified by the supplied primary key values, by adding and deleting as appropriate.
So, you can use this idea to add the stores to your furniture. Replace
<% #store.each do |store| %>
<div>
<%= f.check_box :stores, store %>
<%= store.name %>
</div>
<% end %>
with
<% #store.each do |store| %>
<div>
<%= f.check_box :store_ids, {:multiple => true}, store.id, nil %>
<%= store.name %>
</div>
<% end %>
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 %>
How can I make this work in Rails 2.3?
class Magazine < ActiveRecord::Base
has_many :magazinepages
end
class Magazinepage < ActiveRecord::Base
belongs_to :magazine
end
and then in the controller:
def new
#magazine = Magazine.new
#magazinepages = #magazine.magazinepages.build
end
and then the form:
<% form_for(#magazine) do |f| %>
<%= error_messages_for :magazine %>
<%= error_messages_for :magazinepages %>
<fieldset>
<legend><%= t('new_magazine') %></legend>
<p>
<%= f.label :title %>
<%= f.text_field :title %>
</p>
<fieldset>
<legend><%= t('new_magazine_pages') %>
<% f.fields_for :magazinepages do |p| %>
<p>
<%= p.label :name %>
<%= p.text_field :name %>
</p>
<p>
<%= p.file_field :filepath %>
</p>
<% end %>
</fieldset>
<p>
<%= f.submit :save %>
</p>
</fieldset>
<% end %>
problem is, if I want to submit a collection of magazinepages, activerecord complaints because it's expected a model and not an array.
create action:
def create
#magazine = Magazine.new params[:magazine]
#magazine.save ? redirect_to(#magazine) : render(:action => 'new')
end
In magazine:
accepts_nested_attributes_for :magazinepages
Magazine.new(params[:magazine]) will then handle the object hierarchy for you automatically
I'm not 100% sure what you're asking, but if you're trying to instantiate a new magazine, with many magazinepages, you'll need to iterate over each magazine page. Something like this:
def create
#magazine = Magazine.new(params[:magazine])
if params[:magazinepages]
params[:magazinepages].each do |page|
#magazine.magazinepages.build(page)
end
end
# Save the model, do your redirection or rendering invalid model etc
end