Rails Blog In VS Code-Noticed V1
How To Create A Blog in VS Code — Part VIII — Notifications for your Ruby on Rails app — RailsSeries#Episode 10
Let us continue with the preceding example and integrate Noticed. Noticed, a gem developed by Chris Oliver, facilitates the transmission of notifications of diverse kinds through different channels to various recipients within your application:
In this post (based on Deanin):
You will:
Learn How to Use Noticed version 1.5.7;
Learn how to use Bootstrap Badges;
Set up Turbo JavaScript to review a comment;
Enhance your blog by implementing comment notifications;
Create a bell icon in your navigation bar;
Counter to keep track of unread notifications.
Additionally, display read notifications in the dropdown menu.
Each comment notification will direct you to the
corresponding blog post and provide information
about the user who sent the notification.
Welcome!
Let’s Get Started!
Note: if you get stuck, please see my repo.
0#step — Download the last version (v7) post here and prepare your vscode
environment.
Let’s open a new Feature Branch: git checkout -b use_noticed_gem
.
1#step — On Terminal, Run:
bundle add noticed -v 1.5.7
2#step — Now:
rails g noticed:model
bundle install
rails db:migrate
Output:
🚚 Your notifications database model has been generated!
Next steps:
1. Run "rails db:migrate"
2. Add "has_many :notifications, as: :recipient" to your User model(s).
3. Generate notifications with "rails g noticed:notification"
3#step — Then, open this file and type:
app/models/user.rb
has_many :notifications, as: :recipient, dependent: :destroy
4#step — On Terminal:
rails g noticed:notification CommentNotification
Output:
create app/notifications/comment_notification.rb
5#step — Open this file and type:
app/models/post.rb
has_noticed_notifications model_name: 'Notification'
has_many :notifications, through: :user, dependent: :destroy
6#step — Open this file and type:
app/models/comment.rb
...
after_create_commit :notify_recipient
before_destroy :cleanup_notifications
has_noticed_notifications model_name: 'Notification'
private
def notify_recipient
CommentNotification.with(comment: self, post:).deliver_later(post.user)
# Using deliver_later will execute a background job when you hit 'post' for your comment.
# This means that it won't stall the interface while the job is being processed.
# You can simply post your comment and continue with your tasks, while in the background,
# the Rails system takes care of delivering the comment in the background.
end
def cleanup_notifications
notifications_as_comment.destroy_all
# same as in console: Notification.where(comment_id: :comment_id)
end
...
7#step — Open this file and uncomment delivering by DB, defining def message
and def URL
methods:
app/notifications/comment_notification.rb
# To deliver this notification:
#
# CommentNotification.with(post: @post).deliver_later(current_user)
# CommentNotification.with(post: @post).deliver(current_user)
class CommentNotification < Noticed::Base
# Add your delivery methods
#
deliver_by :database
# deliver_by :email, mailer: "UserMailer"
# deliver_by :slack
# deliver_by :custom, class: "MyDeliveryMethod"
# Add required params
#
# param :post
# Define helper methods to make rendering easier.
def message
@post = Post.find(params[:comment][:post_id])
@comment = Comment.find(params[:comment][:id])
@user = User.find(@comment.user_id)
"#{@user.name} replied to #{@post.title.truncate(14)}"
end
# That allows us to do our url construction
def url
post_path(Post.find(params[:comment][:post_id]))
end
end
8#step —To preview how notifications appear, run the application and create some comments. Afterward, launch the rails console
.
Notification.all
irb(main):001:0> Notification.all
Notification Load (0.1ms) SELECT "notifications".* FROM "notifications"
Comment Load (0.1ms) SELECT "comments".* FROM "comments" WHERE "comments"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]]
Post Load (0.1ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
Comment Load (0.1ms) SELECT "comments".* FROM "comments" WHERE "comments"."id" = ? LIMIT ? [["id", 4], ["LIMIT", 1]]
Post Load (0.0ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT ? [["id", 2], ["LIMIT", 1]]
=>
[#<Notification:0x00007f55ff5fa2d8
id: 1,
recipient_type: "User",
recipient_id: 1,
type: "CommentNotification",
params:
{:comment=>
#<Comment:0x00007f55ff1e2a10
id: 3,
post_id: 1,
user_id: 2,
created_at: Sun, 17 Mar 2024 23:00:00.266022000 UTC +00:00,
updated_at: Sun, 17 Mar 2024 23:00:00.280587000 UTC +00:00>,
:post=>
#<Post:0x00007f55ff18d330
id: 1,
title: "First Post",
body: "From Example.",
created_at: Sun, 17 Mar 2024 22:46:20.617547000 UTC +00:00,
updated_at: Sun, 17 Mar 2024 23:00:00.347376000 UTC +00:00,
views: 5,
user_id: 1>},
read_at: nil,
created_at: Sun, 17 Mar 2024 23:00:00.329481000 UTC +00:00,
updated_at: Sun, 17 Mar 2024 23:00:00.329481000 UTC +00:00>,
#<Notification:0x00007f55ff29d2c0
id: 2,
recipient_type: "User",
recipient_id: 2,
type: "CommentNotification",
params:
{:comment=>
#<Comment:0x00007f55ff2cf338
id: 4,
post_id: 2,
user_id: 1,
created_at: Sun, 17 Mar 2024 23:00:55.881498000 UTC +00:00,
updated_at: Sun, 17 Mar 2024 23:00:55.888500000 UTC +00:00>,
:post=>
#<Post:0x00007f55fefff3d8
id: 2,
title: "Second Post",
body: "From Another.",
created_at: Sun, 17 Mar 2024 22:47:43.070307000 UTC +00:00,
updated_at: Sun, 17 Mar 2024 23:00:55.928286000 UTC +00:00,
views: 5,
user_id: 2>},
read_at: nil,
created_at: Sun, 17 Mar 2024 23:00:55.906453000 UTC +00:00,
updated_at: Sun, 17 Mar 2024 23:00:55.906453000 UTC +00:00>]
9#step —Navigate to the “Bootstrap 5 — Icons — Learn More” link and copy the CDN provided on the webpage: https://icons.getbootstrap.com/. Paste the copied CDN directly below the Bootstrap 5 link in your file. Then, open the file:
app/views/layouts/application.html.erb
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
10#step — Open the Navbar file and insert this rendering command above the session manager:
app/views/layouts/_navbar.html.erb
<%= render 'layouts/notifications' %>
...above here...
<%= render 'user/session_manager' %>
11#step — Create this file and type:
app/views/layouts/_notifications.html.erb
<% if current_user %>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<span class="badge rounded-pill bg-secondary">
<%= @unread.count >= 9 ? "9+" : @unread.count %> <i class="bi bi-bell-fill"></i>
</span>
</a>
<ul class="dropdown-menu dropdown-menu-lg-end" aria-labelledby="navbarDropdown">
<% @unread.each do |notification| %>
<%= render 'layouts/notification', notification: notification %>
<% end %>
<% if @read.count > 0 && @unread.count > 0 %>
<li>
<hr class="dropdown-divider">
</li>
<% elsif @read.count + @unread.count == 0 %>
<li class="dropdown-item">
No notifications yet.
</li>
<% end %>
<% @read.each do |notification| %>
<%= render 'layouts/notification', notification: notification %>
<% end %>
</ul>
</li>
<% end %>
12#step — Create this partial too:
app/views/layouts/_notification.html.erb
<li class="dropdown-item">
<%= link_to notification.to_notification.message, notification.to_notification.url %>
</li>
13#step — GoTo :
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :set_notifications, if: :current_user
private
def set_notifications
notifications = Notification.where(recipient: current_user).newest_first.limit(9)
@unread = notifications.unread
@read = notifications.read
end
end
14#step — GoTo:
app/controllers/posts_controller.rb
In the show()
method, type:
mark_notifications_as_read
And in the private area
, create this method:
def mark_notifications_as_read
return unless current_user
notifications_to_mark_as_read = @post.notifications_as_post.where(recipient: current_user)
notifications_to_mark_as_read.update_all(read_at: Time.zone.now)
end
15#step — For those who’ve caught on, that’s all! 😎️
Now, onto the final episode, where the interface button shifts from Edit
to Cancel
with a simple toggle.
GoTo:
app/views/comments/_comment.html.erb
<div class="comment-<%= comment.id %> container"
style="border: 1px solid black; padding: 1em; margin: 1em;">
<%= comment.user.email %><br />
<% if (comment.updated_at - comment.created_at ) > 1 %>
<span>Edited <%= time_ago_in_words(comment.updated_at) %> ago</span>
<% else %>
<span>Posted <%= time_ago_in_words(comment.created_at) %> ago</span>
<% end %>
<% if current_user == comment.user %>
<div class="btn-group float-end">
<%= link_to "Edit", nil, remote: true, class: "btn btn-warning",
data: {
controller: "comments",
action: "comments#toggleForm",
comments_form_param: "edit-form-#{comment.id}",
comments_body_param: "comment-body-#{comment.id}",
comments_edit_param: "edit-button-#{comment.id}"},
id: "edit-button-#{comment.id}" %>
<%= button_to "Delete", [post, comment], class:"btn btn-danger", method: :delete %>
</div>
<div id="edit-form-<%= comment.id %>" class="d-none" >
<%= render 'comments/form', post: @post, comment: comment, submit_label: "Update" %>
</div>
<% end %>
<hr />
<div id="comment-body-<%= comment.id %>">
<%= comment.body %>
</div>
</div>
16#step — Go to:
app/javascript/controllers/comments_controller.js
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
initialize() {}
connect() {}
toggleForm(event) {
console.log("I clicked the edit button.");
event.preventDefault();
event.stopPropagation();
const formID = event.params["form"];
const commentBodyID = event.params["body"];
const editButtonID = event.params["edit"];
const form = document.getElementById(formID);
const commentBody = document.getElementById(commentBodyID);
const editButton = document.getElementById(editButtonID);
form.classList.toggle("d-none");
form.classList.toggle("mt-5");
commentBody.classList.toggle("d-none");
this.toggleEditButton(editButton);
}
toggleEditButton(editButton) {
if (editButton.innerText === "Edit") {
editButton.innerText = "Cancel";
this.toggleEditButtonClass(editButton);
} else {
editButton.innerText = "Edit";
this.toggleEditButtonClass(editButton);
}
}
toggleEditButtonClass(editButton) {
editButton.classList.toggle("btn-secondary");
editButton.classList.toggle("btn-warning");
}
}
Run and hope for the best!
That’s All, Folks!
Stay tuned for the next episode where we delve into Noticed v2 👋️👋️👋️!
Credits & References
Add Comment Notifications To Your Blog | Ruby On Rails For Beginners Part 5 by Deanin
Bootstrap5 Badges by getbootstrap.com
Noticed — 1.5.7— Docs by rubydoc.info/gems/noticed/1.5.7 (this tut uses it)
Noticed — 2.1.3 — Docs by rubydoc.info/gems/noticed/2.1.3 (the next one, soon…)
Related Posts:
00# Episode — RailsSeries — Installing Ruby on Rails Using ASDF — Why ASDF is Better Than RBENV for Rails Bootstrap App?
01# Episode — RailsSeries — How To Send Email In Rails 7? — User Registration and Onboarding.
02# Episode — RailsSeries — 14 Ruby Extensions 4 Vs Code — Based On This Deanin’s video.
03# Episode — RailsSeries — A Rails Blog In VS Code — Quick Start — How To Create A Blog in VS Code — Part I
04# Episode — RailsSeries — A Rails Blog In VS Code — Styling — How To Create A Blog in VS Code — Part II
05# Episode — RailsSeries — A Rails Blog In VS Code — Create Posts — How To Create A Blog in VS Code — Part III
06# Episode — RailsSeries — A Rails Blog In VS Code — Posts Tips&Tricks — How To Create A Blog in VS Code — Part IV
07# Episode — RailsSeries — A Rails Blog In VS Code — Devise — How To Create A Blog in VS Code — Part V
08# Episode — RailsSeries — A Rails Blog In VS Code — Add Comments to Post — How To Create A Blog in VS Code — Part VI
09# Episode — RailsSeries — Rails Blog In VS Code — Using Stimulus — How To Create A Blog in VS Code — Part VII
10# Episode — RailsSeries — Rails Blog In VS Code — Noticed V1 — Notifications for your Ruby on Rails app — Part VIII (this one)
11# Episode — RailsSeries — Rails Blog In VS Code — Noticed V2 — Notifications for your Ruby on Rails app — Part IX
Tag it all:
git tag -a rails_blog_v8 -m "Blog in Rails 8 - v1.0: Go to https://j3-rails-blog-demo.herokuapp.com/" -m "0- Learn How to Use Noticed Gem;" -m "1- Learn notifications version 1;" -m "2- Go to https://github.com/excid3/noticed;" -m "3- Noticed is a gem that allows your application to send notifications of varying types, over various mediums, to various recipients" -m "Thank you for downloading this project 😘️👌️👋️😍️"
git push origin rails_blog_v8
For your convenience(git cmmds):
-----------------------------
I made a mistake
and decided to discontinue
the initial use_noticed_gem.
-----------------------------
git checkout master
git -d use_noticed_gem
git branch -d use_noticed_gem
git reset --hard HEAD
rails s
-----------------------------
Init again :)
-----------------------------
git checkout -b use_noticed_gem
bundle add noticed -v 1.5.7
rails g noticed:model
bundle install
rails db:migrate
rails g noticed:notification CommentNotification
rails s
rails c
clear
rails s
git status
git add -A
git commit -m ":lipstick: feat: Add Noticed v1 Gem"
git push --set-upstream origin use_noticed_gem
git branch --list
git checkout master
git fetch
git status
git pull
git tag -a rails_blog_v6 -m "Blog in Rails 7 - v1.0: Go to https://j3-rails-blog-demo-5a0a55d44e12.herokuapp.com/" -m "0- Add Comments to Each Post;" -m "1- Set up Action Text, which is a framework in Ruby on Rails for handling rich text content;" -m "2- Upload Rails 7 project to Heroku." -m "Thank you for downloading this project 😘️👌️👋️😍️"
-----------------------------
I made a mistake: wrong tag:/
-----------------------------
git tag -d rails_blog_v6
-----------------------------
git tag -a rails_blog_v8 -m "Blog in Rails 8 - v1.0: Go to https://j3-rails-blog-demo.herokuapp.com/" -m "0- Learn How to Use Noticed Gem;" -m "1- Learn notifications version 1;" -m "2- Go to https://github.com/excid3/noticed;" -m "3- Noticed is a gem that allows your application to send notifications of varying types, over various mediums, to various recipients" -m "Thank you for downloading this project 😘️👌️👋️😍️"
git push origin rails_blog_v8
history > history