My models:
class Topic < ActiveRecord::Base
has_many :questions
end
class Question < ActiveRecord::Base
belongs_to :topic
has_many :answers, dependent: :destroy
accepts_nested_attributes_for :answers, reject_if: :all_blank, allow_destroy: true
end
class Answer < ActiveRecord::Base
belongs_to :question
end
My controller questions_controller:
class QuestionsController < ApplicationController
expose(:topic) { Topic.find(params[:topic_id]) }
expose(:question, attributes: :question_params)
expose(:questions) { topic.questions }
def create
if topic.questions.create(question_params)
redirect_to topic
else
render 'new'
end
end
private
def question_params
params.require(:question).permit(
:title,
:instructions,
answers_attributes: [:id, :answer, :correct, :question_id, :_destroy]
)
end
end
/questions/_form:
= simple_form_for [:topic, question] do |f|
= f.input :title, label: "Question:", placeholder: "Your question here."
= f.input :instructions, label: "Instructions:", placeholder: "If this question requires special instructions, put them here."
%br
%h3
Answers
#answers
= f.simple_fields_for :answers do |answer|
= render 'answer_fields', f: answer
.links
= link_to_add_association 'add answer', f, :answers
= f.submit class: "button small radius expand success"
/questions/answer_fields:
.nested_fields
= f.input :answer
= f.input :correct, as: :boolean
= link_to_remove_association "remove task", f
routes.rb:
Rails.application.routes.draw do
resources :topics do
resources :questions
end
root to: 'topics#index'
end
I'm using cocoon to manage my nested forms.
When I go to /topics/.:id and press the 'Add New Question' button, it takes me to the /topics/.:id/questions/new path, and when I press the 'add answer' button, cocoon renders 3 new spaces for answers. Also, the items are not being deleted if I press the 'remove answer' link.
Why is the view rendering 3 answer fields?
Why is the 'remove answer' link not working?
Related
I am using Rails 5.1 and im having some issues saving params on an n:n relationship.
I have three models:
class Course < ApplicationRecord
belongs_to :studio
has_many :reviews, dependent: :destroy
has_many :has_category
has_many :categories, through: :has_category
validates :name, presence: true
end
class Category < ApplicationRecord
has_many :has_category
has_many :courses, through: :has_category
end
class HasCategory < ApplicationRecord
belongs_to :category
belongs_to :course
end
and a simple form to create a new course with different categories using check_box_tag (not sure if using it correctly though)
<%= simple_form_for [#studio, #course] do |f| %>
<%= f.input :name %>
<%= f.input :description %>
<% #categories.each do |category| %>
<%= check_box_tag "course[category_ids][]", category.id, true %>
<%= category.name%>
<% end %>
<%= f.button :submit %>
<% end %>
And all is permitted and created on the courses controller:
def new
#studio = Studio.find(params[:studio_id])
#course = Course.new
#course.studio = #studio
#categories = Category.all
end
def create
#studio = Studio.find(params[:studio_id])
#course = Course.new(course_params)
#course.studio = #studio
#categories = params[:category_ids]
if #course.save
redirect_to course_path(#course)
else
render :new
end
end
def course_params
params.require(:course).permit(:studio_id, :name, :description, :category_ids)
end
With better_errors i know the categories are being requested, here the request info:
"course"=>{"name"=>"Course test", "description"=>"testing", "category_ids"=>["2", "3"]}, "commit"=>"Create Course", "controller"=>"courses", "action"=>"create", "studio_id"=>"16"}
but the categories are not saved on course_params, HasCategory instance or on the Course, i´ve tried with #course.categories = params[:category_ids] and other solutions without success.
How do i save the categories to the courses?
Try the following
Change the strong parameter with category_ids: []
def course_params
params.require(:course).permit(:studio_id, :name, :description, category_ids: [])
end
Comment out this line #categories = params[:category_ids]
Hope it helps
Here are my models:
class Project < ActiveRecord::Base
has_many :project_applications
has_many :questions
accepts_nested_attributes_for :questions, :allow_destroy => true, :reject_if => proc { |a| a[:content].blank? }
end
class Question < ActiveRecord::Base
belongs_to :project
has_many :answers
has_many :project_applications, through: :answers
end
class Answer < ActiveRecord::Base
belongs_to :question
belongs_to :project_application
end
class ProjectApplication < ActiveRecord::Base
belongs_to :project
belongs_to :student
has_many :answers
has_many :questions, through: :answers
end
A project is created by an employer, and a student can create a project_application. The project_application should present the questions and then show form fields that correspond to the questions answers. I cannot for the life of me figure out how the form view should look. I need a form_for ProjectApplication that accepts nested attributes for answers. I have the following in my controller:
class ProjectApplicationsController < ApplicationController
def new
#project = Project.find(params[:project_id])
#project_application = ProjectApplication.new
#project_application.project = #project
#project_application.project.questions.each do |question|
#answer = question.answers.build
#answer.project_application = #project_application #this line does not work
puts 'answer' + #answer.inspect.to_s
end
puts 'here are the answers' + #project_application.answers.inspect.to_s
end
end
The problem with this is that the answers are not correctly being associated with project_applications because the project_applications don't have an id yet (because they have not been created) so the association can't happen, so the answer fields are not displayed. Here is the view code (does not work) that I have now:
<%= form_for #project_application, url: project_project_applications_path(#project.id), method: :post, remote: true do |f| %>
<%= f.fields_for :project do |proj| %>
<%= proj.fields_for :questions do |quest| %>
<%= quest.fields_for :answers do |answer| %>
<%= answer.text_area :content %>
<% end %>
<% end %>
<% end %>
<%= f.submit "APPLY" %>
<% end %>
How do I change the view and/or controller to properly display answer fields correctly associated with questions and the project application?
My understanding:
You have projects
Each project has many questions & project_applications
Each question belongs to a project, has many answers through project_applications
For each project, you'd like to create applications with the applicable answers. Here's what I'd do:
Models
class Project < ActiveRecord::Base
has_many :project_applications
has_many :questions
end
class Question < ActiveRecord::Base
belongs_to :project
has_many :answers
has_many :project_applications, through: :answers
end
class Answer < ActiveRecord::Base
belongs_to :question
belongs_to :project_application
end
class ProjectApplication < ActiveRecord::Base
belongs_to :project
belongs_to :student
has_many :answers
has_many :questions, through: :project
accepts_nested_attributes_for :answers
def self.build
application = self.new
application.answers.build
end
end
Controller
#app/controllers/project_applications_controller.rb
def new
#projectapplication = ProjectApplication.build
#project = #projectapplication.project
end
def create
end
private
def application_params
params.require(:project_application).permit(:application, :attributes, :here, answer_attributes: [:content])
end
Views
#app/views/project_applications/new.html.erb
<%= form_for #projectapplication do |f| %>
<%= f.text_field :application_fields %>
<% #project.questions.each do |question| %>
<%= f.fields_for :answers, question do |answer| %>
<%= answer.hidden_field :question_id, question.id %>
<%= answer.text_field :content, placeholder: question.content %>
<% end %>
<% end %>
<% end %>
Process
This works by creating a new project_application, sending answers directly to the answer model. Because answers are directly associated with questions, and projectsthrough questions, you should be able to get it working without passing any more data
This might not work outright, but I'm sure with some tweaking it will deliver the desired result
I have 3 models Item which accepts nested attributes for questions and questions accept nested attributes for answers. I'm trying to create an item which has a question and an answer in the same form.
item.rb
class Item < ActiveRecord::Base
has_many :questions, dependent: :destroy
accepts_nested_attributes_for :questions
end
question.rb
class Question < ActiveRecord::Base
belongs_to :item
has_many :answers, dependent: :destroy
accepts_nested_attributes_for :answers
end
answer.rb
class Answer < ActiveRecord::Base
belongs_to :question
end
item_controller.rb
class ItemsController < ApplicationController
def new
#item = #repository.items.new
questions = #item.questions.build
answers = questions.answers.build
end
def create
#item = Item.new(item_params)
if #item.save
redirect_to #item, notice: '...'
else
render action: 'new'
end
end
private
def item_params
params.require(:item).permit(:id, :content, :kind, :questions_attributes => [:content, :helper_text, :kind], :answers_attributes => [:content, :correct])
end
end
_form.haml
= simple_form_for(#item) do |f|
= f.input :kind
= f.input :content
= f.simple_fields_for :questions do |q|
= q.input :content
= q.simple_fields_for :answers do |a|
= a.input :content
= f.submit
The form is being displayed correctly and it saves the question model correctly. I can't seem to save the answer though.
I've already looked at a lot of online help but none are covering it with Rails 4 strong params.
I think your problem is with your strong params:
def item_params
params.require(:item).permit(:id, :content, :kind, questions_attributes: [:content, :helper_text, :kind, answers_attributes: [:content, :correct]])
end
Basically, when you pass a deep nested form (where you have multiple dependent models), you'll have to pass the attributes as part of the other model's attributes. You had the params as separate
I run into a similar issue and, while Richard Peck answer helped me as well, there is one thing that it was missing for me.
If you are deep-nesting you need to specify the id of the parent of the nested item. In this case to create an answers you need to make questions id explicit with q.input :id, otherwise you will run into this error.
= simple_form_for(#item) do |f|
= ...
= f.simple_fields_for :questions do |q|
= ...
= q.input :id
= q.simple_fields_for :answers do |a|
= ...
= f.submit
I have a job model that belongs_to a profile model. A profile has_many jobs. I have a nested model form that in which a user adds jobs. The form is successfully adding jobs, but editing/updating is not working. Instead, when I try to edit a job, it keeps the old version of the job, but adds the new version as well. It does not replace the old version with the new one. How do I fix this? I'm pretty sure it has something to do with the edit/update controllers.
Edit controller:
def edit
#profile = current_user.profile
end
Update controller:
def update
#if current_user.profile.jobs.any?
#profile = current_user.profile.update_attributes(profile_params)
if current_user.profile.invalid?
#profile = current_user.profile
render :edit, :status => :unprocessable_entity
else
redirect_to profile_path(current_user.profile_name)
end
end
The thing is, the update controller is working for the non-nested information, it is just not working for the nested jobs. Here are the strong parameters:
def profile_params
params.require(:profile).permit(:title,
:category, :description, :state, :zip_code, :rate,
jobs_attributes: [:firm, :position, :category, :description,
:begin, :end, :_destroy])
end
And here is the profile model:
class Profile < ActiveRecord::Base
belongs_to :user
has_many :jobs
accepts_nested_attributes_for :jobs , :reject_if => :all_blank, :allow_destroy => true
end
Also, here's my routes if that will help:
resources :profiles do
resources :jobs
end
Thanks for your help in advance.
EDIT
Here are the params from the create action:
{"jobs_attributes"=>{"1383593195849"=>{"firm"=>"1st firm", "position"=>"1st position",
"category"=>"1st category", "description"=>"1st description", "begin"=>"1999",
"end"=>"1999", "_destroy"=>"false"}}}
And here are the params for the same job when updated:
{"jobs_attributes"=>{"0"=>{"firm"=>"1st firm", "position"=>"1st position",
"category"=>"1st category", "description"=>"1st description", "begin"=>"1999",
"end"=>"1999", "_destroy"=>"false"}, "1"=>{"firm"=>"1st firm",
"position"=>"1st position", "category"=>"1st category",
"description"=>"1st description", "begin"=>"1999", "end"=>"1999", "_destroy"=>"1"}}}
EDIT:
Here are my views. I don't think they are part of the problem though.
= simple_form_for #profile do |f|
%h3 Jobs
#jobs
= f.simple_fields_for :jobs do |job|
= render 'job_fields', :f => job
.links
= link_to_add_association 'add job', f, :jobs
= f.submit
And here is the "job_fields" partial:
.nested-fields
= f.input :firm
= f.input :position
= f.input :category
= f.input :begin
= f.input :end
= f.input :description
= f.input :_destroy, as: :boolean, inline_label: 'Delete box'
= link_to_remove_association "remove task", f
The trick is adding the ':id' symbol to the strong params. Although I still haven't figured out why and I'm not sure if its secure.
I have a nested form using the cocoon gem that gests 5 classes deep. They are all belongs_to and has_many except one that is the 2nd to most nested class with a has_one and belongs_to association. Whenever I edit it, it gets deleted and recreates an instance (not what I want) any ideas?
class FirmwaresController < ApplicationController
def index
#firmwares = Firmware.all
end
def new
#firmware = Firmware.new
end
def edit
#firmware = Firmware.find(params[:id])
end
end
class Setting < ActiveRecord::Base
belongs_to :menu_item
has_many :selections, dependent: :destroy
has_many :dependencies
attr_accessible :kind, :name, :placement, :selections_attributes
accepts_nested_attributes_for :selections, reject_if: :all_blank, allow_destroy: true
validates_presence_of :kind
end
class MenuItem < ActiveRecord::Base
belongs_to :menu
belongs_to :dependency
has_one :setting, dependent: :destroy
attr_accessible :dependency_id, :menu_for, :name, :placement, :setting_attributes
accepts_nested_attributes_for :setting, reject_if: :all_blank, allow_destroy: true
validates_presence_of :name
end
_menu_items.html.haml
.row-fluid
.input-prepend
= f.input :name, label: "Menu Item Name"
.input-append
= f.input :placement, label: "Order in Menu (X.Y)", as: :string
= f.simple_fields_for :setting do |settings_form|
= render 'setting_fields', f: settings_form
%p
= link_to_add_association "Has Setting?", f, :setting, class: "btn btn-primary btn-small add_setting_link"
= link_to_remove_association 'Remove Menu Item', f, class: "btn btn-danger btn-small remove_menu_item"
_setting_fields.html.haml
.row-fluid
.input-prepend
= f.input :kind, label: "Type", collection: setting_kinds, input_html: {class: "setting_type"}
.input-append
= link_to_remove_association 'Remove Setting', f, class: 'btn btn-danger btn-small remove_setting_link'
.setting_selection{style: "display: none;"}
= f.simple_fields_for :selections do |selections_form|
= render 'selection_fields', f: selections_form
%p= link_to_add_association 'Add Selection Option', f, :selections, class: "btn btn-primary btn-small"
I assume the has_one association you're referring to is the has_one :setting in MenuItem. If so, you might try adding update_only: true to your accepts_nested_attributes_for :setting options. From the documentation (emphasis mine):
By default the :update_only option is false and the nested attributes are used to update the existing record only if they include the record's :id value. Otherwise a new record will be instantiated and used to replace the existing one. However if the :update_only option is true, the nested attributes are used to update the record’s attributes always, regardless of whether the :id is present.
This question is old, but this could help some people:
The reason the association was always recreated for me, was that I had forgotten to include :id in the permitted parameters of phone_number_attributes! If you forget to do that, rails wont get the id and will recreate a new record to replace the old one.
def user_params
params.require(:user).permit(
:avatar,
:remove_avatar,
{ id_photo_attributes: [:photo, :remove_photo] },
{ phone_number_attributes: [:id, :phone_number] } # dont forget :id here!
)
end