Duplicate form fields when using fields_for - ruby-on-rails

I am trying to create a nested form to handle a has_many :through relationship and getting duplicated fields being rendered.
Models
Company
has_many :provides
has_many :services, :through => :provides
accepts_nested_attributes_for :services, :provides
attr_accessible :service_ids
Provide
belongs_to :company
belongs_to :service
Service
has_many :provides
has_many :companies, :through => :provides
has_many :portfolio_items
acts_as_nested_set
Controller
Settings/Services
def index
#company_services = #company.services
#service_list = Service.where("parent_id IS NULL")
end
def show
#user = current_user
# Find features for supplier based users
unless #company.blank?
#my_sectors = #company.sectors
end
end
def update
if params[:company].nil?
#company.service_ids = nil
end
respond_to do |format|
if #company.update_attributes(params[:company])
format.html { redirect_to settings_path, :notice => "Services successfully updated" }
else
format.html { render :index }
end
end
end
Views
Form -
<%= form_for #company, :url => settings_service_path(#company), :method => :put do |f| %>
<div>
<ul>
<% #service_list.each do |item| %>
<%= f.fields_for :provides do |p| %>
<%= p.fields_for :services do |s| %>
<%= render :partial => "subs", :locals => {:subs => s, :service => item, :f => f, :p => p } %>
<% end %>
<% end %>
<% end %>
</ul>
<%= submit_tag("Update") %>
<% end %>
_subs.html.erb
<li>
<%= check_box_tag :service_ids, service.id, #company.services.include?(service), :name => "company[service_ids][]", :class => "checkbox" %>
<%= label_tag service.id, service.service_name %>
<% unless service.children.blank? %>
<ul>
<%= render :partial => "subs", :collection => service.children %>
</ul>
<% end %>
</li>
I know the fields_for is causing the duplication but I don't know why?
Could anybody clarify?

You need to write,
<% #service_list.each do |item| %>
<%= f.fields_for :provides do |p| %>
<%= p.fields_for :services, item do |s| %>
this way fields_for will iterate only for matching service each time.

Related

How to assign strong parameters of deeply-nested attibutes in Rails4?

I'm trying nested objects in a Rails4-app according to this Railscast. The model survey has_many :questions, the model questions in turn has_many :answers, the model answers belongs_to :questions and questions belongs_to :survey.
Now, the models were at first completely separated from one another which worked fine although I did not want it that way. I preferred them to be nested into one another so that I could assign and display the different objects at the same time.
I then had to figure out how to white-list strong parameters of these nested objects/attributes and there were good questions here that helped me with that.
When I create my database entries everything works fine. The problem comes when I want to edit the object in the database. In the log I get "Unpermitted parameter: answers" even though I've whitelisted every attribute including the ones for answers. I simply don't understand why.
Anybody who can point me in the right direction?
My surveys_controller:
class SurveysController < ApplicationController
before_action :set_survey, only: [:show, :edit, :update, :destroy]
def index
#surveys = Survey.all
end
def show
#survey = Survey.find(params[:id])
end
def new
#survey = Survey.new
3.times do
question = #survey.questions.build
4.times { question.answers.build }
end
end
def create
#survey = Survey.new(survey_params)
if #survey.save
flash[:notice] = 'Survey was successfully created.'
redirect_to(:action => 'index')
else
render('new')
end
end
def edit
#survey = Survey.find(params[:id])
end
def update
#Find an existing object using form parameters
#survey = Survey.find(params[:id])
#Update the object
if #survey.update_attributes(survey_params)
flash[:notice] = "Survey updated successfully."
#If update succeeds, redirect to 'show' action.
redirect_to(:action => 'show', :id => #survey.id)
else
#Else redisplay the 'edit' form.
render('edit')
end
end
def delete
#survey = Survey.find(params[:id])
end
def destroy
#survey = Survey.find(params[:id]).destroy
flash[:notice] = "Survey destroyed successfully."
redirect_to(:action => 'index')
end
private
def set_survey
#survey = Survey.find(params[:id])
end
def survey_params
params.require(:survey).permit(:name, questions_attributes: [:survey_id, :id, :content, answers_attributes: [:id, :question_id, :correct_answer, :content]])
end
end
My survey.rb model:
class Survey < ActiveRecord::Base
has_many :questions, :dependent => :destroy
accepts_nested_attributes_for :questions, :reject_if => lambda { |a| a[:content].blank? }
scope :sorted, lambda { order("questions.created_at DESC")}
end
EDIT: My question.rb-model:
class Question < ActiveRecord::Base
belongs_to :survey
has_many :answers, :dependent => :destroy
accepts_nested_attributes_for :answers, :reject_if => lambda { |a| a[:content].blank? }
scope :sorted, lambda { order("questions.created_at DESC")}
end
my answer.rb-model
class Answer < ActiveRecord::Base
belongs_to :question
end
my /surveys/show.html.erb
<td><%= link_to('<< Back to list', {:action => 'index'}, :class => 'action_index') %></td>
<div class="survey show">
<h2><strong>Show survey:</strong></h2>
<table summary="Survey detail view">
<tr>
<th>Survey name: </th>
<td><%= h #survey.name %></td>
</tr>
<tr>
<th>Question: </th>
<td><% for question in #survey.questions do %>
<li><%= h question.content %></li>
<ul>
<% for answer in question.answers do %>
<li><%= h answer.content %></li>
<% end %>
</ul>
<% end %>
</td>
</tr>
<tr>
<th>Created_at: </th>
<td><%= #survey.created_at %></td>
</tr>
<tr>
<td><%= link_to('Edit', {:action => 'edit', :id => #survey.id }, :class => 'action_edit') %></td>
<td><%= link_to('Delete', {:action => 'destroy', :id => #survey.id }, :class => 'action_edit') %></td>
</tr>
</table>
</div>
My _form_for.html.erb
<%= form_for #survey do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<%= f.fields_for :questions do |ff| %>
<%= render 'question_fields', :f => ff %>
<% end %>
<%= f.fields_for :answers do |fff| %>
<%= render 'answer_fields', :f => fff %>
<% end %>
<p><%= f.submit "Submit" %></p>
<% end %>
My _question_field.html.erb
<p>
<%= f.label :content, "Question" %><br />
<%= f.text_area :content, :rows => 3 %><br />
</p>
My _answer_field.html.erb
<p>
<%= f.label :content, "Answer" %>
<%= f.text_field :content %>
<%= f.radio_button :correct_answer, true %>
</p>
You have posted your Survey model twice instead of your Question model. Does your question model accept_nested_attributes_for :answers?
Assuming that you have and that I understand your structure correctly, your problem is most likely in your form - instead of f.fields_for :answers, you should have ff.fields_for :answers, nested within f.fields_for :questions, as this is a nested resource of Question and not Survey. So:
<%= f.fields_for :questions do |ff| %>
<%= render 'question_fields', :f => ff %>
<%= ff.fields_for :answers do |fff| %>
<%= render 'answer_fields', :f => fff %>
<% end %>
<% end %>

Insert data into multiple tables from one controller/view [Rails 4]

I have more curious questions for all you amazing people!
I am creating a forum and when you create a topic, you are also creating the first post at the same time.
I need to assign variables to certain fields.
Example: :user_id => current_user.id,
I don't have the param settings correct, so many of the fields are NULL when stored in the database.
Models
class Topic < ActiveRecord::Base
belongs_to :forum
has_many :posts, :dependent => :destroy
belongs_to :user
accepts_nested_attributes_for :posts
end
class Post < ActiveRecord::Base
belongs_to :topic
belongs_to :user
end
Topics Controller
# GET /topics/new
def new
#topic = Topic.new
#topic.posts.build
end
def create
#topic = Topic.new(topic_params)
if #topic.save
##topic.responses = Post.new(params[:responses])
flash[:success] = "Topic Posted"
redirect_to "/forums/#{#topic.forum_id}"
else
render :new
end
end
def topic_params
# last_post_at = (:last_post_at => Time.now)
params.require(:topic).permit(
:name,
:description,
[:last_poster_id => current_user.id],
[:last_post_at => Time.now],
[:user_id => current_user.id],
:forum_id,
posts_attributes: [:id, :content, :topic_id, :user_id => current.user.id] )
end
Post Controller
# GET /posts/new
def new
#post = Post.new
end
def create
#post = Post.new(
:content => params[:post][:content],
:topic_id => params[:post][:topic_id],
:user_id => current_user.id)
if #post.save
#topic = Topic.find(#post.topic_id)
#topic.update_attributes(
:last_poster_id => current_user.id,
:last_post_at => Time.now)
flash[:notice] = "Successfully created post."
redirect_to "/topics/#{#post.topic_id}"
else
render :action => 'new'
end
end
_form for View/Topic
<%= form_for(#topic) do |f| %>
<% if params[:forum] %>
<input type="hidden"
id="topic_forum_id"
name="topic[forum_id]"
value="<%= params[:forum] %>" />
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_field :description %>
</div>
<%= f.fields_for :posts do |p| %>
<%= p.label :content %><br />
<%= p.text_area :content %>
<% end %>
<%= f.submit :class => "btn btn-primary" %>
<% end %>
You'll likely be looking for a function called:
accepts_nested_attributes_for
You put this into the model you're working with (in your case Post) and it will pass paeans for the nested model through to the corresponding controller
There is a good RailsCast about this and I've gr some experience with it too. If you want me to post working live code, let me know (I'm on my iPhone)
Live Code
Models
#app/models/image_post.rb
belongs_to :post, :class_name => 'Post'
belongs_to :image, :class_name => 'Image'
accepts_nested_attributes_for :image, :allow_destroy => true
#app/models/post.rb
has_many :images, -> { uniq }, :class_name => 'Image', :through => :images_posts, dependent: :destroy
has_many :images_posts, :class_name => 'ImagePost'
accepts_nested_attributes_for :images_posts, :allow_destroy => true
Controller
def new
#post = Post.new
#post.images_posts.build.build_image
end
def create
#Using Inherited Resources Gem
create!
end
private
def permitted_params
{:post => params.require(:post).permit(:title, :body, images_posts_attributes: [:caption, image_attributes: [:image]] )}
end
Form
<%= form_for [:admin, resource], :html => { :multipart => true } do |f| %>
<table class="resource_table">
<thead>
<th colspan="2"><%= params[:action].capitalize %> <%= resource_class %></th>
</thead>
<tbody class="form">
<% attributes.each do |attr| %>
<tr class="<%= cycle('odd', '')%>">
<td><%= resource_class.human_attribute_name(attr) %></td>
<td>
<% if attr == "body" %>
<%= f.text_area attr, :rows => 60, :cols => 80, :class => "redactor" %>
<% else %>
<%= f.text_field attr, :value => resource.public_send(attr).to_s %>
<% end %>
</td>
</tr>
<% end %>
<%= f.fields_for :images_posts do |images_posts| %>
<%= images_posts.fields_for :image do |images| %>
<tr>
<td>Image</td>
<td><%= images.file_field :image %></td>
</tr>
<% end %>
<tr>
<td>Caption</td>
<td><%= images_posts.text_field :caption %></td>
</tr>
<% end %>
<tr class="dull">
<td colspan="2"><%= f.submit "Go" %></td>
</tr>
</tbody>
</table>
<% end %>
Use accepts_nested_attributes
class topic
accepts_nested_attributes :posts
end
class post
accepts_nested_attributes :topic
end
Then in form you can use fields_for posts while creating topic form
Also in post form fileds_for for topic
You may want to fetch the post params as
params[:topic].fetch(:post_attributes, nil)
Rails 4 has been sanitized the mass-assignment to be called as strong_params
Example

rails: mass-assign error caused by updating child (Post) from parent (Topic) controller (edit view)

I am trying to edit a Topic which has many Posts.
Edit page for a Topic has Topic's name and Post's content that can be edited.
The mass-assignment error occurs in topics_controller.rb, update method, post.update_attributes(params[:post]).
How do I avoid mass-assignment error.
topic.rb
class Topic < ActiveRecord::Base
has_many :posts, :dependent => :destroy
belongs_to :forum
accepts_nested_attributes_for :posts, :allow_destroy => true
attr_accessible :name, :last_post_id, :posts_attributes
end
post.rb
class Post < ActiveRecord::Base
belongs_to :topic
attr_accessible :content
end
topics_controller.rb
def update
#topic = Topic.find(params[:id])
post = #topic.posts.first
if #topic.update_attributes(params[:topic]) && post.update_attributes(params[:post])
topic = Topic.find(#post.topic_id)
flash[:success] = "Success!"
redirect_to topic_posts_path(topic)
else
render 'edit'
end
end
views/topics/edit.html.erb
<%= form_for #topic do |f| %>
<!-- render 'shared/error_messages_topic' -->
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for #topic.posts.first do |post| %>
<%= render :partial => "posts/form", :locals => {:f => post} %>
<% end %>
<%= f.submit "Edit", class: "btn btn-large btn-primary" %>
<% end %>
views/posts/_form.html.erb
<%= f.label :content %>
<%= f.text_area :content %>
In update method you don't have to update attributes of both the models instead of if #topic.update_attributes(params[:topic]) && post.update_attributes(params[:post]) it should this only if #topic.update_attributes(params[:topic]) it will update the posts automatically.
And change your view from this <%= f.fields_for #topic.posts.first do |post| %> to <%= f.fields_for :posts, #topic.posts.first do |post| %> it will work fine.
For more information read this http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-fields_for

find_or_create_by_name with has_many :through

Artists have many Events. Events have many Artists. The join between these two models is called Performances.
I'm trying to associate Artists with Events on the Event add/edit page. I would like to be able to add an Artist only if it doesn't exist, and create the join (performance) regardless. An Artist should be associated with an Event only once.
It was suggested that I use find_or_create_by_name instead of accepts_nested_attributes_for.
I'm following the Railscasts #102 instructions for Auto-Complete which say to use virtual attributes. I haven't even gotten to the auto-complete part, just trying to get find_or_create_by_name working.
I'm getting "undefined method `artist_name' for #" on the Event edit and new pages. In the Railscast, Ryan gets an undefined method before he adds the methods to the model. But I have the method in the Model.
No idea what to do.
event.rb
validates_presence_of :name, :location
validates_uniqueness_of :name
validates_associated :performances
has_many :performances, :dependent => :delete_all
has_many :artists, :through => :performances
#accepts_nested_attributes_for :artists, :reject_if => proc {|a| a['name'].blank?}, :allow_destroy => true
def artist_name
artist.name if artist
end
def artist_name=(name)
self.artist = Artist.find_by_name(name) unless name.blank?
end
artist.rb
validates_presence_of :name
has_many :mixes
has_many :performances, :dependent => :delete_all
has_many :events, :through => :performances
perfomance.rb
belongs_to :artist
belongs_to :event
events_controller.rb
def create
#event = Event.new(params[:event])
respond_to do |format|
if #event.save
flash[:notice] = 'Event was successfully created.'
format.html { redirect_to(admin_events_url) }
format.xml { render :xml => #event, :status => :created, :location => #event }
else
format.html { render :action => "new" }
format.xml { render :xml => #event.errors, :status => :unprocessable_entity }
end
end
end
_form.html.erb
<% form_for([:admin,#event]) do |f| %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<p>
<%= f.label :location %><br/>
<%= f.text_field :location %>
</p>
<p>
<%= f.label :date %><br />
<%= f.date_select :date %>
</p>
<p>
<%= f.label :description %><br />
<%= f.text_area :description %>
</p>
<% f.fields_for :artists do |builder| %>
<%= render 'artist_fields', :f => builder %>
<% end %>
<p><%= link_to_add_fields "Add Artist", f, :artists %></p>
<p>
<%= f.submit 'Submit' %> <%= link_to 'Cancel', admin_events_path %>
</p>
<% end %>
_artist_fields.html.erb
<p class="fields">
<%= f.label :artist_name, "Artist"%><br/>
<%= f.text_field :artist_name %>
<%= link_to_remove_fields "remove", f %>
</p>
Personally I would go back to accepts_nested_attributes_for, ryan bates method there was in the days before nested attributes.
In your controller do something like:
def new
#event = Event.find params[:id]
#artist = #event.artists.build
def edit
#event = Event.find params[:event_id]
#artist = #event.artists.find params[:user_id]
While in the view
...
<% f.fields_for :artists, #artist do |builder| %>
...

Loading association data from database into edit.html.erb

I have the following one to many associations. Document has many Sections and Section has many Items.
class Document < ActiveRecord::Base
has_many :document_sections, :dependent => :destroy, :autosave => true
has_many :document_items, :through => :document_sections
end
class DocumentSection < ActiveRecord::Base
belongs_to :document
has_many :document_items, :dependent => :destroy, :autosave => true
end
class DocumentItem < ActiveRecord::Base
belongs_to :document_section
end
And the 'edit' action as follows :-
def edit
#document = Document.find(params[:id])
end
Here is the edit.html.erb
<h1>Edit!</h1>
<% form_for(#document) do |f| %>
<%= f.error_messages %>
<p>
<p> Header Comment <p/><br />
<%= f.text_field :comment %>
<%= f.hidden_field :uid %>
</p>
<% #document.document_sections.each do |section| %>
<% f.fields_for :section, :index => section.id do |s| %>
<p>
<%= s.hidden_field :seqnum, options = {:value => section.seqnum} %>
</p>
<% section.document_items.each do |item| %>
<% s.fields_for :item, :index => item.id do |i| %>
<p>
<%= i.text_area :comments, options = {:value => item.comments} %>
</p>
<% end %>
<% end %>
<% end %>
<% end %>
<p>
<%= f.submit "Submit Comments" %>
</p>
<% end %>
I have to specify the options hash with the value attribute set, for eg:
options = {:value => item.comments}
in order to show the item comments when I click the 'edit' link to modify the item comments. Shouldn't they be loaded by default, which seems to be the case for header comments.
Thanks for replying. Yes, i want to render the text area with the item.comments value from the database. The below code I had, does not load the comments.
<% s.fields_for :item, :index => item.id do |i| %>
<p>
<%= i.text_area :comments %>
</p>
<% end %>
can you explain me why
<%= text_area(:item, :comments) %>
works but
<%= i.text_area :comments %>
does not. Thanks much.
It seems your understanding of options is not correct. Here is what it is:
Additional options on the input tag can be passed as a hash with options
Which means that options sets attributes for the HTML tag.
You did not specify what exactly you want to do in the question, but I assume you want to render textarea tag with item.comments as a value. If so then you can use the 2nd parameter method (see the docs) and try this:
text_area(:item, :comments, :size => "20x30")
# => <textarea cols="20" rows="30" id="item_comments" name="item[comments]">
# #{#item.comments}
# </textarea>

Resources