Rails doesn't save nested form with polymorphic models - ruby-on-rails

I have these models:
class Review < ActiveRecord::Base
belongs_to :reviewable, polymorphic: true
end
class Article < ActiveRecord::Base
has_one :review, as: :reviewable, dependent: :destroy
accepts_nested_attributes_for :review
end
And a form like this:
<%= form_for #article do |f| %>
<%= f.fields_for(:review, Review.new) do |r| %>
<%= r.label :content %>
<%= r.text_field :content %>
<% end %>
<%= f.label :description %>
<%= f.text_field :description %>
<% end %>
Inside my ArticlesController I create article simple like this:
#article = Article.new(article_params)
#article.save
def article_params
params.require(:article).permit(:description, review_attributes: [:id, :content])
end
What am I doing wrong? Thank you.

Try adding accepts_nested_attributes_for
Updated:
app/models/article.rb
class Article < ActiveRecord::Base
has_one :review, as: :reviewable, dependent: :destroy
accepts_nested_attributes_for :review
end
app/controllers/articles_controller.rb
def new
#article = Article.new
end
def create
#article = Article.new(article_params)
if #article.save
redirect_to #article, notice: "Article created!"
else
render :new
end
end
private
def article_params
params.require(:article).permit(:description, review_attributes: [:content])
end
view
<%= form_for(#article) do |form| %>
<%= form.fields_for(:review_attributes, #article.build_review) do |review| %>
<%= review.label :content %>
<%= review.text_field :content %>
<% end %>
<%= form.label :description %>
<%= form.text_field :description %>
<% end %>

Related

Unknown attribute 'answered_questions_attributes'

I'm trying to create a form where a user can answer questions and submit to a results page, however, when I am trying to pass the AnsweredQuestions attributes through the Quiz controller it's telling me it's an 'unknown attribute'.
Error i'm receving: unknown attribute 'answered_questions_attributes' for AnsweredQuestion.
Quiz.rb:
class Quiz < ApplicationRecord
validates :title, presence: true, length: { maximum: 50 }
has_many :questions, dependent: :destroy
has_many :answered_questions, through: :questions, dependent: :destroy
accepts_nested_attributes_for :answered_questions, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :questions, reject_if: :all_blank, allow_destroy: true
end
AnsweredQuestion.rb:
class AnsweredQuestion < ApplicationRecord
belongs_to :user
belongs_to :question
belongs_to :answer
belongs_to :quiz
end
Quiz controller:
def show
#quiz = Quiz.find(params[:id])
#questions = Question.all
#answered_questions = current_user.answered_questions.build
#quiz.answered_questions.build
end
def create
#quiz = Quiz.new(show_params)
if #quiz.save
flash[:success] = "You have created a new quiz!"
redirect_to #quiz
else
render 'new'
end
end
def post_answered_questions
#answered_question = current_user.answered_questions.build(show_params)
if #answered_question.save
flash[:success] = "You have completed the quiz!"
redirect_to results_quiz_path(params[:quiz][:id])
else
render ''
end
end
private
def user_completed_quiz
if(current_user.answered_questions.pluck(:quiz_id).uniq.include?(params[:id].to_i))
redirect_to quizzes_path
end
end
def show_params
params.require(:quiz).permit(:title, answered_questions_attributes: [:id, :answer_id, :question_id, :user_id, :quiz_id], questions_attributes: [:id, :question_title, :quiz_id, :done, :_destroy, answers_attributes: [:id, :answer_title, :question_id, :quiz_id, :correct_answer, :_destroy]])
end
end
show.html.erb (in quizzes):
<%= form_for(#quiz, url: post_answered_questions_quizzes_path, method: "POST") do |f| %>
<%= #quiz.title %>
<%= f.hidden_field :id, :value => #quiz.id %>
<% #quiz.questions.each do |question| %>
<%= f.fields_for :answered_questions do |answer_ques| %>
<h4><%= question.question_title %></h4>
<%= answer_ques.hidden_field :question_id, :value => question.id %>
<%= answer_ques.hidden_field :quiz_id, :value => #quiz.id %>
<%= answer_ques.select(:answer_id, options_for_select(question.answers.map{|q| [q.answer_title, q.id]})) %>
<% end %>
<% end %>
<%= submit_tag %>
<% end %>
UPDATED:
show.html.erb (quizzes):
<%= form_for(#answered_questions, url: answered_questions_path, method: "POST") do |f| %>
<%= #quiz.title %>
<%= f.hidden_field :id, :value => #quiz.id %>
<% #quiz.questions.each do |question| %>
<%= f.fields_for :answered_questions do |answer_ques| %>
<h4><%= question.question_title %></h4>
<%= answer_ques.hidden_field :question_id, :value => question.id %>
<%= answer_ques.hidden_field :quiz_id, :value => #quiz.id %>
<%= answer_ques.select(:answer_id, options_for_select(question.answers.map{|q| [q.answer_title, q.id]})) %>
<% end %>
<% end %>
<%= submit_tag %>
<% end %>
AnsweredQuestion controller:
class AnsweredQuestionsController < ApplicationController
def show
#answered_question = AnsweredQuestion.new
end
def create
#answered_question = current_user.answered_questions.build(answered_params)
binding.pry
if #answered_question.save
flash[:success] = "You have completed the quiz!"
redirect_to results_quiz_path(params[:quiz][:id])
else
render ''
end
end
def edit
#answered_questions = AnsweredQuestion.find(params[:id])
end
def destroy
AnsweredQuestion.find(params[:id]).destroy
flash[:success] = "Answered quiz deleted"
redirect_to answered_questions_url
end
private
def answered_params
params.require(:answered_questions).permit(:question_id, :answer_ids, :user_id, :quiz_id, :id, :_destroy)
end
end
It looks like you are accepting nested attributes for Quiz but using the nested attributes in AnsweredQuestion instead of in Quiz. You need to add accepts_nested_attributes_for in the other model and in show_params it should be quiz_attributes instead of answered_questions_attributes. Although you are using show_params twice, once over one model and the second one over the other, so you may need two params. Otherwise you'll break the other one.

Rails not saving nested attributes

I have the tables Task and Item. I have a form for Item where I record all the possible items that my Tasks may have, which is working fine. Then I have the form for Task where all the Items are displayed alongside a field to put a cost value for each item. This will result in a join between Task and Item: TaskItem (this table contains task_id, item_id and cost).
When I submit the form, it's saving the Task but not the TaskItems associated. I don't see what I'm missing as I searched a lot for this problem and nothing seems to work. Please, see the code below.
Model:
class Task < ApplicationRecord
has_many :task_items
has_many :items, :through => :task_items
accepts_nested_attributes_for :task_items, :allow_destroy => true
end
class Item < ApplicationRecord
has_many :task_items
has_many :tasks, :through => :task_items
end
class TaskItem < ApplicationRecord
belongs_to :task
belongs_to :item
accepts_nested_attributes_for :item, :allow_destroy => true
end
Controller:
def new
#items = Item.all
#task = Task.new
#task.task_items.build
end
def create
#task = Task.new(task_params)
#task.save
redirect_to action: "index"
end
private def task_params
params.require(:task).permit(:id, :title, task_items_attributes: [:id, :item_id, :cost])
end
My view:
<%= form_for :task, url:tasks_path do |f| %>
<p>
<%= f.label :title %><br>
<%= f.text_field(:title, {:class => 'form-control'}) %><br>
</p>
<% #items.each do |item| %>
<% #task_items = TaskItem.new %>
<%= f.fields_for :task_items do |ti| %>
<%= ti.label item.description %>
<%= ti.text_field :cost %>
<%= ti.hidden_field :item_id, value: item.id %>
<% end %>
<% end %>
<p>
<%= f.submit({:class => 'btn btn-primary'}) %>
</p>
You need to add inverse_of option to the has_many method in class Task:
class Task < ApplicationRecord
has_many :task_items, inverse_of: :task
has_many :items, through: :task_items
accepts_nested_attributes_for :task_items, :allow_destroy => true
end
This is due to the when creating a new TaskItem instance, it requires that the Task instance already exists in database to be able to grab the id fo the Task instance. Using this option, it skips the validation.
You can read this post about inverse_of option and its use cases.
fields_for has an option to specify the object which is going to store the information. This combined with building each of the TaskItem from the has_many collection should ensure that all the relationship are set correctly.
View Code:
<%= form_for #task do |f| %>
<p>
<%= f.label :title %><br>
<%= f.text_field(:title, {:class => 'form-control'}) %><br>
</p>
<% #items.each do |item| %>
<% task_item = #task.task_items.build %>
<%= f.fields_for :task_items, task_item do |ti| %>
<%= ti.label item.description %>
<%= ti.text_field :cost %>
<%= ti.hidden_field :item_id, value: item.id %>
<% end %>
<% end %>
<p>
<%= f.submit({:class => 'btn btn-primary'}) %>
</p>
<% end %>
Controller Code:
def index
end
def new
#items = Item.all
#task = Task.new
end
def create
#task = Task.new(task_params)
#task.save
redirect_to action: "index"
end
private
def task_params
params.require(:task).permit(:id, :title, task_items_attributes: [:id, :item_id, :cost])
end

How to create the child for existing object with self-referential associations?

I have a model with self-referential associations:
class Task < ActiveRecord::Base
has_many :subtasks, class_name: 'Task', foreign_key: "parent_id"
belongs_to :parent, class_name: 'Task'
accepts_nested_attributes_for :subtasks, allow_destroy: true
belongs_to :user
belongs_to :project
end
The meaning is:
I create tasks
To some of the tasks i wanna add some subtasks
When I'm going to the task_path - /tasks/:id, action 'tasks#show' - i see the attributes of the task, but below i want to have an opportunity to add a subtasks.
And some questions in addition:
Is it good way to use one model for Tasks and Subtasks?
Do i need to create second controller?
Thank you and sorry for my English.
UPD1:
TasksController
class TasksController < ApplicationController
before_action :find_task, only: [:show, :edit, :update, :destroy]
def index
#tasks = Task.where("parent_id IS ?", nil)
end
def show
end
def new
#task = Task.new
#task.subtasks.build
end
def edit
end
def create
#task = Task.create(task_params)
if #task.errors.empty?
redirect_to #task
else
render 'new'
end
end
def update
#task.update_attributes(task_params)
if #task.errors.empty?
redirect_to #task
else
render 'edit'
end
end
def destroy
#task.destroy
redirect_to tasks_path
end
private
def task_params
params.require(:task).permit(:title, :description, :priority, :status, :scheduled, :deadline, subtasks_attributes: [:title])
end
def find_task
#task = Task.find(params[:id])
end
end
Show.html.erb for tasks/:id (just a rough draft)
<%= #task.deadline %>
<%= #task.title %>
<%= #task.description %>
<% #task.subtasks.each do |s| %>
<br><%= s.title %>
<%= link_to 'Delete', [s], method: :delete, data: { confirm: 'Are you sure?' } %>
<% end %>
<%= simple_form_for #task do |t| %>
<%= t.simple_fields_for :subtasks do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :title %>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
<% end %>
you can use accept_nested_attributes_for for subtasks
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
also consider using simple_form for passing nested model attributes
https://github.com/plataformatec/simple_form/wiki/Nested-Models
with this approach, no need to create separate controller for subtasks
I think its good to use subtasks for one parent task
The answer is:
We need one controller - TasksController.
We add parameter "accepts_nested_attributes_for" in model 'Task':
class Task < ActiveRecord::Base
has_many :subtasks, class_name: 'Task', foreign_key: "parent_id"
belongs_to :parent, class_name: 'Task'
accepts_nested_attributes_for :subtasks, allow_destroy: true
belongs_to :user
belongs_to :project
end
To TasksController we add additional parameters for subtasks:
def task_params
params.require(:task).permit(:title, :description, :priority, :status, :scheduled, :deadline, subtasks_attributes: [:title])
end
Form for the subtasks looks like this:
<%= simple_form_for #task do |t| %>
<%= t.simple_fields_for :subtasks, #task.subtasks.build do |f| %>
<div class="form-inputs">
<%= f.input :title %>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
<% end %>
Thank you very much! :)

Rails 4 nested attributes multiple records when updating

I'm stuck and I don't know why it is not working right.
I have a model product which has many tags.
When I update the product rails update properly the products attributes but is creating another tag record instead of just updating it.
here is my code:
View form:
<%= form_for ([#product.user, #product]), id: 'edit_form' do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<div class="field">
<%= f.fields_for :tags do |t| %>
<%= t.label :name %>
<%= t.text_field :name %>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
product model:
class Product < ActiveRecord::Base
belongs_to :user, :foreign_key => "user_id"
has_many :tags, :dependent => :destroy
accepts_nested_attributes_for :tags, reject_if: :all_blank, allow_destroy: true, :update_only => true
end
tags model:
class Tag < ActiveRecord::Base
belongs_to :product, :foreign_key => "product_id"
# before_save { name.downcase! }
end
product controller:
def edit
user = User.find(params[:user_id])
#product = user.products.find(params[:id])
#tags = #product.tags.all
respond_to do |format|
format.html
format.js
end
end
def update
user = User.find(params[:user_id])
#product = user.products.find(params[:id])
#tags = #product.tags.all
respond_to do |format|
if #product.update(product_params)
format.html { redirect_to([#product.user, #product], :notice => 'Product successfully updated.') }
else
format.html { render :action => "edit" }
end
end
end
def product_params
params.require(:product).permit(:name, :description, tags_attributes: :name)
end
You have to pass the tag id in the permit params in your controller
def product_params
params.require(:product).permit(:name, :description, tags_attributes: [:id,:name])
end

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

Resources