I have a form for a User model that accepts_nested_attributes on a has_many association to Comment.
Whenever the form is updated, I need to be able to check whether a Comment was added/created along with it.
Aside from checking what's inside the submitted params, is there a more Rails way of doing this?
User model
class User < ActiveRecord::Base
accepts_nested_attributes_for :comments
...
end
View
= simple_form_for #user do |f|
= f.simple_fields_for :comments do |fc|
= fc.input :content
...
end
end
And finally in my controller
def update
#user = User.find(params[:id])
if #user.update_attributes(permitted_params)
# Check if a new comment was added here
end
end
def update
#user = User.find(params[:id])
if #user.update_attributes(permitted_params)
if #user.comments.any?(&:id_changed?)
# do_something
end
end
end
This code exploits ActiveModel::Dirty methods
Related
I am creating a webiste where people can debate with each other. It has 4 main models - post, for_the_motion, against_the_motion, and user( added in the respective order). I ran a migration and made a association between for model and against model.
For each view in "for" model I want to show which user added that particular motion. But I am getting an error
undefined method `image_url' for nil:NilClass
Stuck from long time on this. This is how the models look
user.rb
class User < ApplicationRecord
has_many :posts
has_many :fors
has_many :againsts
class << self
def from_omniauth(auth_hash)
user = find_or_create_by(uid: auth_hash['uid'], provider: auth_hash['provider'])
user.name = auth_hash['info']['name']
user.image_url = auth_hash['info']['image']
user.url = auth_hash['info']['urls'][user.provider.capitalize]
user.save!
user
end
end
end
for.rb
class For < ApplicationRecord
belongs_to :post, optional: true
belongs_to :user,optional: true
end
post.rb
class Post < ApplicationRecord
has_many :fors, dependent: :destroy
has_many :againsts, dependent: :destroy
belongs_to :user, optional: true
end
against.rb
class Against < ApplicationRecord
belongs_to :post, optional: true
belongs_to :user, optional:true
end
CONTROLLERS
posts_controller.rb
class PostsController < ApplicationController
def index
#posts = Post.all
end
def land
end
def show
#post = Post.find(params[:id])
end
def new
#post = Post.new
end
def create
#post = Post.new(post_params)
#post.user = current_user
if #post.save
redirect_to #post
else
render 'new'
end
end
private
def post_params
params.require(:post).permit(:title)
end
end
fors_controller.rb
class ForsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#for = #post.fors.create(fors_params)
#for.user = current_user
redirect_to post_path(#post)
end
private
def fors_params
params.require(:for).permit(:content)
end
end
sessions_controller.rb
class SessionsController < ApplicationController
def create
begin
#user = User.from_omniauth(request.env['omniauth.auth'])
session[:user_id] = #user.id
# flash[:success] = "Welcome, #{#user.name}!"
rescue
# flash[:warning] = "There was an error while trying to authenticate you..."
end
redirect_to root_path
def destroy
if current_user
session.delete(:user_id)
# flash[:success] = 'See you!'
end
redirect_to root_path
end
end
end
This is where I am getting the error
<h1><%=#post.title%></h1>
<div class="fort">
<h3>For the motion</h3>
<%#post.fors.each do |f|%>
<p><%=f.content%></p>
<p><%=f.user.image_url%></p>/*This is where errors arise*/
<%end%>
<%= render "fors/form"%>
</div>
<div class="against">
<h3>Against the motion</h3>
<%#post.againsts.each do |f|%>
<p><%=f.content%></p>
<p><%= #post.user.name%></p>
<%end%>
<%= render "againsts/form"%>
</div>
Here is the github link for any other required information
https://github.com/sarfrazbaig/DebatingSociety2
Seems like you missed saving the .user on fors_controller.rb:
class ForsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#for = #post.fors.create(fors_params)
# .create above already will save a new For record in DB
# therefore your #for.user assignation will be only assigned in memory, but not yet in DB
#for.user = current_user
# you'll need to save it again afterwards:
#for.save
redirect_to post_path(#post)
end
# ...
end
Suggestion:
use .new instead of .create to not-yet-save into the DB, and only call save when everything that you need to assign is already assigned.
class ForsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#for = #post.fors.new(fors_params)
#for.user = current_user
#for.save
redirect_to post_path(#post)
end
# ...
end
Take note that you would still encounter that error even if you already updated your code with the above; this is because currently your For records in the DB all are missing the .user value. You'll have to manually assign and save the .user accordingly for each For record, and probably best that you'd write a...
class For < ApplicationRecord
validates :user, presence: true
end
... validation so that this error will be prevented in the future.
One of the #post.fors is lacking a user, which is permitted by the belongs_to :user, optional: true in your For model.
You can restrict your query to showing only fors that have an associated user:
#post.fors.joins(:users) or you can use the safe navigation operator to return nil when attempting to read the image_url for a non-existent user - f.user&.image_url
i am learning Ruby on Rails and i am using Rails 4. Following a Rails tutorial on Making a Blog App with Comments, the author wrote this to create a comment in comments_controller.rb
def create
#post=Post.find(params[:post_id])
#comment=#post.comments.build(params[:post].permit[:body])
redirect_to #post_path(#post)
end
and in the partial : _form.html.erb
<%= form_for([#post, #post.comments.build]) do |f| %>
<h1><%= f.label :body %></h1><br />
<%= f.text_area :body %><br />
<%= f.submit %>
<% end %>
I was wondering if i could only let the current user to comment on a post, having made all appropriate associations between User Model and Comment Model, so that while displaying the comments, i could retreive information from the User through Comment. Clearly, i do not just want to use a
before_action :authenticate_user!
as i want an association between User and Comment.
That tutorial you're following isn't so good.
Here's what you should be looking at:
#config/routes.rb
resources :posts do
resources :comments #-> url.com/posts/:post_id/comments/new
end
#app/controllers/comments_controller.rb
class CommentsController < ApplicationController
def create
#post = Post.find params[:post_id]
#post.comments.new comment_params #-> notice the use of "strong params" (Google it)
#post.save
end
private
def comment_params
params.require(:comment).permit(:body)
end
end
To add a User to a Comment, you'll want to do this:
#config/routes.rb
resources :posts do
resources :comments #-> url.com/posts/:post_id/comments/new
end
#app/models/user.rb
class User < ActiveRecord::Base
has_many :comments
end
#app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :post
end
#app/controllers/comments_controller.rb
class CommentsController < ApplicationController
before_action :authenticate_user! #-> only if you're using devise
def create
#post = Post.find params[:post_id]
#comment = current_user.comments.new comment_params
#comment.post = #post
#comment.save
end
private
def comment_params
params.require(:comment).permit(:body)
end
end
If you're unsure about setting up a has_many/belongs_to relationship, you should create your tables like this:
If I understand correctly, you have prepared proper associations between your models, and the question is how to update your controller's action to make it work.
If I have proper understanding of your Comment model, besides post, it has body and user attributes.
First of all, you should update your current code:
#comment = #post.comments.build(params[:post].permit[:body])
To look like this:
#comment = #post.comments.build(body: params[:post].permit[:body])
To properly set the body attribute, and creating proper association with current_user is as simple as:
#comment = #post.comments.build(body: params[:post].permit[:body],
user: current_user)
At that point the comment is not saved yet, so you have two options:
After building the comment you can save it manually:
#comment.save
Or 2. replace build with create:
#comment = #post.comments.create(body: params[:post].permit[:body],
user: current_user)
Hope that helps!
I'm new to Rails. I'm building my first app - simple blog. I have User and Post models, where each user can write many posts. Now I want to add Comment model, where each post can have many comments, and also each user can comment on any post created by any other user.
In Comment model I have
id \ body \ user_id \ post_id
columns.
Model associations:
user.rb
has_many :posts, dependent: :destroy
has_many :comments
post.rb
has_many :comments, dependent: :destroy
belongs_to :user
comment.rb
belongs_to :user
belongs_to :post
So how do I correctly define create action in CommentsController?
Thank you.
UPDATE:
routes.rb
resources :posts do
resources :comments
end
comments_controller.rb
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(comment_params)
if #comment.save
redirect_to #post
else
flash.now[:danger] = "error"
end
end
The result is
--- !ruby/hash:ActionController::Parameters
utf8: ✓
authenticity_token: rDjSn1FW3lSBlx9o/pf4yoxlg3s74SziayHdi3WAwMs=
comment: !ruby/hash:ActionController::Parameters
body: test
action: create
controller: comments
post_id: '57'
As we can see it doesnt send user_id and works only if I delete validates :user_id, presence: true string from comment.rb
Any suggestions?
In your way you should put this:
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(comment_params)
#comment.user_id = current_user.id #or whatever is you session name
if #comment.save
redirect_to #post
else
flash.now[:danger] = "error"
end
end
And also you should remove user_id from comment_params as strong parameters .
Hope this will help you .
Associations
To give you a definition of what's happening here, you have to remember whenever you create a record, you are basically populating a database. Your associations are defined with foreign_keys
When you ask how to "add comments to User and Post model" - the bottom line is you don't; you add a comment to the Comment model, and can associate it with a User and Post:
#app/models/comment.rb
Class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :post
end
This prompts Rails to look for user_id and post_id in the Comment model by default.
This means if you wanted to create a comment directly, you can associate it to either of these associations by simply populating the foreign_keys as you wish (or use Rails objects to populate them)
So when you want to save a comment, you can do this:
#app/controllers/comments_controller.rb
Class CommentsController < ApplicationController
def create
#comment = Comment.new(comment_params)
end
private
def comment_params
params.require(:comment).permit(:user_id, :post_id, :etc)
end
end
Conversely, you can handle it by using standard Rails objects (as the accepted answer has specified)
Class CommentsController < ApplicationController
before_action :set_user
before_action :set_post
def create
#comment = #post.comments.create(comment_params)
if #comment.save
redirect_to #post
else
flash.now[:danger] = "error"
end
end
private
set_post
#post = User.posts.find(params[:post_id])
end
set_user
#user = User.find(params[:user_id])
end
comment_params
params[:comment].permit()
end
Basically my idea is very simple - I want to create a new cart for each new user. The form itself is generated with scaffold and we're talking rails 4.0.1 here.
Is there a way to do that and if so - how? Maybe you can link me some live examples?
You do not need multiple forms to create multiple objects in Rails controller. Assuming that you have relationships like this:
class User < ActiveRecord::Base
has_many :carts #or has_one :cart
end
class Cart < ActiveRecord::Base
belongs_to :user
end
Then it's perfectly acceptable to do this:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new user_params
if #user.save
#user.carts.create # or #user.create_cart
redirect_to user_path
else
render action: :new
end
end
private
def user_params
params.require(:user).permit(...)
end
end
If the new user form happens to include some cart-specific details, then use fields_for to make them available in the form:
= form_for :user do |f|
... f.blah for user fields ...
= fields_for :cart do |cart_fld|
... cart_fld.blah for cart fields ...
and add cart_params to your controller.
#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!