-
-
Save leastbad/5ccc8daa468701fab89abb21370807dc to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class="card"> | |
<div class="card-header"> | |
<h3 class="card-title"> | |
<%= notification.to_notification.in_app_subject %> | |
</h3> | |
</div> | |
<div class="card-body"> | |
<%= notification.to_notification.in_app_body %> | |
</div> | |
</div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
internal_payload = { | |
model_app_name: model.app.name, | |
model_title: model.title, | |
model_url: Router.gig_url(model_id), | |
recipient_full_name: Current.user.full_name | |
} | |
model_payload = model.notification_payload || {} | |
# ensure that our internal payload overrides any values under the same key from the gig notification payload | |
params = gig_payload.merge(internal_payload) | |
Notifications::Channel | |
.deliver_later(via: :new_internal_event, | |
to: model.user, | |
with: params) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Notification < ApplicationRecord | |
include Noticed::Model | |
belongs_to :recipient, polymorphic: true | |
def to_notification | |
Notifications::Channel.as_notifier(via: type, with: params) | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class NotificationMailer < ApplicationMailer | |
# This method is needed to allow the `method_missing` to be called | |
def self.action_methods | |
all_inclusive = Class.new do | |
def include?(_) | |
true | |
end | |
end | |
all_inclusive.new | |
end | |
def method_missing(method_name, *_args) | |
return super unless method_name.to_s.end_with?('_notifier') | |
# parms == {:recipient=>#<User>, :record=>#<Notification> || nil } | |
notification = if params[:record] | |
params[:record].to_notification | |
else | |
Notifications::Channel.as_notifier(via: method_name, with: params) | |
end | |
mail(to: params[:recipient].email, | |
subject: notification.email_subject) do |format| | |
format.html { render html: notification.email_html_body.html_safe } | |
format.text { render plain: notification.email_text_body } | |
end | |
end | |
def respond_to_missing?(method_name, include_private = false) | |
super | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Notifications::Broadcast < ApplicationRecord | |
belongs_to :channel, class_name: 'Notifications::Channel' | |
belongs_to :worker_filter | |
serialize :payload, JSON | |
validates :payload, presence: true | |
validate :not_sent_to_internal_channel | |
after_create_commit :deliver_notification | |
def recipients | |
service = WorkerDocumentService.new(channel.app) | |
worker_documents = service.find_by_worker_filter(worker_filter) # rubocop:disable Rails/DynamicFindBy | |
all_ids = worker_documents.map { |document| document['app_worker_id'] } | |
User.joins(:worker).merge(Worker.joins(:app_workers)).where(app_workers: { id: all_ids }) | |
end | |
private | |
def deliver_notification | |
data = payload.is_a?(String) ? JSON.parse(payload) : payload | |
channel.deliver(to: recipients, with: data) | |
end | |
def not_sent_to_internal_channel | |
return if channel_id.nil? | |
return unless channel.internal? | |
errors.add(:channel, :broadcast_to_internal_channel) | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Notifications::Channel < ApplicationRecord | |
belongs_to :app | |
has_many :templates, class_name: 'Notifications::Template', dependent: :destroy | |
has_many :broadcasts, class_name: 'Notifications::Broadcast', dependent: :destroy | |
has_many :user_preferences, class_name: 'Notifications::Preference', dependent: :destroy | |
has_one :in_app_template, -> { in_app.order(updated_at: :desc) }, class_name: 'Notifications::Template', inverse_of: :channel | |
has_one :email_html_template, -> { email.html.order(updated_at: :desc) }, class_name: 'Notifications::Template', inverse_of: :channel | |
has_one :email_text_template, -> { email.text.order(updated_at: :desc) }, class_name: 'Notifications::Template', inverse_of: :channel | |
validates :name, presence: true, uniqueness: { scope: :app_id, case_sensitive: true } | |
scope :internal, -> { where(internal: true) } | |
def self.deliver(via:, to:, with:) | |
as_notifier(via: via, with: with) | |
&.deliver(to) | |
end | |
def self.deliver_later(via:, to:, with:) | |
as_notifier(via: via, with: with) | |
&.deliver_later(to) | |
end | |
def self.as_notifier(via:, with:) | |
channel_name = via.to_s.remove('_notifier').remove('Notifier') | |
find_by_name(channel_name) # rubocop:disable Rails/DynamicFindBy | |
&.as_notifier(with: with) | |
end | |
def self.find_by_name(channel_name) | |
find_by(name: [ | |
channel_name, | |
channel_name.to_s.underscore, | |
channel_name.to_s.camelize | |
]) | |
end | |
def deliver(to:, with:) | |
as_notifier(with: with) | |
.deliver(to) | |
end | |
def deliver_later(to:, with:) | |
as_notifier(with: with) | |
.deliver_later(to) | |
end | |
def as_notifier(with:) | |
to_noticed_class | |
.with(with) | |
end | |
private | |
def to_noticed_class | |
# we need local variables for the `define_method` closures to have access to these data | |
in_app_subject_template = in_app_template&.subject | |
in_app_body_template = in_app_template&.body | |
email_subject_template = (email_html_template || email_text_template)&.subject | |
email_html_body_template = email_html_template&.body | |
email_text_body_template = email_text_template&.body | |
channel_id = id | |
klass = Class.new(Noticed::Base) do | |
deliver_by :database, if: :in_app_notifications? | |
deliver_by :email, mailer: 'NotificationMailer', if: :email_notifications? | |
define_method :in_app_notifications? do | |
preference = preferences&.in_app? | |
return preference unless preference.nil? | |
true | |
end | |
define_method :email_notifications? do | |
preference = preferences&.email? | |
return preference unless preference.nil? | |
true | |
end | |
define_method :in_app_subject do | |
render_template(in_app_subject_template) | |
end | |
define_method :in_app_body do | |
render_template(in_app_body_template).html_safe | |
end | |
define_method :email_subject do | |
render_template(email_subject_template) | |
end | |
define_method :email_html_body do | |
render_template(email_html_body_template) | |
end | |
define_method :email_text_body do | |
render_template(email_text_body_template) | |
end | |
define_method :preferences do | |
recipient.notification_preferences.find_by(channel_id: channel_id) | |
end | |
define_method :render_template do |template| | |
return '' unless template | |
template % attributes | |
end | |
define_method :attributes do | |
original = params.symbolize_keys | |
Hash.new { |_, k| original[k] || '' } | |
end | |
end | |
# to avoid ever seeing something like #<#<Class:0x00007fd58318d340>:0x00007fd5868c5088> in logs or the console | |
Object.const_set "#{app.name.camelize}#{name.camelize}Notifier", klass | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class NotificationsController < Workers::BaseController | |
def index | |
@notifications = current_user.notifications.unread.newest_first.load | |
@notifications.mark_as_read! | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Notifications::Preference < ApplicationRecord | |
belongs_to :channel, class_name: 'Notifications::Channel' | |
belongs_to :user | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Notifications::Template < ApplicationRecord | |
belongs_to :channel, class_name: 'Notifications::Channel' | |
enum deliver_by: { email: 'email', in_app: 'in_app' }, _prefix: false | |
enum format: { text: 'text', html: 'html' }, _prefix: false | |
validates :subject, presence: true | |
validates :body, presence: true | |
validates :deliver_by, presence: true | |
validates :format, presence: true | |
validates :channel, uniqueness: { scope: [:format, :deliver_by] } | |
# I find this semantically more readable than the default `deliver_by_in_app` generated by the `enum` | |
scope :deliver_in_app, -> { where(deliver_by: :in_app) } | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
ActiveRecord::Schema.define(version: 2021_02_12_094634) do | |
create_table "notifications", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| | |
t.string "recipient_type", null: false | |
t.bigint "recipient_id", null: false | |
t.string "type", null: false | |
t.json "payload" | |
t.datetime "read_at" | |
t.datetime "created_at", precision: 6, null: false | |
t.datetime "updated_at", precision: 6, null: false | |
t.index ["read_at"], name: "index_notifications_on_read_at" | |
t.index ["recipient_type", "recipient_id"], name: "index_notifications_on_recipient_type_and_recipient_id" | |
end | |
create_table "notifications_broadcasts", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| | |
t.bigint "channel_id", null: false | |
t.text "params" | |
t.datetime "created_at", precision: 6, null: false | |
t.datetime "updated_at", precision: 6, null: false | |
t.bigint "worker_filter_id", null: false | |
t.index ["channel_id"], name: "index_notifications_broadcasts_on_channel_id" | |
t.index ["worker_filter_id"], name: "index_notifications_broadcasts_on_worker_filter_id" | |
end | |
create_table "notifications_channels", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| | |
t.string "name" | |
t.bigint "app_id", null: false | |
t.datetime "created_at", precision: 6, null: false | |
t.datetime "updated_at", precision: 6, null: false | |
t.boolean "internal", default: false | |
t.index ["app_id"], name: "index_notifications_channels_on_app_id" | |
end | |
create_table "notifications_preferences", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| | |
t.bigint "channel_id", null: false | |
t.bigint "user_id", null: false | |
t.boolean "in_app" | |
t.boolean "email" | |
t.datetime "created_at", precision: 6, null: false | |
t.datetime "updated_at", precision: 6, null: false | |
t.index ["channel_id"], name: "index_notifications_preferences_on_channel_id" | |
t.index ["user_id"], name: "index_notifications_preferences_on_user_id" | |
end | |
create_table "notifications_templates", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| | |
t.string "subject" | |
t.text "body" | |
t.string "deliver_by" | |
t.string "format" | |
t.bigint "channel_id", null: false | |
t.datetime "created_at", precision: 6, null: false | |
t.datetime "updated_at", precision: 6, null: false | |
t.index ["channel_id"], name: "index_notifications_templates_on_channel_id" | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment