Strong parameters with polymorphic associations - ruby-on-rails

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?

Related

How to implement presence validation for nested model in Rails?

Full source code is here https://github.com/tenzan/postfile
Creating a post working fine.
I have a parent element "Conversation" and its child/nested element "Post".
When I click on "Create Post" with nothing entered, it should throw an error "Body can't be blank".
Instead, it giving another error:
conversation.rb:
class Conversation < ApplicationRecord
belongs_to :contact
has_many :posts
end
post.rb:
class Post < ApplicationRecord
belongs_to :conversation
belongs_to :author, polymorphic: true
has_rich_text :body
validates :body, presence: :true
end
posts_controller.rb:
class PostsController < ApplicationController
before_action :authenticate_user!
before_action :set_conversation
def create
#post = #conversation.posts.new(post_params)
#post.author = current_user
respond_to do |format|
if #post.save
format.html { redirect_to #conversation }
end
end
end
private
def set_conversation
#conversation = Conversation.find(params[:conversation_id])
end
def post_params
params.require(:post).permit(:body)
end
end
I show all posts within from conversation's show.html.erb:
<p id="notice"><%= notice %></p>
<p>
<strong>Subject:</strong>
<%= #conversation.subject %>
</p>
<p>
<strong>Contact:</strong>
<%= link_to #conversation.contact.name, #conversation.contact %>
</p>
<%= link_to 'Edit', edit_conversation_path(#conversation) %> |
<%= link_to 'Back', conversations_path %>
<div id="posts">
<%= render #posts %>
</div>
<%= render partial: "posts/form", locals: { conversation: #conversation, post: Post.new } %>
Posts's partial _form.html.erb:
<%= form_with model: [conversation, post], id: "form" do |form| %>
<div>
<% form.object.errors.full_messages.each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<br>
<%= form.rich_text_area :body %>
<%= form.submit %>
<% end %>
Full source code is here https://github.com/tenzan/postfile
Thanks in advance.
You have this block in your posts_controller, which is where your error is arising:
respond_to do |format|
if #post.save
format.html { redirect_to #conversation }
end
end
Inside a respond_to block, you should have blocks identified by the format type, but you've added an if statement at that top level of the block where Rails is expecting a format.xxx. Move the if outside your respond_to block and you should be fine:
if #post.save
respond_to do |format|
format.html { redirect_to #conversation }
end
else
DO SOMETHING WITH THE ERROR
end
(Also NB that you should handle the error if the post doesn't save, even if it's just to say "Sorry, please try again".)

Trying to create comments inside posts - rails

I'm traying to create a social media with posts and comments inside the posts.
I'm trying to link the comments to the posts but I'm not going well. I dont know if the bug is from the controllers or the views. That`s what I have:
Comment model:
class Comment < ApplicationRecord
belongs_to :user
belongs_to :post
end
post model:
class Post < ApplicationRecord
belongs_to :user
has_many :comments
end
routes:
resources :posts do
member do
resources :comments
end
end
posts controller (the idea is to show the comment form and show inside the posts, and make a kind of feed, like facebook, instagram.. ):
class PostsController < ApplicationController
def index
#posts = Post.includes(:user).order(updated_at: :desc)
#comment = Comment.new
end
def new
#post = Post.new
end
def create
#post = Post.new(post_params)
#post.user = current_user
if #post.save
redirect_to request.referrer
# redirect_to feed_users_path
else
render :new
end
end
private
def post_params
params.require(:post).permit(:content, :user)
end
end
comments controller:
class CommentsController < ApplicationController
def create
#comment = Comment.new(comment_params)
#comment.user = current_user
#comment.post = #post
if #comment.save
redirect_to request.referrer
end
end
private
def comment_params
params.require(:comment).permit(:content, :post, :user)
end
end
this is the post index:
<% #posts.each do |post| %>
<div class="row" id="post-<%= post.id %>">
<div class="col-12 post-container">
<!-- header -->
<div class="post-header d-flex justify-content-start">
<%= link_to user_path(post.user) do %>
<h5><%= post.user.first_name %> <%= post.user.last_name %></h5>
<% end %>
<p>
<%= link_to user_path(post.user) do %>
#<%= post.user.first_name %>
<% end %>
<span><%= distance_of_time_in_words(Time.now, post.created_at) %></span> ago
</p>
</div>
<!-- content -->
<div class="post-content">
<div class="post-text">
<h4><%= post.content %></h4>
</div>
</div>
<hr>
<div class="form--input">
<%= simple_form_for([#post.comment], remote: true) do |f| %>
<div class="form--input">
<%= f.input :content, input_html: { class: "form-txt py-3" }, placeholder: "What do you want to comment?", label: false %>
</div>
<div class="form--input_elements d-flex justify-content-between mx-4">
<div class="form--input_submit">
<%= f.submit "Post Comment", class: "btn btn-flat", id: "post-submit", role: "status", remote: true %>
</div>
</div>
<% end %>
</div>
</div>
</div>
<% end %>
You have a problem in the routes file, it should be like this :
resources :posts do
resources :comments
end

form_for nested resources(post, comment)

I have one question about form_for with nested resources. I create something like blog, with posts,comments, comments on comments(like replies).And I have an issue. Then I try to make comment it: "Redirected to http://localhost:3000/
Filter chain halted as :get_parent rendered or redirected
Completed 302 Found"
new.html.erb for comments:
<div class= "container" %>
<%= form_for #comment do |f| %>
<%= f.input :title %>
<%= f.text_area :body %>
<%= f.submit %>
<% end %>
</div>
My comments controller:
before_filter :get_parent
def new
#comment = #parent.comments.build
end
def create
#comment = #parent.comments.build(params[:comment])
#comment.user_id = current_user.id
if #comment.save
redirect_to posts_path(#comment.post), :notice => 'Thank you for your comment!'
else
render :new
end
end
private
def comment_params
params.require(:comment).permit(:body, :title, :user_id, :commentable_id, :commentable_type)
end
def get_parent
#parent = Post.find_by_id(params[:post_id]) if params[:post_id]
#parent = Comment.find_by_id(params[:comment_id]) if params[:comment_id]
redirect_to root_path unless defined?(#parent)
end
end
post model:
has_many :comments, as: :commentable
belongs_to :user
def post
commentable.is_a?(Post) ? commentable : commentable.post
end
comment model:
belongs_to :user
belongs_to :commentable, polymorphic: true
has_many :comments, :as => :commentable
routes:
resources :posts do
resources :comments
end
resources :comments do
resources :comments
end
post_show.html.erb
<h1><%= #post.title %></h1>
<div class="body">
<%= #post.body %>
</div>
<h2>Comments</h2>
<p><%= link_to 'Add a Comment', new_post_comment_path(#post) %></p>
<ul class="comment_list">
<%= render :partial => 'comments/comment', :collection => #post.comments %>
</ul>
github repo with app: https://github.com/Dmitry96/dasasd
Your new form does not pass neither post_id nor comment_id parameter. It should be eather in the form action url or in the form body.
I can not see all the picture, but I think you have to add parent id to the form action url. It is /comments now, has no parent id parameter in it. It must be /posts/:post_id/comments or /comments/:comment_id/comments.
Change your form to:
<%= form_for [#parent, #comment] do |f| %>
<%= f.input :title %>
<%= f.text_area :body %>
<%= f.submit %>
<% end %>

Adding Comments to multiple models

Im trying to add comments to my topics model the same way you can add comments to posts on my app. i currently have to partials for comments _comment.html.erb and _form.html.erb
_comment :
<%= content_tag :div, class: 'media', id: "comment-#{comment.id}" do %>
<div class= "media">
<div class= "media-body">
<small>
<%= comment.user.name %> commented <%= time_ago_in_words(comment.created_at) %> ago
<% if user_is_authorized_for_comment?(comment) %>
| <%= link_to "Delete", [comment.post, comment], method: :delete %>
<% end %>
</small>
<p> <%= comment.body %></p>
</div>
</div>
<% end %>
_form :
<h4>Add a comment</h4>
<%= form_for [post, comment] do |f| %>
<div class="form-group">
<%= f.label :body, class: 'sr-only' %>
<%= f.text_field :body, class: 'form-control', placeholder: "Enter a new comment" %>
</div>
<%= f.submit "Submit Comment", class: 'btn btn-default pull-right' %>
<% end %>
my topic show is :
#DISPLAY Topic comments here
<h3> Comments</h3>
<%= render #topic.comments %>
</div>
<% if current_user %>
<%= render 'comments/form', comment: Comment.new, post: #post %>
<% end %>
#------
comment controller :
def create
#post = Post.find(params[:post_id])
comment = #post.comments.new(comment_params)
comment.user = current_user
if comment.save
flash[:notice] = "Comment saved successfully."
redirect_to [#post.topic, #post]
else
flash[:alert] = "Comment failed to save."
redirect_to [#post.topic, #post]
end
end
def destroy
#post = Post.find(params[:post_id])
comment = #post.comments.find(params[:id])
if comment.destroy
flash[:notice] = "Comment was deleted"
redirect_to [#post.topic, #post]
end
end
i have updated the routes for topic comments :
resources :topics do
resources :posts, except: [:index]
resources :comments, only: [:create, :destroy]
end
my question is do i need to create a separate partial to add comments to topics or can i update my _comment partial to work for both post and topic comments . and how can i accomplish this ?
Models
You'll need a polymorphic association on the Comment model:
#app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
end
#app/models/topic.rb
class Topic < ActiveRecord::Base
has_many :comments, as: :commentable
end
#app/models/post.rb
class Post < ActiveRecord::Base
has_many :comments, as: :commentable
end
Controllers
This will allow you to save the comments for your various models, the controllers / flow coming secondary:
#config/routes.rb
resources :topics, :posts do
resources :comments, only: [:create, :destroy] #-> url.com/topics/:topic_id/comments
end
#app/controllers/comments_controller.rb
class CommentsController < ApplicationController
def create
id = params[:post_id] || params[:topic_id]
if params[:post_id]
#parent = Post.find id
elsif params[:topic_id]
#parent = Topic.find id
end
#comment = #parent.comments.find params[:id]
#comment.save
end
def destroy
#parent = params[:post_id] || params[:topic_id]
#comment = #parent.comments.new comment_params
#comment.destroy
end
private
def comment_params
params.require(:comment).permit(:x, :y)
end
end
Because you're passing the data to the comments controller, you'll only need to evaluate which #parent you're working with.
Views
For your views, you need to pass locals to your _form partial:
#app/views/posts/show.html.erb
<%= render "comments/form", locals: {parent: #post} %>
#app/views/comments/_form.html.erb
<%= form_for [parent, parent.comments.new] do |f| %>
I had this road-block as well and here is what I came up with that passed. I must first give #Richard Peck applause for getting my wheels turning, so thank you :).
Models
Did not implement a polymorphic association. Stuck with has_many and belongs_to, nothing more
Partials
_comment.html.erb
Set up the delete partial to accept "parent" as a local
<div class="media">
<div class="media-body">
<small>
<%= comment.user.name %>
commented
<%= time_ago_in_words(comment.created_at) %>
ago
<% if user_is_authorized_for_comment_via_post?(comment) %>
|
<%= link_to "Delete", [parent, comment], method: :delete %>
<% end %>
</small>
<p>
<%= comment.body %>
</p>
</div>
</div>
_form.html.erb
same idea as _comment.html.erb, see above
<h4>Add a comment</h4>
<%= form_for [parent, comment] do |f| %>
<div class="form-group">
<%= f.label :body, class: 'sr-only' %>
<%= f.text_field :body, class: 'form-control', placeholder: "Enter a new comment" %>
</div>
<%= f.submit "Submit Comment", class: 'btn btn-default pull-right' %>
<% end %>
Setting up CommentController
...
def create
# upon clicking on create, determine what param id is passed
if params[:post_id]
# if it is a post id, set instance of post id as #parent
#parent = Post.find(params[:post_id])
elsif params[:topic_id]
# if it is a topic id, set instance of topic id as #parent
#parent = Topic.find(params[:topic_id])
end
# create instance as #comment. Build/create
# comment belonging to #parent (Topic or Post)
#comment = #parent.comments.build(comment_params)
# The comment must be associated to the current user.
# A comment must have a user, and value of user within instance of #comment
# is currently nil. Set user id as current user
#comment.user = current_user
# save comment to database
if #comment.save
# direction of save through if and elsif
# Redirection depends on the comment's parent.
# .is_a? method determines if it is of a certain class. Here, is #parent
# of class Post? Is #parents is the same parent id passed through params?
if #parent.is_a?(Post) # template error with this included: (== params[:post_id])
flash[:notice] = 'Comment saved successfully'
redirect_to [#parent.topic, #parent]
# if not part of the class Post, is it a Topic? If so, save here and
# redirect to the topic after save
elsif #parent.is_a?(Topic)
flash[:notice] = 'Comment saved successfully'
redirect_to #parent
end
end
end
def destroy
comment = Comment.find(params[:id])
# #topic = Topic.find(params[:topic_id])
# topic_comment = #topic.comments.find(params[:id])
# #post = Post.find(params[:post_id])
# post_comment = #post.comments.find(params[:id])
if comment.destroy
flash[:notice] = 'Comment was deleted'
redirect_to :back
else
flash[:alert] = "Comment counld't be deleted. Try again"
redirect_to :back
end
end
...
Passing in Comments from topic/show and post/show
topic/show
Note: notice how locals are passed into the controller from here
...
<div class="row">
<%= render 'comments/form', comment: Comment.new, parent: #topic %>
</div>
<% #topic.comments.each do |comment| %>
<%= render partial: 'comments/comment', locals: { parent: #topic, comment: comment } %>
<% end %>
...
post/show
<% if current_user %>
<% #post.comments.each do |comment| %>
<%= render partial: 'comments/comment', locals: { parent: #post, comment: comment } %>
<% end %>
<% end %>
<% if current_user %>
<%= render 'comments/form', comment: Comment.new, parent: #post %>
<% end %>
Hope this helps.

How do I make sure each post in the Post feed shown in the home page has all its comments displayed under it?

I have a Post model, and a Comment model that belongs to Post. I was able to display the Post in the home view corresponding to the home controller and home action and the User/show view. Thus, in the home and user views, the posts are listed in order of creation time. I was also able to have a post form in both the user and home views and a comment form in the home and user views.
The problem arises when I try to display the comment underneath each displayed Post in the home and user views. How can I list the comments associate with each post under the post in the home and user views ?
How do I make sure the comments in the databased are listed under the corresponding post ?
Here is my comments controller:
class CommentsController < ApplicationController
def index
#comments =Comment.all.paginate(page: params[:page])
end
def show
#comment = Comment.find(params[:id])
#post = #Comment.post
end
def new
end
def create
#comment = Comment.new(comment_params)
if #comment.save
flash[:success] = "Comment created"
redirect_to :back
else
render 'new'
end
end
def edit
#comment = Comment.find(params[:id])
end
def update
#comment = comment.find(params[:id])
if #comment.update_attributes(comment_params)
flash[:success] = "Comment updated"
redirect_to #comment.post
else
render 'edit'
end
end
def destroy
Comment.find(params[:id]).destroy
flash[:success] = "Comment deleted"
redirect_to users_url
end
private
def comment_params
params.require(:comment).permit(:author_name, :body)
end
end
Here is my home view: app/views/home/home.html.erb:
<% if logged_in? %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<%= render 'shared/user_info' %>
</section>
<hr/>
<br/>
<section class="stats">
<%= render 'shared/stats' %>
</section>
<section class="post_form">
<%= render 'shared/post_form' %>
</section>
</aside>
<div class="col-md-8">
<h3>Post Feed</h3>
<%= render 'shared/feed' %>
</div>
</div>
<% else %>
<div class="center jumbotron">
<h1>Welcome to the Unstarv website</h1>
<h2>
Please sign up now to use this site
<%= link_to "Sign Up", signup_path =%>
now.
</h2>
<%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %>
</div>
<%= link_to image_tag("rails.png", alt: "unstarv logo"), '#' %>
<% end %>
And here is my home controller:
class HomeController < ApplicationController
def home
if logged_in?
#post = current_user.posts.build
#feed_items = current_user.feed.paginate(page: params[:page])
end
end
def about
end
def privacy
end
def terms
end
end
And here is my Post model, the relevant part:
class Post < ActiveRecord::Base
belongs_to :user
has_many :comments
default_scope -> { order(created_at: :desc) }
mount_uploader :picture, PictureUploader
end
The relevant part of my User model:
class User < ActiveRecord::Base
attr_accessor :remember_token
before_save { self.email = email.downcase }
has_many :posts, dependent: :destroy
has_many :comments
has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent: :destroy
has_many :passive_relationships, class_name: "Relationship",
foreign_key: "followed_id",
dependent: :destroy
has_many :following, through: :active_relationships, source: :followed
has_many :followers, through: :passive_relationships, source: :follower
validates :username, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, length: { minimum: 6 }, allow_blank: true
def feed
following_ids = "SELECT followed_id FROM relationships WHERE follower_id = :user_id"
Post.where("user_id IN (#{following_ids}) OR user_id = :user_id", user_id: id)
end
end
And here is my Comment model:
class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :user
default_scope -> { order(created_at: :desc) }
end
And here is the post controller:
class PostsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
def index
#posts = Post.all
end
def show
#post = Post.find(params[:id])
#comment = Comment.new
#comment.post_id = #post.id
#comments = #post.comments.all
end
def new
#post = Post.new
end
def create
#post = current_user.posts.build(post_params)
if #post.save
flash[:success] = "Post created!"
redirect_to root_url
else
#feed_items = []
render 'home/home'
end
end
def edit
#post = Post.find(params[:id])
end
def update
#post = Post.find(params[:id])
#post.update(post_params)
flash.notice = "Post '#{#post.title}' Updated!"
render 'home/home '
end
def update
#post = Post.find(params[:id])
#post.update(post_params)
flash.notice = "Post '#{#post.title}' Updated!"
redirect_to root_url
end
private
# Use callbacks to share common setup or constraints between actions.
def set_post
#post = Post.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def post_params
params.require(:post).permit(:title, :body, :picture)
end
end
Here is my app/views/post/_post.html.erb file,
<li id="post-<%= post.id %>">
<span class="user"><%= link_to post.user.username, post.user %></span>
<span class="content">
<%= post.title %>
<%= post.body %>
<%= image_tag post.picture.url if post.picture? %>
</span>
<span class="timestamp">
Posted <%= time_ago_in_words(post.created_at) %> ago.
<% if current_user?(post.user) %>
<%= link_to "delete", post, method: :delete,
data: { confirm: "You sure?" } %>
<% end %>
</span>
<section>
<h2>Your Comments here</h2>
<h3>Post a Comment</h3>
<h3>Post a Comment</h3>
<%= render 'shared/comment_form' %>
<% post.comments.all.each do |comment| %>
<h4><small>Comment by</small> <%= comment.post.user.username %></h4>
<p class="comment"><%= comment.body %></p>
<p><small>Posted <%= distance_of_time_in_words(Time.now, comment.created_at) %> ago</small></p>
<br/>
<%end%>
</li>
And here is my app/views/shared/comment_form_html.erb , which works fine:
<%= form_for [ post, post.comments.build] do |f| %>
<p>
<%= f.label :body, "Your Comment" %><br/>
<%= f.text_area :body %>
</p>
<p>
<%= f.submit 'Submit' . method="post", class: 'btn btn-primary' %>
</p>
<% end %>
Here is my app/views/posts/_post.html.erb
<li id="post-<%= post.id %>">
<span class="user"><%= link_to post.user.username, post.user %></span>
<span class="content">
<%= post.title %>
<%= post.body %>
<%= image_tag post.picture.url if post.picture? %>
</span>
<span class="timestamp">
Posted <%= time_ago_in_words(post.created_at) %> ago.
<% if current_user?(post.user) %>
<%= link_to "delete", post, method: :delete,
data: { confirm: "You sure?" } %>
<% end %>
</span>
<h2>Comments</h2>
<h3>Post a Comment</h3>
<%= render 'shared/comment_form', post: post %>
<h3>Comments (<%= post.comments.size %>)</h3>
<% post.comments.each do |comment| %>
<h4><small>Comment by</small> <%= comment.post.user.username %></h4>
<p class="comment"><%= comment.body %></p>
<p>Posted <%= distance_of_time_in_words(Time.now, comment.created_at) %> ago</p>
</li>
<br/>
<%end%>
Thanks a lot for your help !!!!
The issue is related in how you are creating the comments.
With the actual code, the comment does not belong to any post so in your comments_controller change create action to something like:
def create
post = Post.find(params[:post_id])
#comment = post.comments.build(comment_params)
if #comment.save
flash[:success] = "Comment created"
redirect_to :back
else
render 'new'
end
end

Resources