I will have an option were comment model can be used for a user to post in a person profile page and community page. Currently I'm working on community and would like some direction as I'm confused.
Current error I get is ActiveModel::ForbiddenAttributesError for my CommentsController#Create. Would like help if possible or help me point in the direction of fixing my mistake.
questions in regards to what view people are seeing comment is /communities/show
Models
User
has_one :profile
has_many :communities
has_many :comments, dependent: :destroy
Community
extend FriendlyId
friendly_id :title, use: [:slugged, :finders]
has_many :comments, dependent: :destroy
belongs_to :user
Comment
belongs_to :user
belongs_to :community
Routes
resources :communities do
resources :comments
end
Controllers
Communities
def show
#community = Community.friendly.find(params[:id])
#current_user = User.find(session[:user_id])
#comment = Comment.new
end
Comments
before_filter :load_community
def create
#comment = #community.comments.build(params[:comment])
#comment.user_id = current_user.id
if #comment.save
redirect_to :back
else
redirect_to "/"
end
# #comment = Comment.new(comment_params)
# #comment.user_id = session[:user_id]
# if #comment.save && #comment.community_id
# flash[:notice] = "Comment has been posted"
# else
# flash[:alert] = #comment.errors.full_messages
# end
end
private
def load_community
#community = Community.friendly.find(params[:community_id])
end
def comment_params
params.require(:comment).permit(:text, :user_id, :community_id, :profile_id)
end
Views
/communities/show
<%= render "profiles/index" %>
<h4><%= #community.title.capitalize! %></h4>
<%= #community.bio %>
<%= render "comments/new" %>
/comments/_new
<%= form_for ([#community, #comment]) do |f| %>
<%= f.text_area :text, placeholder: "Enter New Comment Here ...", :cols => 50, :rows => 3, :class => 'text_field_message', :id => 'new_comment' %>
<%= f.submit :class => 'new_comment_button' %>
<% end %>
Thank you everyone who helps explain where I'm making my mistake and also sorry in advance if I may need to ask what you might be requesting from me. For further questions please ask.
UPDATE
What I see in my console is
Started POST "/communities/dang/comments" for 127.0.0.1 at 2015-10-23 18:38:47 -0400
Processing by CommentsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"us8KNTLUZUdao13GK4OQId0YoUqf+CeLFIGjydnyWtI=", "comment"=> {"text"=>"www"}, "commit"=>"Create Comment", "community_id"=>"dang"}
Community Load (0.1ms) SELECT "communities".* FROM "communities" WHERE "communities"."slug" = 'dang' ORDER BY "communities"."id" ASC LIMIT 1
Completed 500 Internal Server Error in 11ms
ActiveModel::ForbiddenAttributesError (ActiveModel::ForbiddenAttributesError):
app/controllers/comments_controller.rb:17:in `create'
Okay.
ActiveModel::ForbiddenAttributesError for my CommentsController#Create
This basically means you're not permitting the required attributes in your create method.
This is what you need:
#app/controllers/comments_controller.rb
class CommentsController < ApplicationController
def create
#comment = #community.comments.new comment_params
#comment.save
end
private
def comment_params
params.require(:comment).permit(:text).merge(user_id: current_user.id)
end
end
You should read up on strong params to better understand how this works.
Polymorphic
You also have another issue which can be solved with a polymorphic association:
Simply, this allows you to associate a model with any number of others.
In your instance, where you can comment on users and communities, this functionality will serve well:
#app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :commentable, polymorphic: true
end
#app/models/user.rb
class User < ActiveRecord::Base
has_many :sent_comments, class_name: "Comment", foreign_key: :user_id
has_many :comments, as: :commentable
end
#app/models/community.rb
class Community < ActiveRecord::Base
has_many :comments, as: :commentable
end
This will allow you to perform the following:
#user = User.find params[:id]
#user.comments.new comment_params
def comment_params
params.require(:comment).permit(:text).merge(user_id: current_user.id) #-> current_user from Devise
end
This allows you to use #user.comments and #community.comments with a single association.
You'll have to migrate the commentable_id & commentable_type columns into your comments table, but after that the above code should work.
Related
I have issue when create nested model in Rails 6:
post.rb
class Post < ApplicationRecord
has_many :post_votes, dependent: :destroy
end
post_vote.rb
class PostVote < ApplicationRecord
belongs_to :posts
end
routes.rb
resources :posts do
resources :post_votes
end
views:
<%= button_to post_post_votes_path(post), method: :post, remote: true, form_class: "post_vote" do%>
<%= bootstrap_icon "arrow-up-circle", width: 20, height: 20, fill: "#333" %>
<%end%>
PostVost Controller
def create
#post = Post.find(params[:post_id])
#post_vote = PostVote.new
if already_voted?
# flash[:notice] = "You can't vote more than once"
redirect_to root_path
else
#post_vote = #post.post_votes.build(user_id: current_user.id)
end
# redirect_to post_path(#post)
respond_to do |format|
format.html {}
format.js
end
end
def already_voted?
PostVote.where(user_id: current_user.id, post_id: params[:post_id]).exists?
end
I check the log file, no record was update in database
Any one known why i can not create new post_vote model?
Thank you so much!
On this line:
#post_vote = #post.post_votes.build(user_id: current_user.id)
.build only creates the object in memory. It does not persist it to the database.
Try:
#post_vote = #post.post_votes.create(user_id: current_user.id)
or
#post_vote = #post.post_votes.create!(user_id: current_user.id)
if you want an exception to be thrown if persistence fails.
The problem is using belong_to without optional
class PostVote < ApplicationRecord
belongs_to :posts
end
After:
class PostVote < ApplicationRecord
belongs_to :posts, optional: true
end
Rails 5, I am trying to add Likes from scratch without a gem dependency. I am so close to having it down but am being completely stumped by what's happening when Comments get involved.
Storing and saving article.likes worked perfectly. Then I got the comment.likes to work. Now, when I like a Comment, they individually store a new Like (great!), except now Article is not properly saving any likes, and even weirder: article.likes.count gives a TOTAL sum of all it's comments' likes.. I am sure this is an easy fix but I am just totally missing it and I've tried everything. I've gone down some deep rabbit holes for this.
I think the problem lies in routing, or how the models relate.
Articles have many Comments, and both of them have many Likes.
Here are the models starting with like.rb:
class Like < ApplicationRecord
belongs_to :user
belongs_to :article
belongs_to :comment
# Make sure that one user can only have one like per post or comment
validates :user_id, uniqueness: { scope: [:article_id, :comment_id] }
end
article.rb
class Article < ApplicationRecord
belongs_to :user
...
# destroy associated comments on article deletion
has_many :comments, dependent: :destroy
has_many :likes, dependent: :destroy
end
comment.rb
class Comment < ApplicationRecord
belongs_to :article
belongs_to :user
...
has_many :likes, dependent: :destroy
end
routes.rb
...
resources :articles, :path => 'blog' do
resources :likes, only: [:create, :destroy]
resources :comments do
resources :likes, only: [:create, :destroy]
end
end
the meaty likes_controller.rb. Mind the #{}, EVERYTHING checks out the way it should, so why does the comment.likes.create save correctly, but the article.likes.create does not?? Help, please.
class LikesController < ApplicationController
before_action :get_signed_in_user
before_action :comment_or_article
def create
if #comment_like
like_resource(comment)
else
like_resource(article)
end
end
def destroy
if #comment_like
comment.likes.where(user: current_user).destroy_all
else
article.likes.where(user: current_user).destroy_all
end
flash[:success] = "Unliked! :("
redirect_to article_redirect(article)
end
private
def like_resource(obj)
if obj.likes.where(user: current_user.id).present?
flash[:error] = "You've already upvoted this! + #{like_params} + #{#comment_like} + #{obj}"
if #comment_like
if obj.likes.create(user: current_user, article: #article)
flash[:success] = "Upvoted Comment! + #{like_params} + #{#comment_like} + #{obj}"
else
flash[:error] = "An error prevented you from upvoting."
end
elsif obj.likes.create(user: current_user)
flash[:success] = "Upvoted Blog! + #{like_params} + #{#comment_like} + #{obj}"
else
flash[:error] = "An error prevented you from upvoting."
end
redirect_to article_path(article)
end
def get_signed_in_user
unless user_signed_in?
flash[:error] = "Sign in to upvote!"
redirect_back(fallback_location: articles_path)
end
end
# decide what we are liking
def comment_or_article
if comment
#comment_like = true
else
#comment_like = false
end
end
def article
#article = Article.find(params[:article_id])
end
def comment
unless params[:comment_id].nil?
#comment = article.comments.find(params[:comment_id])
end
end
def like_params
params.permit( :article_id, :comment_id).merge(user_id: current_user.id)
end
end
Finally the like buttons in my articles/show.html.erb:
<%= link_to "Like", article_likes_path(#article), method: :post %>
<%= "#{#article.likes.count}" %>
... # inside loop to show comments:
<%= link_to "Like", article_comment_likes_path(#article, comment), method: :post %>
<%= "#{comment.likes.count}" %>
TLDR:
Comment likes work fine, save fine, count individually fine.
Article likes do not save, but article.likes.count === article.comments.likes.count. Why?
I want article.likes to be completely unique, like it's own comments are.
Thank you in advance.
EDIT: took out belongs_to :comments in like.rb and refactored like_controller.rb main function to
private
def like_resource(obj)
if obj.likes.where(user: current_user.id).present?
flash[:error] = "You've already upvoted this!"
elsif obj.likes.create(user: current_user, article: #article)
flash[:success] = "Upvoted!"
else
flash[:error] = "An error prevented you from upvoting."
end
redirect_to article_path(article)
end
Always supplying the #article helps when liking a comment. An Article like would not need a comment_id to save.
Sorry for the long post, hopes this helps someone.
I just figured it out, actually.
class Like < ApplicationRecord
belongs_to :user
belongs_to :article
# belongs_to :comment
end
Commenting the above out, it allows the #article "like" to save without a comment being referenced. Article likes are saved properly. However, article.likes.count still increments whenever an article.comment is liked. This means article.likes is always >= article.comments.likes; which is completely fine.
I just changed the #article.likes to:
<%= "#{#article.likes.where(comment_id: nil).count}" %>
Filtering out all the strictly article.likes. The comment.likes still work perfectly.
I have what I thought is a simple event signup application. A user registers for the site and then can select an event, apply to participate in that event by updating some fields that are on the user model (At this point, just a first_name). A User can attend many Events, but must register (Participation) for each one. An Event can have many Users through Participations. Any help is greatly appreciated!
There are currently three models:
# user.rb
class User < ActiveRecord::Base
has_many :participations
has_many :events, through: :participations
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
# event.rb
class Event < ActiveRecord::Base
has_many :participations
has_many :users, through: :participations
end
# And a join table: participation.rb
class Participation < ActiveRecord::Base
belongs_to :user
belongs_to :event
accepts_nested_attributes_for :user
end
Here's my routes file:
routes.rb
Rails.application.routes.draw do
mount RailsAdmin::Engine => '/admin', as: 'rails_admin'
devise_for :users
root 'events#index'
resources :events do
resources :participations
end
resources :users
end
And I think the only applicable controller:
participations_controller.rb
class ParticipationsController < ApplicationController
def index
end
def new
#participation = Participation.new
#user = current_user
#event = Event.find(params[:event_id])
end
def create
#participation = Participation.new(participation_params)
if #participation.save
redirect_to events_path, notice: "You are registered!"
else
render action: 'new'
end
end
private
def participation_params
params.require(:participation).permit(:status, :user_attributes => [:id, :first_name])
end
end
The form should simply create a new participation based on the event_id, set its status, and update the user_attributes.
views/participations/new.html.erb
<%= form_for #participation, url: {action: "create"} do |f| %>
<%= f.label :status %><br />
<%= f.text_field :status %>
<%= f.fields_for :user, current_user do |builder| %>
<fieldset>
<%= builder.label :first_name, "First Name" %><br />
<%= builder.text_field :first_name %><br />
</fieldset>
<% end %>
<%= f.submit "Register" %>
<% end %>
Unfortunately, completing the form returns a 404 error with a missing participation_id.
Started POST "/events/1/participations" for ::1 at 2015-11-24 21:26:35 -0600
Processing by ParticipationsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"0vVTyeGwGUheZPbOoMeyUvr2ciJG2OpXwqToc2pYLr2HXDMhogX8llESiG8Z4Cc5Pq5sBmiHi43rvjHka7K3yA==", "participation"=>{"status"=>"done", "user_attributes"=>{"first_name"=>"sone", "id"=>"1"}}, "commit"=>"Register", "event_id"=>"1"}
Completed 404 Not Found in 3ms (ActiveRecord: 0.0ms)
ActiveRecord::RecordNotFound - Couldn't find User with ID=1 for Participation with ID=:
Well as described here, what is happening is that when you pass an id to the nested model, accepts_nested_attributes will look and try to update the model you are looking for.
So at that moment there is not such association between current user and the Participation you want to create, that's why you get the error:
Couldn't find User with ID=1 for Participation with ID=:
That means there is not such user with ID=1 associated with your participation
My suggestion:
Instead of add nested attributes for user, why not just add the fields you need to the Participation model?
Add the first_name attribute to your participation model and in your controller do the following:
class ParticipationsController < ApplicationController
def index
end
def new
#participation = Participation.new
#participation.user = current_user
#participation.event = Event.find(params[:event_id])
end
def create
#participation = Participation.new(participation_params)
#participation.user = current_user
#participation.event = Event.find(params[:event_id])
if #participation.save
redirect_to events_path, notice: "You are registered!"
else
render action: 'new'
end
end
private
def participation_params
params.require(:participation).permit(:status, :first_name)
end
end
Then in your form you can just make a normal first_name input:
<fieldset>
<%= f.label :first_name, "First Name" %><br />
<%= f.text_field :first_name %><br />
</fieldset>
Try that and let me know, by the way do not forget to remove the accepts_nested_attributes from Participation model, and make sure your migrations are correctly set up for match the associations you have. I hope have helped you.
Update
If you do not want to persist user information in the Participation then you can just add attribute accessors to your Participation model, and store information in your current_user in your create action:
#app/models/participation.rb
class Participation < ActiveRecord::Base
belongs_to :user
belongs_to :event
attr_accessor :first_name, :whatever_other_attribute # You can add as many attributes you need.
end
Then just update information of your current_user in your create action:
#app/controllers/participations_controller
def create
#participation = Participation.new(participation_params)
#participation.user = current_user
#participation.event = Event.find(params[:event_id])
current_user.update_attributes first_name: #participation.first_name
if #participation.save
redirect_to events_path, notice: "You are registered!"
else
render action: 'new'
end
end
This way you store the information in the current_user instead of Participation, also this way you can easily customize the different information you will ask in the participation form.
Thanks to #ssoulless for pointing me in to the final solution.
Ultimately I was able to update the controller action to associate the user and then use the participation_params to update the participation[:user].
# participations_controller.rb
...
def create
#participation = Participation.new(status: participation_params[:status])
#participation.mission_id = params[:mission_id]
current_user.update_attributes(participation_params[:user_attributes])
#participation.user = current_user
if #participation.save
redirect_to missions_path, notice: "You are registered!"
else
render action: 'new'
end
end
private
def participation_params
params.require(:participation)
.permit(:id, :status, user_attributes: [:id, :first_name])
end
I like this approach a bit more since it hides the user attributes in a private method. Also for anyone that this might help, when using accepts_nested_attributes_for in your model, you need to add it to the strong_params #permit parameters (Don't forget the ID!).
#post.comments.all is clear. and i dont see any errors after i send form. When i click "Submit" i sent to posts/id/comments, but
my routes.rb
resources :posts do
resources :comments
end
post controller
def show
#current_user ||= User.find(session[:user_id]) if session[:user_id]
#commenter = #current_user
#post = Post.find(params[:id])
#comment = Comment.build_from( #post, #commenter.id, "234234" )
#comments = Comment.all
respond_to do |format|
format.html
format.json { render json: #post }
end
end
comments controller
class CommentsController < ApplicationController
def index
#comments = #post.comments.all
end
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.new params[:comment]
if #comment.save
redirect_to #post # comment not save, so i dont redirect to this page
else
# is that there
end
end
end
post model
acts_as_commentable
has_many :comments
comment model
class Comment < ActiveRecord::Base
acts_as_nested_set :scope => [:commentable_id, :commentable_type]
attr_accessible :commentable, :body, :user_id
validates :body, :presence => true
validates :user, :presence => true
# NOTE: install the acts_as_votable plugin if you
# want user to vote on the quality of comments.
#acts_as_votable
belongs_to :commentable, :polymorphic => true
# NOTE: Comments belong to a user
belongs_to :user
# Helper class method that allows you to build a comment
# by passing a commentable object, a user_id, and comment text
# example in readme
def self.build_from(obj, user_id, comment)
new \
:commentable => obj,
:body => comment,
:user_id => user_id
end
#helper method to check if a comment has children
def has_children?
self.children.any?
end
# Helper class method to lookup all comments assigned
# to all commentable types for a given user.
scope :find_comments_by_user, lambda { |user|
where(:user_id => user.id).order('created_at DESC')
}
# Helper class method to look up all comments for
# commentable class name and commentable id.
scope :find_comments_for_commentable, lambda { |commentable_str, commentable_id|
where(:commentable_type => commentable_str.to_s, :commentable_id => commentable_id).order('created_at DESC')
}
# Helper class method to look up a commentable object
# given the commentable class name and id
def self.find_commentable(commentable_str, commentable_id)
commentable_str.constantize.find(commentable_id)
end
end
post view
%h2 Add a comment:
- #comments.each do |c|
= #c.body
= form_for([#post, #comment]) do |f|
.field
= f.label :body
%br/
= f.text_area :body
.actions
= f.submit
Thanks in advance and sorry for bad english
First of all you can debug why #comment.save return false yourself - just add p #comment.errors in else block and check server log.
It seems for me that you try to save invalid comments because you don't have setup user for #comment in action CommentsController#create. Comment validates presence of user!
There are several ways how to fix it. Analyzing your code I think the simplest way for you is modify CommentsController#create
#CommentsController
def create
#current_user ||= User.find(session[:user_id]) if session[:user_id]
#post = Post.find(params[:post_id])
#comment = #post.comments.new params[:comment]
#comment.user = #current_user
if #comment.save
redirect_to #post # comment not save, so i dont redirect to this page
else
# is that there
end
end
Another way is to use some gem for authentication - I recommend devise
One more way (very bad way) is to pass user_id through hidden field (you have defined #current_user in PostsController#show and user_id in attr_accessible list in Comment). But this is easy way to hack your application and write comments on behalf of any user in system!
There is an association with course and course instance. When I visit the url /courses/1/course_instances/new, I get the error below.
This is the error I got:
No route matches {:controller=>"course_instances", :course_id=>nil}
Models
Course:
class Course < ActiveRecord::Base
attr_accessible :code, :credits, :description, :hours, :id, :name, :pass_mark
has_many :course_instances, :dependent => :destroy
accepts_nested_attributes_for :course_instances
end
Course Instance
class CourseInstance < ActiveRecord::Base
attr_accessible :end_date, :id, :start_date
belongs_to :course
end
Routes
Sis::Application.routes.draw do
resources :courses do
resources :course_instances
end
root :to => 'home#index'
end
Course Instance Controller
class CourseInstancesController < ApplicationController
before_filter :find_course
def new
#course_instance = #course.course_instances.build
respond_to do |format|
format.html
end
end
def find_course
#course = Course.find(params[:course_id])
end
end
new.html.erb
<%= form_for ([#course, #course_instance]) do |f| %>
---- excluded for brevity ----
Rake Routes
new_course_course_instance GET /courses/:course_id/course_instances/new(.:format) course_instances#new
Am not sure what I did but it started to work. I tried backtracking to recreate the error however I have been unsuccessful so far. Weird! Thanks for the help from everyone!