0.01
Repository is archived
Low commit activity in last 3 years
No release in over a year
Dead simple configurable user notifications with little overhead.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Project Readme

This unfinished experiment is no longer active. This was never actually ready and really shouldn't be used.

Correspondent

Dead simple configurable user notifications using the Correspondent engine!

Configure subscribers and publishers and let Correspondent deal with all notification work with very little overhead.

Installation

Add this line to your application's Gemfile:

gem 'correspondent'

And then execute:

$ bundle

Create the necessary migrations:

$ rails g correspondent:install

Usage

Model configuration

Notifications can easily be setup using Correspondent. The following example goes through the basic usage. There are only two steps for the basic configuration:

  1. Invoke notifies and configure the subscriber (user in this case), the triggers (method purchase in this case) and desired options
  2. Define the to_notification method to configure information that needs to be used to create notifications
# Example model using Correspondent
# app/models/purchase.rb
class Purchase < ApplicationRecord
  belongs_to :user
  belongs_to :store

  # Notifies configuration
  # First argument is the subscriber (the one that receives a notification). Can be an NxN association as well (e.g.: users) which will create a notification for each associated record.
  # Second argument are the triggers (the method inside that model that triggers notifications). Can be an array of symbols for multiple triggers for the same entity.
  # Third argument are generic options as a hash
  notifies :user, :purchase, avoid_duplicates: true

  # Many notifies definitions can be used for different subscribers
  # In the following case, every time purchase is invoked the following will happen:
  # 1. A notification will be created for `user`
  # 2. A notification will be created for `store`
  # 3. An email will be triggered using the `StoreMailer` (invoking a method called purchase_email)
  notifies :store, :purchase, avoid_duplicates: true, mailer: StoreMailer

  # `notifies` will hook into the desired triggers.
  # Every time this method is invoked by an instance of Purchase
  # a notification will be created in the database using the
  # `to_notification` method. The handling of notifications is
  # done asynchronously to cause as little overhead as possible.
  def purchase
    # some business logic
  end

  # The to_notification method returns the information to be
  # used for creating a notification. This will be invoked automatically
  # by the gem when a trigger occurs.
  # When calling this method, entity and trigger will be passed. Entity
  # is the subscriber (in this example, `user`). Trigger is the method
  # that triggered the notification. With this approach, the hash
  # built to pass information can vary based on different triggers.
  # If entity and trigger will not be used, this can simply be defined as
  #
  # def to_notification(*)
  #   # some hash
  # end
  def to_notification(entity:, trigger:)
    {
      title: "Purchase ##{id} for #{entity} #{send(entity).name}",
      content: "Congratulations on your recent #{trigger} of #{name}",
      image_url: "",
      link_url: "/purchases/#{id}",
      referrer_url: "/stores/#{store.id}"
    }
  end
end

Correspondent can also trigger emails if desired. To trigger emails, the mailer class should be passed as an object and should implement a method follwing the naming convention.

# app/models/purchase.rb

class Purchase < ApplicationRecord
  belongs_to :user

  # Pass the desired mailer in the `mailer:` option
  notifies :user, :purchase, mailer: ApplicationMailer

  def purchase
    # some business logic
  end
end

# app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: 'from@example.com'
  layout 'mailer'

  # The mailer should implement methods following the naming convention of
  # #{trigger}_email(triggering_instance)
  #
  # In this case, the `trigger` is the method purchase, so Correspondent will look for
  # the purchase_email method. It will always pass the instance that triggered the email
  # as an argument.
  def purchase_email(purchase)
    @purchase = purchase
    mail(to: purchase.user.email, subject: "Congratulations on the purchase of #{purchase.name}")
  end
end

To reference the created notifications in the desired model, use the following association:

# app/models/purchase.rb

class User < ApplicationRecord
  has_many :purchases
  has_many :notifications, class_name: "Correspondent::Notification", as: :subscriber
end

class Purchase < ApplicationRecord
  belongs_to :user
  has_many :notifications, class_name: "Correspondent::Notification", as: :publisher
end

If a specific column is not needed for your project, remove them from the generated migrations and don't return the respective attribute inside the to_notification method.

Options

The available options, their default values and their explanations are listed below.

# Avoid duplicates
# Prevents creating new notifications if a non dismissed notification for the same publisher and same subscriber already exists
notifies :some_resouce, :trigger, avoid_duplicates: false

# Mailer
# The Mailer class that implements the desired mailer triggers to send emails. Default is nil (doesn't send emails).
notifies :some_resouce, :trigger, mailer: nil

# Email only
# For preventing the creation of notifications and only trigger emails, add the email_only option
notifies :some_resouce, :trigger, email_only: false

# Conditionals
# If or unless options can be passed either as procs/lambdas or symbols representing the name of a method
# These will be evaluated in an instance context, every time trigger is invoked
notifies :some_resource, :trigger, if: :should_be_notified?

notifies :some_resource, :trigger, unless: -> { should_be_notified? && is_eligible? }

JSON API

Correspondent exposes a few APIs to be used for handling notification logic in the application.

All APIs use the stale? check. So if passing the If-None-Match header, the API will support returning 304 (not modified) if the collection hasn't changed.

Parameters

:subscriber_type -> The subscriber resource name - not in plural (e.g.: user)
:subscriber_id   -> The id of the subscriber

Index

Retrieves all non dismissed notifications for a given subscriber.

Request
GET /correspondent/:subscriber_type/:subscriber_id/notifications

Response
[
    {
        "id":20,
        "title":"Purchase #1 for user user",
        "content":"Congratulations on your recent purchase of purchase",
        "image_url":"",
        "dismissed":false,
        "publisher_type":"Purchase",
        "publisher_id":1,
        "created_at":"2019-03-01T14:19:31.273Z",
        "link_url":"/purchases/1",
        "referrer_url":"/stores/1"
    }
]

Preview

Returns total number of non dismissed notifications and the newest notification.

Request
GET /correspondent/:subscriber_type/:subscriber_id/notifications/preview

Response
{
    "count": 3,
    "notification": {
        "id":20,
        "title":"Purchase #1 for user user",
        "content":"Congratulations on your recent purchase of purchase",
        "image_url":"",
        "dismissed":false,
        "publisher_type":"Purchase",
        "publisher_id":1,
        "created_at":"2019-03-01T14:22:31.649Z",
        "link_url":"/purchases/1",
        "referrer_url":"/stores/1"
    }
}


Dismiss

Dismisses a given notification.

Resquest
PUT /correspondent/:subscriber_type/:subscriber_id/notifications/:notification_id/dismiss

Response
STATUS no_content (204)

Destroy

Destroys a given notification.

Resquest
DELETE /correspondent/:subscriber_type/:subscriber_id/notifications/:notification_id

Response
STATUS no_content (204)

Contributing

Contributions are very welcome! Don't hesitate to ask if you wish to contribute, but don't yet know how. Please refer to this simple guideline.