Add notification to RoR messaging inbox system - ruby-on-rails

I have a rails app with intern messaging system. User can send message to other user. Example : User 1 send message User 2 can respond viceversa.
All works perfectly. But I want to upgrade this system with notfications functionnality. I want to type of notifications :
1) On navbar
2) By mail
Problem : I dont know how i can do this.
Can you help me ?
Conversations table
class CreateConversations < ActiveRecord::Migration
def change
create_table :conversations do |t|
t.integer :sender_id
t.integer :recipient_id
t.timestamps
end
end
end
Messages table. In this code I have a boolean :read. I think solution can be here. What do you think about this ?
class CreateMessages < ActiveRecord::Migration
def change
create_table :messages do |t|
t.text :body
t.references :conversation, index: true
t.references :user, index: true
t.boolean :read, :default => false
t.timestamps
end
end
end
conversation.rb
class Conversation < ActiveRecord::Base
belongs_to :sender, :foreign_key => :sender_id, class_name: 'User'
belongs_to :recipient, :foreign_key => :recipient_id, class_name: 'User'
has_many :messages, dependent: :destroy
validates_uniqueness_of :sender_id, :scope => :recipient_id
scope :between, -> (sender_id,recipient_id) do
where("(conversations.sender_id = ? AND conversations.recipient_id =?) OR (conversations.sender_id = ? AND conversations.recipient_id =?)", sender_id,recipient_id, recipient_id, sender_id)
end
end
Message.rb
class Message < ActiveRecord::Base
belongs_to :conversation
belongs_to :user
validates_presence_of :body, :conversation_id, :user_id
def message_time
created_at.strftime("%m/%d/%y at %l:%M %p")
end
end
conversations_controller.rb
class ConversationsController < ApplicationController
before_action :authenticate_user!
# GET /conversations
# GET /conversations.json
def index
#users = User.all
# Restrict to conversations with at least one message and sort by last updated
#conversations = Conversation.joins(:messages).uniq.order('updated_at DESC')
end
# POST /conversations
# POST /conversations.json
def create
if Conversation.between(params[:conversation][:sender_id], params[:conversation][:recipient_id]).present?
#conversation = Conversation.between(params[:conversation][:sender_id], params[:conversation][:recipient_id]).first
else
#conversation = Conversation.create!(conversation_params)
end
redirect_to conversation_messages_path(#conversation)
end
private
# Use callbacks to share common setup or constraints between actions.
def conversation_params
params.require(:conversation).permit(:sender_id, :recipient_id)
end
end
messages_controller.rb
class MessagesController < ApplicationController
before_action do
#conversation = Conversation.find(params[:conversation_id])
end
def index
#messages = #conversation.messages
if #messages.length > 10
#over_ten = true
#messages = #messages[-10..-1]
end
if params[:m]
#over_ten = false
#messages = #conversation.messages
end
if #messages.last
if #messages.last.user_id != current_user.id
#messages.last.read = true;
end
end
#message = #conversation.messages.new
end
def new
#message = #conversation.messages.new
end
def create
#message = #conversation.messages.new(message_params)
if #message.save
redirect_to conversation_messages_path(#conversation)
end
end
private
def message_params
params.require(:message).permit(:body, :user_id)
end
end
/conversations/index.html.erb
<div class="ui segment">
<h3>Mailbox</h3>
<div class="ui list">
<div class="item">
<% #conversations.each do |conversation| %>
<% if conversation.sender_id == current_user.id || conversation.recipient_id == current_user.id %>
<% if conversation.sender_id == current_user.id %>
<% recipient = User.find(conversation.recipient_id) %>
<% else %>
<% recipient = User.find(conversation.sender_id) %>
<% end %>
Conversation avec <%= link_to recipient.prenom, conversation_messages_path(conversation)%>
<% end %>
<% end %>
</div>
</div>
</div>
<div class="ui segment">
<h3>All Users</h3>
<div class="ui list">
<% #users.each do |user| %>
<% if user.id != current_user.id %>
<div class="item">
<%= user.prenom %> <%= button_to 'Message me', conversations_path(conversation: { sender_id: current_user.id, recipient_id: user.id }), class: 'btn btn-primary m-t' %>
</div>
<% end %>
<% end %>
</div>
</div>
messages/index.html.erb
<% if #over_ten %>
<%= link_to 'Show Previous', "?m=all" %>
<% end %>
<div class="ui segment">
<% #messages.each do |message| %>
<% if message.body %>
<% user = User.find(message.user_id) %>
<div class="item">
<div class="content">
<div class="header"><strong><div class="imageavatarmessage"><%= image_tag user.avatar(:thumb), class:"imageavatarmessage" %></div><%= user.prenom %></strong> <%= message.message_time %></div>
<div class="list">
<div class="item">
<i class="right triangle icon"></i>
<%= message.body %>
</div>
</div>
</div>
</div>
<% end %>
<% end %>
</div>
<%= form_for [#conversation, #message], html: {class: "ui reply form"} do |f| %>
<div class=”field”>
<%= f.text_area :body, class: "form-control" %>
</div>
<%= f.text_field :user_id, value: current_user.id, type: "hidden" %>
<div>
<%= f.submit "Add Reply", class: "ui blue labeled submit icon button" %>
</div>
<% end %>

I would recommend you to go through this tutorial to build nice notification system
https://www.devwalks.com/lets-build-instagram-part-6-notifications/
Basically, you have to create new model, set dependencies with your Message model and integrate to the controller
For the email notifications, it's even simpler.
Just create new mailer and fire it on create action in messages_controller
def create
#message = #conversation.messages.new(message_params)
if #message.save
SendMessageMailer.new_message(#message).deliver_later
redirect_to conversation_messages_path(#conversation)
end
end
EDIT:
To create mailer, you should do something like this:
rails g mailer SendMessage
Go to /app/mailers/send_message_mailer.rb and add action, same type of building as controllers
def new_message(message)
#message = message
mail(to: #message.user.email, subject: 'Hey! Here is what you missed')
end
Also, create an view (email template) and code with erb code
app/views/send_message_mailer/new_message.html.erb
Im not going deep into this, I guess you can figure out how to pass interval (let's say don't send if user is online or have read the message) and differentiate with receiver/sender users
Mailer, once again, is just same type of controller. You can pass as many params as you need and use model nesting inside mailer controller and views
mailer
def new_message(message, sender, receiver)
end
controller
SendMessageMailer.new_message(#message, #message.user, params[:receiver]).deliver_later

Related

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

ActiveModel::UnknownAttributeError in MessagesController#create

Good day, I am trying to create a chat section on my website but I keep on getting ActiveModel::UnknownAttributeError in MessagesController#create
unknown attribute 'user_id' for Message.
I don't know what I am doing wrong here,
my route is
resources :conversations do
resources :messages
end
Here is my conversations model
class Conversation < ApplicationRecord
belongs_to :sender, foreign_key: :sender_id, class_name: 'User'
belongs_to :recipient, foreign_key: :recipient_id, class_name: 'User'
has_many :messages, dependent: :destroy
validates_uniqueness_of :sender_id, scope: :recipient_id
scope :between, -> (sender_id, recipient_id) do
where("(conversations.sender_id = ? AND conversations.recipient_id =?) OR (conversations.sender_id = ? AND conversations.recipient_id =?)", sender_id, recipient_id, recipient_id, sender_id)
end
end
And in the Message model, app/models/message.rb: I have
class Message < ApplicationRecord
belongs_to :conversation
belongs_to :user
validates_presence_of :body, :conversation_id, :user_id
end
In the Conversations controller, app/controllers/conversations_controller.rb: I have
class ConversationsController < ApplicationController
before_action :authenticate_user!
def index
#users = User.all
#conversations = Conversation.all
end
def create
if Conversation.between(params[:sender_id], params[:recipient_id]).present?
#conversation = Conversation.between(params[:sender_id], params[:recipient_id]).first
else
#conversation = Conversation.create!(conversation_params)
end
redirect_to conversation_messages_path(#conversation)
end
private
def conversation_params
params.permit(:sender_id, :recipient_id)
end
end
I think the main issue is in my conversation index page on the line that links to a message page #message = #conversation.messages.new(message_params)
In the Message controller, app/controllers/messages_controller.rb: I have
class MessagesController < ApplicationController
before_action do
#conversation = Conversation.find(params[:conversation_id])
end
def index
#messages = #conversation.messages
#message = #conversation.messages.new
end
def new
#message = #conversation.messages.new
end
def create
#message = #conversation.messages.new(message_params)
if #message.save
redirect_to conversation_messages_path(#conversation)
end
end
private
def message_params
params.require(:message).permit(:body, :user_id)
end
end
In app/views/conversations/index.html.erb
<h1>My Inbox</h1>
<h1>All Conversations:</h1>
<% #conversations.each do |conversation| %>
<% if conversation.sender_id == current_user.id || conversation.recipient_id == current_user.id %>
<% if conversation.sender_id == current_user.id %>
<% recipient = User.find(conversation.recipient_id) %>
<% else %>
<% recipient = User.find(conversation.sender_id) %>
<% end %>
<h3><%= link_to recipient.email, conversation_messages_path(conversation)%></h3>
<% end %>
<% end %>
<h1>All Users:</h1>
<% #users.each do |user| %>
<% if user.id != current_user.id %><h3>
<%= link_to user.email, conversations_path(sender_id: current_user.id, recipient_id: user.id), method: "post"%></h3>
<% end %>
<% end %>
In app/views/messages/index.html.erb I have
<% #messages.each do |message| %>
<% if message.body %>
<% user = User.find(message.user_id) %>
<%= user.email %><%= message.message_time %>
<%= message.body %>
<% end %>
<% end %>
<%= form_for [#conversation, #message] do |f| %>
<%= f.text_area :body %>
<%= f.text_field :user_id, value: current_user.id, type: "hidden" %>
<%= f.submit "Send Reply" %>
<% end %>
I would appreciate if anyone can guild me on what to do right and is this a good way to setup a chat system?

ArgumentError in Messages#index while building conversation + messages model

For a marketplace that I'm working on I'd like to add messaging-functionality between users.
I found this tutorial that matches my requirements and I'm trying to replicate it.
When trying to send a message to another user however, I get the following error:
ArgumentError in Messages#index
Showing /home/ubuntu/workspace/marketplace/app/views/messages/index.html.erb where line #9 raised:
First argument in form cannot contain nil or be empty
From the Message view file it's relating to:
<%= form_for [#conversation, #message] do |f| %>
<%= f.text_area :body %>
<%= f.text_field :user_id, value: current_user.id %>
<%= f.submit "Add Reply" %>
A conversation_id was made, but in Message view file it goes wrong.
Does anybody have a clue what I'm missing? I'm quite new to Rails and I'm blind-staring on this for 2 nights now. Your help is much appreciated!
Routes.rb
resources :conversations do
resources :messages
end
Conversation Model:
class Conversation < ApplicationRecord
belongs_to :sender, :foreign_key => :sender_id, class_name: "User"
belongs_to :recipient, :foreign_key => :recipient_id, class_name: "User"
has_many :messages, dependent: :destroy
validates_uniqueness_of :sender_id, :scope => :recipient_id
scope :between, -> (sender_id, recipient_id) do
where("(conversations.sender_id = ? AND conversations.recipient_id = ?) OR (conversations.sender_id = ? AND conversations.recipient_id = ?)", sender_id, recipient_id, recipient_id, sender_id)
end
end
Message Model:
class Message < ApplicationRecord
belongs_to :conversation
belongs_to :user
validates_presence_of :body, :conversation_id, :user_id
def message_time
created_at.strftime("%_d %b %Y at %k:%M")
end
end
Conversations Controller:
class ConversationsController < ApplicationController
before_action :authenticate_user!
def index
#users = User.all
#conversations = Conversation.all
end
def create
if Conversation.between(params[:sender_id],params[:recipient_id]).present?
#conversation = Conversation.between(params[:sender_id], params[:recipient_id]).first
else
#conversation = Conversation.create!(conversation_params)
end
redirect_to conversation_messages_path(#conversation)
end
private
def conversation_params
params.permit(:sender_id, :recipient_id)
end
end
Messages Controller:
class MessagesController < ApplicationController
before_action do
#conversation = Conversation.find(params[:conversation_id])
end
def index
#messages = #conversation.messages
end
def new
#message = #conversation.messages.new
end
def create
#message = #conversation.messages.new(message_params)
if #message.save
redirect_to conversation_messages_path(#conversation)
end
end
private
def message_params
params.require(:message).permit(:body, :user_id)
end
end
Conversation index page
<h3>Mailbox</h3>
<% #conversations.each do |conversation| %>
<% if conversation.sender_id == current_user.id || conversation.recipient_id == current_user.id %>
<% if conversation.sender_id == current_user.id %>
<% recipient = User.find(conversation.recipient_id) %>
<% else %>
<% recipient = User.find(conversation.sender_id) %>
<% end %>
<%= link_to recipient.firstname, conversation_messages_path(conversation)%>
<% end %>
<% end %>
<h3>All Users</h3>
<% #users.each do |user| %>
<% if user.id != current_user.id %>
<%= user.firstname %> <%= link_to "Message me!", conversations_path(sender_id: current_user.id, recipient_id: user.id), method: "post"%>
<% end %>
<% end %>
Messages index page
<% #messages.each do |message| %>
<% if message.body %>
<% user = User.find(message.user_id) %>
<p><%= user.firstname %> and <%= message.message_time %></p>
<p><%= message.body %></p>
<% end %>
<% end %>
<%= form_for [#conversation, #message] do |f| %>
<%= f.text_area :body %>
<%= f.text_field :user_id, value: current_user.id %>
<%= f.submit "Add Reply" %>
<% end %>
You did not define the #message variable in your MessagesController#index action. Please define it like this
def index
#messages = #conversation.messages
#message = #conversation.messages.new
end

Rails unable to send id to update controller

I am building a blog application in which I am trying to have two links in that two links I am sending the post_id to my update controller but it is giving me error .And I want to collect that post_id in my update controller and want to check the "status"(Status is basically the column name in my posts table and by default my status column has a status of pending ) .So when admin click on approve the status of column should change to approve and when admin click on decline the status of column should change to decline also user post should be deleted .Admin can access all the users posts whereas user can access his only post
posts_controller
class PostsController < ApplicationController
before_action :authenticate_user!
def index
#posts = Post.user_post(current_user).order('created_at DESC').paginate(:page => params[:page], :per_page => 5)
end
def new
#post = Post.new
end
def show
#post=find_params
end
def create
#post = Post.new(post_params)
#post.user = current_user
if #post.save
Post.upload(params[:post][:files],#post.id)
redirect_to #post
else
render 'new'
end
end
def edit
#post = find_params
puts "cccccccccc#{params[:commit]}"
Post.up(#post.id,params[:commit])
end
def update
#post = find_params
if #post.update(post_params)
redirect_to #post
else
render 'edit'
end
end
def destroy
#post = find_params
#post.destroy
redirect_to posts_path
end
private
def post_params
params.require(:post).permit(:title, :body)
end
def find_params
Post.find(params[:id])
end
end
posts/_form.html.erb
<%= form_for #post,html: { multipart: true } do |f| %>
<% if #post.errors.any? %>
<div id="errors">
<h2><%= pluralize(#post.errors.count, "error") %> prevented this post from saving:</h2>
<ul>
<% #post.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.label :title %><br>
<%= f.text_field :title %><br>
<br>
<%= f.label :body %><br>
<%= f.text_field :body %><br>
<br>
<!-- if you want to upload multiple files at a time -->
<%= f.label :files %><br>
<%= f.file_field :files,:multiple => true %><br>
<br>
<%= f.submit %>
<br>
<% end %>
posts/edit.html.erb
<div id="page_wrapper">
<h1>Edit Post</h1>
<%= render 'form' %>
<br>
</div>
migration
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.string :title
t.text :body
t.integer :user_id
t.string :status
t.timestamps
end
end
end
posts/show.html.erb
<div id="post_content">
<h1 class="title"><%= #post.title %></h1>
<p class="date">
Submitted <%= time_ago_in_words(#post.created_at) %> Ago
<% if user_signed_in? %>
<%= link_to 'Edit', edit_post_path(#post) %> |
<%= link_to 'Delete', post_path(#post), method: :delete, data: { confirm: 'Are you sure?' } %>
<%= link_to "approve",[:edit,#post] %>
<%= link_to "decline",[:edit,#post] %>
<% end %>
</p>
<p class="body"><%= #post.body %></p>
<div id="comments">
<h2><%= #post.comments.count %> Comments</h2>
<%= render #post.comments %>
<h3>Add a comment:</h3>
<%= render "comments/form" %>
</div>
</div>
post.rb
class Post < ActiveRecord::Base
has_many :documents
has_many :comments, dependent: :destroy
belongs_to :user
validates :title, presence: true, length: {in: 5..15}
validates :body, presence: true,length: {in: 5..200}
def self.up(id,params)
puts "aaaaaaaa#{id}"
puts "bbbbbbbbbbbbb#{params}"
end
def self.user_post(id)
role = User.find_role(id)
if role == 'admin'
Post.all
elsif role == 'user'
Post.where(user_id: id)
elsif role == 'developer'
Post.where(user_id: id)
end
end
def self.icon(extension)
case extension
when 'pdf'
EXTENSION[1]['pdf']
when 'png' || 'jpg' || 'jpeg'
EXTENSION[0]['png']
when 'doc' || 'odt'
EXTENSION[2]['doc']
end
end
####limit to upload files not more than ######
def self.upload(files,post_id)
files.each do |file|
#file_extension=file.content_type.split('/')[1]
doc = Document.new(document: file,post_id: post_id )
#save is a method which will save the content in the database
doc.save!
end
end
end
routes.rb
Rails.application.routes.draw do
root "posts#index"
devise_for :users
resources :posts do
resources :comments
end
resources :uploads
end
There are a bunch of problems with your code.. I'll not try to address them all but the specific problem you are facing..
first you need to generate path for both approve and decline action..
resources :posts do
patch '/approve' => 'posts#approve', as: :approve #posts_approve_path(post) is the route helper for this
patch '/decline' => 'posts#decline', as: :decline #posts_decline_path(post) is the route helper for this
resources :comments
end
this will generate the required routes.
now inside your view wherever you want to approve and decline.
<%= link_to "approve",posts_approve_path(#post.id), method: :patch %>
<%= link_to "decline",posts_decline_path(#post.id), method: :patch %>
In your controller you need to modify find_params as the generated routes will pass :posts_id as the :id param.
def find_params
id = params[:id] || params[:posts_id]
Post.find(id)
end
Now in your controller add 2 new methods to implement the functionality.
def approve
#post = find_params
#post.update_attribute(:status, 'approved') if #post.present?
redirect_to post_path(#post), notice: 'Post approved'
end
def decline
#post = find_params
#post.destroy if #post.present?
redirect_to post_path(#post), notice: 'Post deleted'
end

Polymorphic uninitialized constant

having a lot of trouble setting up polymorphic. I have Comments which works fine, but the Links which should work exactly the same has NameError in Links#index. If I try to forum_posts/1/comments all is good but not forum_posts/1/links.
I was following the railscast tutorial http://railscasts.com/episodes/154-polymorphic-association?view=asciicast
Appreciate the help.
Error
NameError in Links#index
uninitialized constant ForumPost::Link
Extracted source (around line #4):
1: <h1>Links</h1>
2:
3: <ul id="links">
4: <% #links.each do |link| %>
5: <li><%= link.display_name %></li>
6: <% end %>
7: </ul>
routes
resources :forum_posts do
resources :comments
resources :links
end
Models
*models/forum_posts.rb*
class ForumPost < ActiveRecord::Base
attr_accessible :content, :display_name, :section, :user_id
has_many :comments, :as => :commentable
has_many :links, :as => :linkable
end
models/comments.rb
class Comment < ActiveRecord::Base
attr_accessible :commentable_id, :commentable_type, :content, :user_id
belongs_to :commentable, :polymorphic => true
end
models/comments.rb
class Links < ActiveRecord::Base
attr_accessible :description, :display_name, :inamge, :linkable_id, :linkable_type, :user_id
belongs_to :linkable, :polymorphic => true
end
Controllers
*controllers/comments_controller.rb*
class CommentsController < ApplicationController
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
def index
#commentable = find_commentable
#comments = #commentable.comments
end
def create
#commentable = find_commentable
#comment = #commentable.comments.build(params[:comment])
if #comment.save
flash[:notice] = "Successfully saved comment."
redirect_to :id => nil
else
render :action => 'new'
end
end
end
*controller/links_controller.rb*
class LinksController < ApplicationController
def find_linkable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
def index
#linkable = find_linkable
#links = #linkable.links
end
def create
#linkable = find_linkable
#link = #linkable.links.build(params[:link])
if #link.save
flash[:notice] = "Successfully saved link."
redirect_to :id => nil
else
render :action => 'new'
end
end
end
Views
views/comments/index.html.erb
<h1>Comments</h1>
<ul id="comments">
<% #comments.each do |comment| %>
<li><%= comment.content %></li>
<% end %>
</ul>
<h2>New Comment</h2>
<%= form_for [#commentable, Comment.new()] do |form| %>
<%= form.label :content %><br/>
<%= form.text_area :content, :rows => 5 %><br/>
<%= form.submit "Add comment" %>
<% end %>
views/links/index.html.erb
<h1>Links</h1>
<ul id="links">
<% #links.each do |link| %>
<li><%= link.display_name %></li>
<% end %>
</ul>
<h2>New Link</h2>
<%= form_for [#linkable, Link.new()] do |form| %>
<%= form.label :display_name %><br/>
<%= form.text_area :display_name %><br/>
<%= form.submit "Add link" %>
<% end %>
Your Links model should be placed at app/models/link.rb and should be called Link, not Links.

Resources