I am attempting to add the ability for users to add comments to posts on my rails site.
I have a Posts table and a Users table in my database. I'm using resourceful routes to display individual posts on the 'show' action of my Posts controller. I want to be able to have a comment box show up under the post that the user can enter a comment into, then click submit and have it create the comment.
I tried making a model for comments, giving them a belongs_to relationship for both user and post. I also added the has_many relationship to the user and post models. I then tried to have a Comment Controller use a 'create' action in order to process a form on each posts' 'show' action.
I am running into the issue of not being able to get the post_id to inject into the new comment. I can get the user_id by grabbing it from the user's session, but the only thing passed to the 'create' action on the Comments Controller is the actual text of the comment through the form.
Is this a good way of going about adding this feature? There must be a better way of doing it, or maybe I'm just missing something.
My Posts Controller 'show' action:
#PostsController.rb
def show
#post = Post.where(:id => params[:id]).first
if #post.nil?
flash[:error] = "Post does not exist"
redirect_to(root_path)
end
#comment = Comment.new
end
The form on the 'show' view for the 'show' action on PostsController:
#views/posts/show.html.erb
<%= form_for #comment do |f| %>
<%= f.text_area(:content, :size => '20x10', :class => 'textarea') %>
<%= f.submit('Create Post', class: 'button button-primary') %>
<% end %>
My Comments Controller 'create' action:
#CommentsController.rb
def create
#comment = Comment.new(params.require(:comment).permit(:content, :post_id, :user_id))
#comment.user_id = session[:user_id]
#Need to set post_id here somehow
if #comment.valid?
#comment.save
flash[:success] = "Comment added successfully."
redirect_to(post_path(#comment.post))
else
#error = #comment.errors.full_messages.to_s
#error.delete! '[]'
flash.now[:error] = #error
render('posts/show')
end
end
My Post model:
class Post < ApplicationRecord
belongs_to :subject
belongs_to :user
has_many :comments
validates :title, :presence => true,
:length => {:within => 4..75}
validates :content, :presence => true,
:length => {:within => 20..1000}
end
My Comment model:
class Comment < ApplicationRecord
belongs_to :user
belongs_to :post
validates :content, :presence => true,
:length => {:within => 6..200}
end
In your post controller show action, make the new comment belong to the post
def show
#post = Post.where(:id => params[:id]).first
if #post.nil?
flash[:error] = "Post does not exist"
redirect_to(root_path)
end
#comment = #post.comments.new # <--- here's the change
end
Then add the post_id field to the form as a hidden field
<%= form_for #comment do |f| %>
<%= f.hidden_field :post_id %>
<%= f.text_area(:content, :size => '20x10', :class => 'textarea') %>
<%= f.submit('Create Post', class: 'button button-primary') %>
<% end %>
And you should be good to go without changing the comment controller create action
Related
I have an model called assignment, which represents a rich join between users and questions in a multiple choice quiz app. when a user answers a question, the response is recorded in the following update method in the assignments controller
def edit
#assignment = Assignment.find_by_id(params[:id])
#user = #assignment.user
#question = #assignment.question
puts "user #{#user} question #{#question} assignment #{#assignment}"
end
def update
#assignment = Assignment.find(params[:id])
#user = #assignment.user
#assignment.update_attributes(:response => params[:assignment][:response])
if #assignment.save
flash[:notice] = "Your response has been saved"
redirect_to(:action => 'show', :id => #assignment.id , :user_id => #user.id)
else
puts #assignment.save
puts "could not save"
render(user_assignment_path(#user, #assignment) , :html => {:method => :get})
end
end
I have a before save called grade that gets called. Here is the model:
class Assignment ActiveRecord::Base
belongs_to :user
belongs_to :question
attr_accessible :title, :body, :user_id, :question_id , :response
before_save :grade
def grade
self.correct = (response == self.question.solution) unless response == nil
end
end
So the first time I submit a response, the save works perfectly and it redirects me accordingly. after that, if i try to edit the question again and resubmit the form, the save fails.
Can anyone think of a reason this might be occurring?
In addition I know there is an error in the second redirect, so if someone could correct my usage that would be a bonus help
EDIT Here is the Edit erb, in case someone can spot something wrong here. I also added the edit method in the controller above.
<div class="admin-display">
<%if #admin%>
<p>
You may edit the user's response below or change the question to override whether the question is marked correct.
</p>
<%end%>
</div>
<div class="question body">
<%= #question.question %>
</div>
<div class="answer-choices">
<%= params[:user_id] + " " + params[:id] %>
<ol type="A">
<%=form_for(#assignment , :url => user_assignment_path(params[:user_id] , params[:id]) , :html => {:method => "put"}, :user_id => params[:user_id]) do |f|%>
<%[#question.answerA, #question.answerB ,#question.answerC ,#question.answerD].each do |choice|%>
<li>
<%= f.radio_button(:response, choice)%>
<%= choice %>
</li>
<%end%>
</ol>
<div class="form-buttons">
<%= submit_tag("Submit Response") %>
</div>
<%end%>
</div>
EDIT 2 I have just gone through the steps by hand in the rails console, without any issue, so there must be something strange going on.
It's probably due to the fact that your grade() callback method is returning a false, which will cancel the action all together (as stated in the documentation).
Hope this will work for you.
In Assignment model, add correct field as attr_accessible. First time as the response is nil, it is not executing the statement in before_save method, Hence your code will be
class Assignment ActiveRecord::Base
belongs_to :user
belongs_to :question
attr_accessible :title, :body, :user_id, :question_id , :response, :correct
before_save :grade
def grade
correct = (response == question.solution) unless response.nil?
end
end
and cotroller action could be
def update
#assignment = Assignment.find(params[:id])
#user = #assignment.user
#assignment.update_attributes(:response => params[:assignment][:response])
if #assignment.valid?
flash[:notice] = "Your response has been saved"
redirect_to(:action => 'show', :id => #assignment.id , :user_id => #user.id)
else
render(user_assignment_path(#user, #assignment) , :html => {:method => :get})
end
end
for now i've got followings:
model => User (name, email)
has_and_belongs_to_many :trips
model => Trip (dest1, dest2)
has_and_belongs_to_many :users
validates :dest1, :dest2, :presence => true
model => TripsUsers (user_id, trip_id) (id => false)
belongs_to :user
belongs_to :trip
As you see from the code, trip model has validation on dest1, and dest2, but it's not showing up an errors. Controller and view defined as follow:
trips_controller.rb
def new
#user = User.find(params[:user_id])
#trip = #user.trips.build
end
def create
#user = User.find(params[:user_id])
#trip = Trip.new(params[:trip])
if #trip.save
#trip.users << #user
redirect_to user_trips_path, notice: "Success"
else
render :new
end
end
_form.html.erb
<%= simple_form_for [#user, #trip] do |f| %>
<%= f.error_notification %>
<%= f.input :dest1 %>
<%= f.input :dest2 %>
<%= f.submit "Submit" %>
<% end %>
According to the rails guide on presence validation, it can't be used with associated objects. Try to use a custom validation:
validate :destinations_presence
def destinations_presence
if dest1.nil?
errors.add(:dest1, "missing dest1")
elsif dest2.nil?
errors.add(:dest1, "missing dest2")
end
end
Currently I am have a comment which belongs to a micropost, but the issue is, when a user creates a comment, the comment gets stored in the database with a micropost id but the id is not for the specific micropost rather it seem as though the comment just incremented the micropost id by + 1. Very confused and would very much appreciate any help. Thank you!
Comment Model
class Comment < ActiveRecord::Base
attr_accessible :content, :user_id, :micropost_id
belongs_to :micropost
belongs_to :user
validates :content, presence: true, length: { maximum: 140 }
default_scope order: 'comments.created_at DESC'
end
Micropost Model
class Micropost < ActiveRecord::Base
attr_accessible :title, :content, :view_count
belongs_to :user
has_many :comments
accepts_nested_attributes_for :comments
end
Comments Controller
class CommentsController < ApplicationController
def create
#micropost = Micropost.find(params[:micropost_id])
#comment = #micropost.comments.build(params[:comment])
#comment.user_id = current_user.id
#comment.save
respond_to do |format|
format.html
format.js
end
end
end
Form
<div class="CommentField">
<%= form_for ([#micropost, #micropost.comments.new]) do |f| %>
<%= f.text_area :content, :class => "CommentText", :placeholder => "Write a Comment..." %>
<div class="CommentButtonContainer">
<%= f.submit "Comment", :class => "CommentButton b1" %>
</div>
<% end %>
</div>
Routes
resources :microposts do
resources :comments
end
Raked Routes
micropost_comments GET /microposts/:micropost_id/comments(.:format) comments#index
POST /microposts/:micropost_id/comments(.:format) comments#create
new_micropost_comment GET /microposts/:micropost_id/comments/new(.:format) comments#new
edit_micropost_comment GET /microposts/:micropost_id/comments/:id/edit(.:format) comments#edit
micropost_comment GET /microposts/:micropost_id/comments/:id(.:format) comments#show
PUT /microposts/:micropost_id/comments/:id(.:format) comments#update
DELETE /microposts/:micropost_id/comments/:id(.:format) comments#destroy
I think the issue here is how much work you are putting into this. Rails is built to know about most of this without the need to do what you are doing. My suggestion would be to change your comments controller to something like this
class CommentsController < ApplicationController
def create
#comment = Comment.new(params[:comment])
#comment.save
respond_to do |format|
format.html
format.js
end
end
end
since you are rendering your comments partial form through another partial you'll need to pass along the local variable of the associated post above it.
"comments/form", :locals => { :micropost => micropost } %>
and your form to something like this
<div class="CommentField">
<%= form_for ([micropost, #comment]) do |f| %>
<%= f.text_area :content, :class => "CommentText", :placeholder => "Write a Comment..." %>
<div class="CommentButtonContainer">
<%= f.submit "Comment", :class => "CommentButton b1" %>
</div>
<% end %>
</div>
In all my rails apps, that is all the association I would need to do for it to properly assign the Ids by itself. I'm sure that will fix the issue.
I am having troubles with a polymorphic association in Rails. I have an application where it should be possible to comment on different models, such as Posts, Images, Projects
Right now I just have Posts to comment on. On the start page there is an index view of the latest Posts and each Post has a small Comment form underneath to comment on via Ajax, very much like Facebook.
My models look like this:
class Post < ActiveRecord::Base
belongs_to :post_category
belongs_to :user
has_many :comments, :as => :commentable
validates_presence_of :user_id
validates_presence_of :post_category_id
validates_presence_of :title
validates_presence_of :body
end
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :commentable, :polymorphic => true
end
Now in my Comments controller I added the following method (I think I took it from railscasts or something), which I assume tries to find out the #commentable dynamically when creating an comment.
But this always returns the error undefined methodcomments' for nil:NilClass`
# find commentable (parent) item
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value) unless name == 'user_id'
end
end
nil
end
def create
#commentable = find_commentable
#comment = #commentable.comments.build(params[:comment])
if #comment.save
redirect_to #comment, :notice => 'Comment was successfully created.'
redirect_to :id => nil
else
render :action => "new"
end
end
The two things I tried in my partial were:
leaving the commentable info out of the form
= form_for [#commentable, Comment.new], :remote => true do |f|
#new_comment.add_comment
= f.hidden_field :user_id, :value => current_user.id
= f.text_field :content, :size => 55, :value => 'leave a comment...', :class => 'comment_form'
= f.submit "send"
and 2. passing the commentable_id and commentable_type
= form_for [#commentable, Comment.new], :remote => true do |f|
#new_comment.add_comment
= f.hidden_field :user_id, :value => current_user.id
= f.hidden_field :commentable_id, :value => post_id
= f.hidden_field :commentable_type, :value => 'Post'
= f.text_field :content, :size => 55, :value => 'leave a comment...', :onfocus => 'this.select()', :class => 'comment_form'
= f.submit "send"
both without luck. Any help would be highly appreciated.
the whole comments controller code is in this gist: https://gist.github.com/1334286
It seems like the commentable is not assigned correctly in the comments controller. This could have multiple reasons. Here is a setup that should work for you:
In the Posts controller, e.g. action "show":
#post = Post.find(params[:id])
In the posts/show view comments form:
= form_for [#post, #post.comments.new], :remote => true do |f|
You should be able to use your comments controller as it - but you should change the render to e.g. a redirect_to :back in the create action since the comments controller will most probably not have a "new" view on its own (it is dependent from the commentable)
Also, make sure that you have nested routes for all resources that can act as a commentable, like so:
resources :posts do
resources :comments do
end
resources :comments do
resources :comments # subomments
end
UPDATED to reflect information in the comments
Don't use #commentable in the post show view, since it's only defined in the comments controller.
Do this instead:
_comment.html.erb: (the comment partial in the post show view)
<%= form_for ([comments, #vote]), remote: true do |f| %>
posts/posts_controller.rb:
<%= form_for ([#post, #vote]), remote: true do |f| %>
I have a very basic rails app. I am playing around with validation.
Controller
class PagesController < ApplicationController
def new
#user = User.new
end
def edit
#user = User.new(:state => params[:state], :country => params[:country])
#user.save
end
end
Model
class User < ActiveRecord::Base
validates_presence_of :country
validates_presence_of :state
end
Views/pages/edit.html.erb
<% form_for :user, #user, :url => { :action => "edit" } do |f| %>
<%= f.text_field :country %>
<%= f.text_field :state %>
<%= submit_tag 'Create' %>
<% end %>
All I want to do is click Create when I have not entered anything and then have a validation come up and list the required fields. I've read some tutorials and they make it so simple. Why can't I get this to work? what am i doing wrong? When i create a scaffold then it works ok but that generates a scaffold.css in public/stylesheets. W/out scaffold right now i have no stylesheet in the public folder.
you're sending the form to the "edit" action, which doesn't do any processing. You need it to go to the "create" action, which should look something like this:
def create
#user = User.new(params[:user])
if #user.save
flash[:notice] = 'Your user was successfully created.'
redirect_to users_path
else
render :action => 'edit'
end
end
Your form_for line can be short and sweet. Also, you need to call error_messages to get the auto-generated list of errors:
<% form_for #user do |f| %>
<%= f.error_messages %>
...other fields go here...
<% end %>
See Rails conditional validation: if: doesn't working
It seems like you think validates ... if: works differently as it actually does. This line
validates :to_id, presence: true, if: :from_different_to?
translates to validate that the to_id is present if the from_different_to method returns true. When from_different_to evaluates to false then do not validate.