Rails Setting Devise Username as attribute of comment - ruby-on-rails

I'm new to rails and still figuring out which things belong in the model and which in the controller. I'm creating a simple comment model that belongs to articles. I have a attribute :commenter which is a string. I would like to get the username from the current_user (I'm using devise for my login feature) Would I do this in the create method of my controller?
Something like
def create
#post = Post.find(params[:post_id])
#comment.commenter = current_user.username
#comment = #post.comments.create(comment_params)
redirect_to post_path(#post)
end

class User < ActiveRecord::Base
has_many :posts
has_many :comments
end
class Post < ActiveRecord::Base
belongs_to :user
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :user #should have user_id: integer in Comment
belongs_to :post #should have post_id: integer in comment
delegate :username, to: :user, allow_nil: true
end
In posts controller: -
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.new(comment_params)
#comment.user = current_user
if #comment.save
flash[:success] = "Comment saved successfully!"
redirect_to post_path(#post)
else
flash[:error] = #comment.errors.full_messages.to_sentence
redirect_to post_path(#post)
end
end
After that you can get all user details of any comment:-
comment = Comment.find(#id_of_comment)
comment.username => #will return username because of delegation
Reference for delegation

Related

Can not create or build nested model in Rails 6

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

Error getting user's name and email from the associated model in Ruby on Rails

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

Is it correct architecture for adding user's posts?

Create action in posts controller:
def create
#post = current_user.posts.build(post_params)
if #post.not_exists?(current_user)
if #post.save
#flash
redirect_to root_path
else
#flash[:error]
redirect_to root_path
end
else
#flash[:error]
redirect_to root_path
end
end
Post model:
class Post < ActiveRecord::Base
belongs_to :user
##validations
def not_exists?(user)
return true unless user.posts.find_by(name: self.name)
end
end
My question: is it correct to build my create action like this? Or there is a better architectural design? I think it is too fat action.
Why not use a validation instead ?
class Post < ActiveRecord::Base
belongs_to :user
validates_uniqueness_of :name, :scope => :user_id

Add Comment to User and Post models (Ruby on Rails)

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

Passing the user information to the mailer

I have a Post which has many comments. I am trying to send a notification email to the owner of the post that someone has commented on their post but I am having a hard time trying to get the owner of the post to the mailer. the code if as follows
class CommentsController < ApplicationController
def create
#commentable = find_commentable
#comment = #commentable.comments.build(comment_params)
#comment.user_id = current_user.id
if #comment.save
flash[:notice] = "Successfully posted an offer."
PostMailer.comment_posted(----).deliver #this is the mail code
redirect_to #commentable
else
flash[:error] = "Error adding an offer."
end
end
end
below is the mailer code
class PostMailer < ActionMailer::Base
default from: "contact#example.com"
def comment_posted(user)
#user = user
mail to: user.first_name, subject: "You have a new Comment!"
end
end
below is the comment model
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :commentable, :polymorphic => true
has_ancestry
end
and the Post model
class Post < ActiveRecord::Base
belongs_to :user
has_many :comments, :as => :commentable, dependent: :destroy
end
Use this:
PostMailer.comment_posted(#commentable.user).deliver
#commentable will give you corresponding Post record for the given comment. Post belongs_to a user, so you can access the poster using #commentable.user.
I noticed in the controller you are doing:
PostMailer.comment_posted
Shouldn't this be:
PostMailer.offer_posted(#comment.user).deliver
?

Resources