Rails nested models with comments as commentable (polymorphic association) - ruby-on-rails

I have question and answer models with the comment to both as commentable. Comments on the question work fine but on the answer I am getting: "undefined method `comments' for nil:NilClass." Following is the code:
controller/comment_controller.rb
class CommentsController < ApplicationController
before_action :authenticate_user!
def create
#comment = #commentable.comments.new comment_params
#comment.user = current_user
#comment.save
redirect_to #commentable, notice: "Thanks for your comment."
end
private
def comment_params
params.require(:comment).permit(:content)
end
end
controller/questions/comment_controller.rb
class Questions::CommentsController < CommentsController
before_action :set_commentable
private
def set_commentable
#commentable = Question.find_by_permalink(params[:question_id])
end
end
controller/questions/answers/comment_controller.rb
class Answers::CommentsController < CommentsController
before_action :set_commentable
private
def set_commentable
#question = Question.find_by_permalink(params[:question_id])
#commentable = #question.answers.find(params[:answer_id])
end
end
Models
class Question < ApplicationRecord
belongs_to :user
has_many :answers, dependent: :destroy
has_many :comments, as: :commentable, dependent: :destroy
end
class Answer < ApplicationRecord
belongs_to :user
belongs_to :question
has_many :comments, as: :commentable, dependent: :destroy
end
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
belongs_to :user
end
Routes:
resources :questions do
resources :comments, module: :questions
resources :answers, :only => [:create, :destroy, :update] do
resources :comments, module: :answers
end end
Question: show.html.erb:
<h1><%= link_to #question.title, question_path(#question)%></h1>
<p><%= #question.description %></p>
<%= render partial: "comments/comments", locals: {commentable: #question } %>
<%= render partial: "comments/form", locals: {commentable: #question } %>
<% #question.answers.each do |answer| %>
<%= render partial: "comments/comments", locals: {commentable: #answer } %>
<%= render partial: "comments/form", locals: {commentable: #answer } %>
<% end %>
_comments.html.erb
<div class="content">
<h2 class="title"><%= pluralize(commentable.comments.count, 'Comment', plural: 'Comments') %></h2>
<% commentable.comments.each do |comment| %>
<small><%= time_ago_in_words(comment.created_at) %></small>
<p><%= comment.content %></p>
<% end %>
</div>
_form.html.erb
<%= form_for [commentable, Comment.new] do |f| %>
<%= f.text_area :content, class:'textarea', placeholder:'Add a comment' %>
<%= f.submit 'Comment', class:'button is-primary' %>
<% end %>
Update:
Strange thing happend. I reset the database and altest I can add comment to the answer now, not sure how it got fixed.
However I am getting a new error which make more sense: undefined method `answer_comments_path' for #<#:0x007f89e04905b8>. I have put <%= answer.inspect %> just before the comment input form to the answer and I am getting as expected answer model data.
So when I do the rake routes i dont see answer_comments_path.
The route is question_answer_comments_path. How can I pass this path to the answer/comments form.

Related

rails 7 nested forms attributes can't be saved?

build an online course application with lessons and each lesson with exercises
I created the lesson without any problem.
But when creating the exercises, my attributes (Question and answer) are not created and I have the error
more details about my approach:
Form Error:
You will notice the presence of errors on Question and Anwser
routes.rb
Rails.application.routes.draw do
resources :courses, only: [:show] do
resources :exercises, only: [:new, :create, :show]
end
### course ##
resources :courses, except: [:show, :new]
get "new-course", to:"courses#new"
## Exercise ##
resources :exercises, except: [:new, :create, :show]
devise_for :users
# Defines the root path route ("/")
root "courses#index"
end
Note: Exercise's rested in Course
Models:
****models/user.rb****
class User < ApplicationRecord
#User Relations
has_many :courses, class_name: "Course", foreign_key: "user_id", dependent: :destroy
has_many :exercises, class_name: "Exercise", foreign_key: "user_id", dependent: :destroy
end
****model/course.rb****
class Course < ApplicationRecord
has_rich_text :content
belongs_to :user
has_many :exercises, class_name: "Exercise", foreign_key: "course_id", dependent: :destroy
extend FriendlyId
friendly_id :title, use: :slugged
end
****models/exercise.rb****
class Exercise < ApplicationRecord
belongs_to :user
belongs_to :course
has_many :questions, class_name: "Question", foreign_key: "exercise_id", dependent: :destroy
has_rich_text :content
### Nested attributes
accepts_nested_attributes_for :questions, allow_destroy: true
extend FriendlyId
friendly_id :title, use: :slugged
end
****models/question.rb****
class Question < ApplicationRecord
belongs_to :exercise
has_many :answers, class_name: "Answer", foreign_key: "question_id", dependent: :destroy
### Nested Attributes
accepts_nested_attributes_for :answers, allow_destroy: true
### Constantes question type
enum question_type: { single_choice: 0, multiple_choice: 1, long_answer: 2 }
def self.question_type_select
question_types.keys.map { |k| [k.titleize, k] }
end
end
****models/answer.rb****
class Answer < ApplicationRecord
belongs_to :question
end
courses_controller.rb
class CoursesController < ApplicationController
before_action :authenticate_user!
before_action :set_course, only: %i[ show edit update destroy ]
# GET /courses/new
def new
#course = Course.new
end
# POST /courses or /courses.json
def create
#course = current_user.courses.build(course_params)
respond_to do |format|
if #course.save
format.html { redirect_to course_url(#course), notice: "Course was successfully created." }
format.json { render :show, status: :created, location: #course }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #course.errors, status: :unprocessable_entity }
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_course
#course = Course.friendly.find(params[:id])
end
# Only allow a list of trusted parameters through.
def course_params
params.require(:course).permit(:title, :content, :slug, :user_id)
end
end
controllers/course_controller.rb
class ExercisesController < ApplicationController
before_action :authenticate_user!
before_action :set_exercise, only: %i[ show edit update destroy ]
before_action :set_course, only: %i[new create]
# GET /exercises or /exercises.json
def index
#exercises = Exercise.all
end
# GET /exercises/1 or /exercises/1.json
def show
end
# GET /exercises/new
def new
#exercise = #course.exercises.build()
end
# GET /exercises/1/edit
def edit
end
# POST /exercises or /exercises.json
def create
#exercise = Exercise.new(exercise_params)
respond_to do |format|
if #exercise.save
format.html { redirect_to course_path(#course), notice: "course was successfully created." }
else
format.html { render :new, status: :unprocessable_entity }
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_exercise
#exercise = Exercise.friendly.find(params[:id])
end
def set_course
#course = Course.friendly.find(params[:course_id])
end
# Only allow a list of trusted parameters through.
def exercise_params
params.require(:exercise).permit(:title, :content, :slug, :user_id, :course_id,
questions_attributes: [:_destroy, :id, :question_type, :name,
answers_attributes: [:_destroy, :id, :name]])
end
end
in views:
views/courses/show.html.erb
<%= link_to 'New exercices', new_course_exercise_path(#course) %>
views/exercises/form.html.erb
<%= form_with( model: exercise, url:[#course, exercise]) do |form| %>
<% if exercise.errors.any? %>
<div id="error_explanation" class="bg-red-50 text-red-500 px-3 py-2 font-medium rounded-lg mt-3">
<h2><%= pluralize(exercise.errors.count, "error") %>
erreurs ont empêché la sauvegarde de cet exercice
</h2>
<ul>
<% exercise.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="">
<%= form.text_field :title , class:"w-full", placeholder:"le title de l'exercice ..." %>
</div>
<div class="mt-2">
<%= form.rich_text_area :content , class:"w-full", placeholder:"Consigne de l'exercice ..." %>
</div>
<%= form.hidden_field :course_id, value: #exercise.course_id %>
<%= form.hidden_field :user_id, value: current_user.id %>
<!-- Add Question template-->
<div data-controller="nested-form" data-nested-form-index-value='QUESTION_RECORD'
class=" border p-2 ">
<template data-nested-form-target='template'>
<%= form.fields_for :questions, Question.new, child_index: 'QUESTION_RECORD' do |question| %>
<%= render 'question_fields', form: question %>
<% end %>
</template>
<!-- Add Question field-->
<%= form.fields_for :questions do |question| %>
<%= render 'question_fields', form: question %>
<% end %>
<!-- Add Question input-->
<div data-nested-form-target="add_item" class="my-2">
<%= link_to "Add Question", "#",
data: { action: "nested-form#add_association" },
class:"bg-gray-200 p-1" %>
</div>
</div>
<div class="mt-8">
<%= form.submit "Add Exercise", class:"bg-blue-600 p-2 font-bold text-white" %>
</div>
<% end %>
to keep the code more readable, I split the questions and answers insertion in 2 partials
views/exercises/_question_fields.html.erb
<div class='nested-fields box' data-controller='dynamic-select'>
<div class='form-group m-2 py-2'>
<!-- Add Question type -->
<%= form.select :question_type,
options_for_select(Question.question_type_select, selected:
form.object.question_type),
{},
'data-dynamic-select-target': 'select',
'data-action': 'dynamic-select#selected' %>
</div>
<!-- Add Question field -->
<div class='form-group'>
<!-- Question Remove -->
<%= form.hidden_field :_destroy %>
<%= form.text_field :name, placeholder:'Question', class:'w-full form-control' %>
<small>
<%= link_to "Remove Question", "#",
data: { action: "click->nested-form#remove_association" } %>
</small>
</div>
<!-- Answer template-->
<div data-controller="nested-form"
data-nested-form-index-value='ANSWER_RECORD'
data-dynamic-select-target='choice'>
<template data-nested-form-target='template'>
<%= form.fields_for :answers, Answer.new, child_index 'ANSWER_RECORD' do |answer| %>
<%= render 'answer_fields', form: answer %>
<% end %>
</template>
<!-- Answer Form -->
<%= form.fields_for :answers do |answer| %>
<%= render 'answer_fields', form: answer %>
<% end %>
<!-- Add Answer -->
<div data-nested-form-target="add_item" class="my-2">
<%= link_to "Add Answer", "#", data: { action: "nested-form#add_association" }, class:"bg-green-300 p-1" %>
</div>
</div>
<div data-controller="nested-form" data-dynamic-select-target='long'>
</div>
</div>
views/exercises/_answer_fields.html.erb
<div class='nested-fields my-2'>
<div class='form-group'>
<%= form.hidden_field :_destroy %>
<%= form.text_field :name, placeholder: 'Answer', class: 'form-control w-full' %>
<small>
<%= link_to "Remove Answer", "#", data: { action: "click->nested-form#remove_association" } %>
</small>
</div>
</div>
in the console I see that the parameters are passed with the values from the form, but the question and answer tables are not saved

Rails reviews not showing up on album show page

Albums have many reviews and users through reviews, Reviews belong to album and belong to user, and Users have many reviews and have many albums through reviews.
On my albums show page, Reviews do not display. But Reviews display on the reviews index page. I can't seem to figure out what I'm missing. I'm not getting any errors.
Here is my Albums controller:
class AlbumsController < ApplicationController
before_action :set_album, only: [:show, :edit, :update]
def index
#albums = Album.all
#current_user
end
def show
#review = #album.reviews.build
end
def new
#album = Album.new
end
def create
#album = Album.new(album_params)
if #album.save
redirect_to album_path(#album)
else
render :new
end
end
def edit
end
def update
if #album.update(album_params)
redirect_to album_path(#album), notice: "Your album has been updated."
else
render 'edit'
end
end
private
def set_album
#album = Album.find(params[:id])
end
def album_params
params.require(:album).permit(:artist, :title, :avatar)
end
end
Reviews Controller:
class ReviewsController < ApplicationController
def index
#reviews = Review.all
end
def show
#review = Review.find(params[:id])
##reviews = Review.where("album_id = ?", params[:album_id])
end
def new
#review = Review.new
end
def create
#review = current_user.reviews.build(review_params)
if #review.save
redirect_to reviews_path
else
render :new
end
end
def update
end
private
def review_params
params.require(:review).permit(:title, :date, :content, :user_id, :album_id, album_attributes:[:artist, :title])
end
end
Here is my albums show page:
<% if #album.avatar.attached? %>
<image src="<%=(url_for(#album.avatar))%>%" style="width:350px;height:350px;">
<% end %>
<br>
<%= #album.artist %> -
<%= #album.title %>
<br>
<%= link_to "Edit Album", edit_album_path %>
<br><br><br>
<%= render '/reviews/form' if logged_in? %>
<h3>Album Reviews</h3>
<% #album.reviews.each do |review| %>
<%= review.title %>
<%= review.content %>
<% end %>
<br><br><br>
<%= link_to "All Albums", albums_path %><br>
<%= link_to "Upload a New Album", new_album_path %>
Here is my routes file:
Rails.application.routes.draw do
get '/signup' => 'users#new', as: 'signup'
post '/signup' => 'users#create'
get '/signin' => 'sessions#new'
post '/signin' => 'sessions#create'
get '/signout' => 'sessions#destroy'
resources :albums do
resources :reviews, except: [:index]
end
resources :users
resources :reviews, only: [:index]
root to: "albums#index"
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end
Here are my Models:
class Album < ApplicationRecord
has_many :reviews
has_many :users, through: :reviews
has_one_attached :avatar
end
class Review < ApplicationRecord
belongs_to :album, optional: true
belongs_to :user
accepts_nested_attributes_for :album
end
class User < ApplicationRecord
has_secure_password
has_many :reviews
has_many :albums, through: :reviews
accepts_nested_attributes_for :reviews
end
Review form:
<%= form_for :review, method: "POST",:url => { :action => :create } do |f| %>
<!-- <%= f.collection_select :album_id, Album.order(:artist),:id,:artist %> -->
<br>
<%= f.label :title %>
<%= f.text_field :title%>
<br><br>
<%= f.label :date %>
<%= f.datetime_field :date%>
<br><br>
<%= f.label :content %>
<%= f.text_area :content %>
<br><br>
<%= f.submit "Submit" %>
<% end %>
The problem is the iteration, because you use the instance variable coming from the controller instead of the block variable, it should be:
<% #album.reviews.each do |review| %>
<%= review.title %>
<%= review.content %>
<% end %>
#review is a new instance of a review (empty) which you created for the form I guess.
So instead of showing each review's title and content you showed the one from the empty review saved in #review in each iteration.

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! :)

Strong parameters with polymorphic associations

I'm building a polymorphic comment system per railscasts episode 154. I can't seem to get the strong parameters to accept the right field. In the POST action the parameters come through like this:
{"utf8"=>"✓",
"authenticity_token"=>"/yVWatJSRY1AmAqgbS4Z9S8kIlfQAKBbUeHc/5coxeM=",
"comment"=>{"content"=>"Hello"},
"commit"=>"Create Comment",
"user_id"=>"1"}
And My MVC I will post below. Does anyone know the correct way to use strong parameters in this case?
Model:
class Link < ActiveRecord::Base
has_many :votes
belongs_to :user
has_many :comments, as: :commentable
end
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
end
Controller
class CommentsController < ApplicationController
before_action :load_commentable
def index
#comments = #commentable.comments
end
def new
#comment = #commentable.comments.new
end
def create
#comment = #commentable.comments.new(params.require(:comment [:content]))
if #comment.save
redirect_to [#commentable, :comments], notice: "Comment created."
else
render :new
end
end
def load_commentable
resource, id = request.path.split('/')[1,2]
#commentable = resource.singularize.classify.constantize.find(id)
end
end
View
<h1>New Comment</h1>
<%= form_for [#commentable, #comment] do |f| %>
<% if #comment.errors.any? %>
<div class="error_messages">
<h2>Please correct the following errors.</h2>
<ul><% #comment.errors.full_messages.each do |msg| %>
<li><%= msg %>
<% end %>
</ul>
</div>
<% end %>
<div class="field" >
<%= f.text_area :content, rows: 8 %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
It should't be any different then any other controller in terms of strong params.
does
#comment = #commentable.comments.new(params.require(:comment).permit(:content))
not work?

Rails polymorphic to nested polymorphic Mass Assignment Security Error

New to rails and this one has been troubling me for a while now. All the tutorials on polymorphic associations seem to be only two levels and never a polymorphic model nested in a polymorphic model. really need help on this.
I have a app that has post, comment and link models. Both comments and links are polymorphic as I want to add link urls to both comments and posts, as well as comments to other things, similar to how facebook works. I want to nest the links model so can send as one form. Post and links work but comments and links has an MassAssignmentSecurity error.
Erorr
ActiveModel::MassAssignmentSecurity::Error in CommentsController#create
Can't mass-assign protected attributes: link
Request Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"
J1JfIINrtvax77M3JcbDDWvHFpyGG1ciK1DisGOLu6M=",
"comment"=>{"content"=>"z",
"link"=>{"link_url"=>"z"}},
"commit"=>"Add comment",
"forum_post_id"=>"16"}
Routes
resources :forum_posts do
resources :comments
resources :links
end
resources :comments do
resources :links
end
Forum_Post Model
class ForumPost < ActiveRecord::Base
attr_accessible :content, :links_attributes, :comments_attributes , :link_url
has_many :links, :as => :linkable
has_many :comments, :as => :commentable
accepts_nested_attributes_for :links, :allow_destroy => true #, :reject_if => lambda { |t| t[:link].nil?}
end
Comment Model
class Comment < ActiveRecord::Base
attr_accessible :commentable_id, :commentable_type, :content,:links_attributes, :link_url
belongs_to :commentable, :polymorphic => true
has_many :links, :as => :linkable
accepts_nested_attributes_for :links, :allow_destroy => true #, :reject_if => lambda { |t| t[:link].nil?}
end
Link Model
class Link < ActiveRecord::Base
attr_accessible :description, :image_url, :link_url, :linkable_id, :linkable_type, :title, :link_id
belongs_to :linkable, :polymorphic => true
end
Forum_Post Controller
class ForumPostsController < ApplicationController
...............
def new
#forum_post = ForumPost.new
#link = #forum_post.links.build
respond_to do |format|
format.html # new.html.erb
format.json { render json: #forum_post }
end
end
........
end
Comments Controller
class CommentsController < ApplicationController
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
def index
#commentable = find_commentable
#comments = #commentable.comments
end
def create
#commentable = find_commentable
#comment = #commentable.comments.build(params[:comment])
#comment.links.build
if #comment.save
flash[:notice] = "Successfully saved comment."
redirect_to :id => nil
else
render :action => 'new'
end
end
end
Links Controller
class LinksController < ApplicationController
def find_linkable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
def index
#linkable = find_linkable
#links = #linkable.links
end
def create
#linkable = find_linkable
#link = #linkable.link.build(params[:link])
if #link.save
flash[:notice] = "Successfully saved link."
redirect_to :id => nil
else
render :action => 'new'
end
end
end
Comment Partial View
<h2>Comments</h2>
<% if commentable.comments.empty? %>
No comments to display.
<% else %>
<% for comment in commentable.comments %>
<%= comment.content %>
<% end %>
<% end %>
<% :link_url %>
<h2>New Comment</h2>
<%= form_for [#commentable, Comment.new] do |f| %>
<div class="field">
<%= f.label :content %><br />
<%= f.text_area :content, :rows => 5 %>
</div>
<%= f.fields_for [#linkable, Link.new] do |link| %>
<%= render :partial => 'links/link', :locals => { :f => link } %>
<% end%>
<div class="actions">
<%= submit_tag "Add comment" %>
</div>
<% end %>
Link Partial View
<h2>Link Previews</h2>
<div class="field">
<%= f.label :link_url %><br />
<%= f.text_field :link_url, :id => "url_field" %>
</div>

Resources