Follow this guide to upgrade your Noticed implementation to the next version
We've added a counter cache to Noticed::Event to keep track of the associated notifications.
Run the following command to copy over the migrations:
rails noticed:install:migrations
We've made some major changes to Noticed to simplify and support more delivery methods.
Instead of having models live in your application, Noticed v2 adds models managed by the gem.
Delete the Notification
model at app/models/notification.rb
.
Then run the new migrations:
rails noticed:install:migrations
rails db:migrate
To migrate your data to the new tables, loop through your existing notifications and create new records for each one. You can do this in a Rake task or in the Rails console:
# Temporarily define the Notification model to access the old table
class Notification < ActiveRecord::Base
self.inheritance_column = nil
end
# Migrate each record to the new tables
Notification.find_each do |notification|
attributes = notification.attributes.slice("type", "created_at", "updated_at").with_indifferent_access
attributes[:type] = attributes[:type].sub("Notification", "Notifier")
attributes[:params] = Noticed::Coder.load(notification.params)
attributes[:params] = {} if attributes[:params].try(:has_key?, "noticed_error") # Skip invalid records
# Extract related record to `belongs_to :record` association
# This allows ActiveRecord associations instead of querying the JSON data
# attributes[:record] = attributes[:params].delete(:user) || attributes[:params].delete(:account)
attributes[:notifications_attributes] = [{
type: "#{attributes[:type]}::Notification",
recipient_type: notification.recipient_type,
recipient_id: notification.recipient_id,
read_at: notification.read_at,
seen_at: notification.read_at,
created_at: notification.created_at,
updated_at: notification.updated_at
}]
Noticed::Event.create!(attributes)
end
After migrating, you can drop the old notifications table.
To be more consistent with Rails:
mark_as_read!
has been renamed to mark_as_read
mark_as_unread!
has been renamed to mark_as_unread
Noticed::Base
has been deprecated in favor of Noticed::Event
. This is an STI model that tracks all Notifier deliveries and recipients.
class CommentNotifier < Noticed::Event
end
The database delivery is now baked into notifications.
You will need to remove deliver_by :database
from your notifiers.
For clarity, we've renamed app/notifications
to app/notifiers
.
Notifiers - the class that delivers notifications Notification - the database record of the notification
We recommend renaming your existing classes to match. You'll also need to update the type
column on existing notifications when renaming.
Noticed::Notification.find_each do |notification|
notification.update(type: notification.type.sub("Notification", "Notifier"))
end
Configuration for each delivery method can be contained within a block now. This improves organization for delivery method options by defining them in the block. Procs/Lambdas will be evaluated when needed and symbols can be used to call a method.
If you are using a symbol to call a method, we pass the notification object as an argument to the method. This allows you to access the notification object within the method.
Your method must accept a single argument. If you don't need to use the object you can just use (*)
.
class CommentNotifier < Noticed::Event
deliver_by :action_cable do |config|
config.channel = "NotificationChannel"
config.stream = ->{ recipient }
config.message = :to_websocket
end
def to_websocket(notification)
{ foo: :bar }
end
end
class CommentNotifier < Noticed::Event
include IosNotifier
def data_only?
false
end
def url
comment_thread_path(record.thread)
end
end
module IosNotifier
extend ActiveSupport::Concern
included do
deliver_by :ios do |config|
config.format = :ios_format
config.apns_key = :ios_cert
config.key_id = :ios_key_id
config.team_id = :ios_team_id
config.bundle_identifier = :ios_bundle_id
config.device_tokens = :ios_device_tokens
config.if = :send_ios_notification?
end
end
def ios_format(apn)
apn.alert = { title:, body: } unless data_only?
apn.mutable_content = true
apn.content_available = true
apn.sound = "notification.m4r"
apn.custom_payload = {
url:,
type: self.class.name,
id: record.id,
image_url: "" || image_url,
params: params.to_json
}
end
def ios_cert(*)
Rails.application.credentials.dig(:ios, Rails.env.to_sym, :apns_token_cert)
end
def ios_key_id(*)
Rails.application.credentials.dig(:ios, Rails.env.to_sym, :key_id)
end
def ios_team_id(*)
Rails.application.credentials.dig(:ios, Rails.env.to_sym, :team_id)
end
def ios_bundle_id(*)
Rails.application.credentials.dig(:ios, Rails.env.to_sym, :bundle_identifier)
end
def ios_device_tokens(notification)
notification.recipient.ios_device_tokens
end
def send_ios_notification?(notification)
recipient = notification.recipient
return false unless recipient.is_a?(User)
recipient.send_notifications?
end
end
Notifications are always delivered later now. deliver
and deliver_later
perform the same action.
param
and params
have been renamed to required_param(s)
to be more clear.
class CommentNotifier < Noticed::Event
required_param :comment
required_params :account, :comment
end
Helper methods defined in Notifiers have changed slightly. In order to access helper methods from Notification objects, for example:
<div>
<% @user.notifications.each do |notification| %>
<%= link_to notification.message, notification.url %>
<% end %>
</div>
You’ll need to wrap helper methods in the new notification_methods
block within your Notifier:
class NewCommentNotifier < Noticed::Event
deliver_by :email do |config|
# ...
end
notification_methods do
# I18n helpers still available here
def message
t(".message")
end
# URL helpers are available here too
def url
user_post_path(recipient, params[:post])
end
end
end
In order to extend the Noticed models you'll need to use a concern an a to_prepare block:
# config/initializers/noticed.rb
module NotificationExtensions
extend ActiveSupport::Concern
included do
belongs_to :organisation
scope :filter_by_type, ->(type) { where(type:) }
scope :exclude_type, ->(type) { where.not(type:) }
end
# You can also add instance methods here
end
Rails.application.config.to_prepare do
# You can extend Noticed::Event or Noticed::Notification here
Noticed::Event.include NotificationExtensions
Noticed::Notification.include NotificationExtensions
end
has_noticed_notifications
has been superceded with a record
polymorphic relationship that can be directly associated and queried with ActiveRecord.
We recommend backfilling the record
association if your notification params has a primary related record and switching to a has_many association instead.
class Comment < ApplicationRecord
has_many :noticed_events, as: :record, dependent: :destroy, class_name: "Noticed::Event"
end
If you would like to keep the JSON querying, you can implement a method for querying your model depending on the database you use:
# Define the
param_name = "user"
# PostgreSQL
model.where("params @> ?", Noticed::Coder.dump(param_name.to_sym => self).to_json)
# MySQL
model.where("JSON_CONTAINS(params, ?)", Noticed::Coder.dump(param_name.to_sym => self).to_json)
# SQLite
model.where("json_extract(params, ?) = ?", "$.#{param_name}", Noticed::Coder.dump(self).to_json)
# Other
model.where(params: {param_name.to_sym => self})
Recipients can be associated with notifications using the following. This is useful for displaying notifications in your UI.
class User < ApplicationRecord
has_many :notifications, as: :recipient, dependent: :destroy, class_name: "Noticed::Notification"
end
Options for delivery methods have been renamed for clarity and consistency.
- The
format
option has been renamed tomessage
. - The
Noticed::NotificationChannel
has been removed and an example channel is provided in the ActionCable docs.
method
is now a required option. Previously, it was inferred from the notification name but we've decided it would be better to be explicit.
- The
format
option has been renamed tojson
. - The
device_tokens
option is now required and should return an Array of device tokens. - The
invalid_token
option replaces thecleanup_device_tokens
method for handling invalid/expired tokens. - We no longer wrap the json payload in the
message{}
key. This means we are more compatible with the FCM docs and any future changes that Google make.
- The
cert_path
option has been renamed toapns_key
and should be given the key and not a path. - The
device_tokens
option is now required and should return an Array of device tokens. - The
invalid_token
option replaces thecleanup_device_tokens
method for handling invalid/expired tokens.
- The
format
option has been renamed tojson
.
- The
format
option has been renamed tojson
. - The
url
option now defaults to"https://slack.com/api/chat.postMessage
instead ofRails.application.credentials.dig(:slack, :notification_url)
- Twilio has been renamed to
:twilio_messaging
to make room for:twilio_voice
and other services they may provide in the future. - The
format
option has been renamed tojson
.
- Vonage has been renamed to
:vonage_sms
to make room for other Vonage services in the future. - The
format
option has been renamed tojson
and is now required.