Git Product home page Git Product logo

abstract_notifier's Introduction

Gem Version Build

Abstract Notifier

Important

The project has been merged into active_delivery. This repository is no longer maintained.

Abstract Notifier is a tool which allows you to describe/model any text-based notifications (such as Push Notifications) the same way Action Mailer does for email notifications.

Abstract Notifier (as the name states) doesn't provide any specific implementation for sending notifications. Instead, it offers tools to organize your notification-specific code and make it easily testable.

πŸ“– Read the introduction post: "Crafting user notifications in Rails with Active Delivery"

Sponsored by Evil Martians

Requirements:

  • Ruby ~> 2.4

NOTE: although most of the examples in this readme are Rails-specific, this gem could be used without Rails/ActiveSupport.

Installation

Add this line to your application's Gemfile:

gem "abstract_notifier"

And then execute:

$ bundle

Usage

Notifier class is very similar to Action Mailer mailer class with notification method instead of a mail method:

class EventsNotifier < ApplicationNotifier
  def canceled(profile, event)
    notification(
      # the only required option is `body`
      body: "Event #{event.title} has been canceled",
      # all other options are passed to delivery driver
      identity: profile.notification_service_id
    )
  end
end

# send notification later
EventsNotifier.canceled(profile, event).notify_later

# or immediately
EventsNotifier.canceled(profile, event).notify_now

To perform actual deliveries you must configure a delivery driver:

class ApplicationNotifier < AbstractNotifier::Base
  self.driver = MyFancySender.new
end

A driver could be any callable Ruby object (i.e., anything that responds to #call).

That's a developer responsibility to implement the driver (we do not provide any drivers out-of-the-box; at least yet).

You can set different drivers for different notifiers.

Parameterized notifiers

Abstract Notifier support parameterization the same way as Action Mailer:

class EventsNotifier < ApplicationNotifier
  def canceled(event)
    notification(
      body: "Event #{event.title} has been canceled",
      identity: params[:profile].notification_service_id
    )
  end
end

EventsNotifier.with(profile: profile).canceled(event).notify_later

Defaults

You can specify default notification fields at a class level:

class EventsNotifier < ApplicationNotifier
  # `category` field will be added to the notification
  # if missing
  default category: "EVENTS"

  # ...
end

NOTE: when subclassing notifiers, default parameters are merged.

You can also specify a block or a method name as the default params generator. This could be useful in combination with the #notification_name method to generate dynamic payloads:

class ApplicationNotifier < AbstractNotifier::Base
  default :build_defaults_from_locale

  private

  def build_defaults_from_locale
    {
      subject: I18n.t(notification_name, scope: [:notifiers, self.class.name.underscore])
    }
  end
end

Background jobs / async notifications

To use notify_later you must configure async_adapter.

We provide Active Job adapter out-of-the-box and use it if Active Job is present.

The custom async adapter must implement enqueue method:

class MyAsyncAdapter
  # adapters may accept options
  def initialize(options = {})
  end

  # `enqueue` method accepts notifier class and notification
  # payload.
  # We need to know notifier class to use its driver.
  def enqueue(notifier_class, payload)
    # your implementation here
  end
end

# Configure globally
AbstractNotifier.async_adapter = MyAsyncAdapter.new

# or per-notifier
class EventsNotifier < AbstractNotifier::Base
  self.async_adapter = MyAsyncAdapter.new
end

Delivery modes

For test/development purposes there are two special global delivery modes:

# Track all sent notifications without peforming real actions.
# Required for using RSpec matchers.
#
# config/environments/test.rb
AbstractNotifier.delivery_mode = :test

# If you don't want to trigger notifications in development,
# you can make Abstract Notifier no-op.
#
# config/environments/development.rb
AbstractNotifier.delivery_mode = :noop

# Default delivery mode is "normal"
AbstractNotifier.delivery_mode = :normal

NOTE: we set delivery_mode = :test if RAILS_ENV or RACK_ENV env variable is equal to "test". Otherwise add require "abstract_notifier/testing" to your spec_helper.rb / rails_helper.rb manually.

NOTE: delivery mode affects all drivers.

Testing

Abstract Notifier provides two convinient RSpec matchers:

# for testing sync notifications (sent with `notify_now`)
expect { EventsNotifier.with(profile: profile).canceled(event).notify_now }
  .to have_sent_notification(identify: "123", body: "Alarma!")

# for testing async notifications (sent with `notify_later`)
expect { EventsNotifier.with(profile: profile).canceled(event).notify_later }
  .to have_enqueued_notification(identify: "123", body: "Alarma!")

Abstract Notifier provides two convinient minitest assertions:

require 'abstract_notifier/testing/minitest'

class EventsNotifierTestCase < Minitest::Test
  include AbstractNotifier::TestHelper

  test 'canceled' do
    assert_notifications_sent 1, identify: "123", body: "Alarma!" do
      EventsNotifier.with(profile: profile).canceled(event).notify_now
    end

    assert_notifications_enqueued 1, identify: "123", body: "Alarma!" do
      EventsNotifier.with(profile: profile).canceled(event).notify_later
    end
  end
end

NOTE: test mode activated automatically if RAILS_ENV or RACK_ENV env variable is equal to "test". Otherwise add require "abstract_notifier/testing/rspec" to your spec_helper.rb / rails_helper.rb manually. This is also required if you're using Spring in test environment (e.g. with help of spring-commands-rspec).

Related projects

Active Delivery is the next-level abstraction which allows combining multiple notification channels in one place.

Abstract Notifier provides a notifier line for Active Delivery:

class ApplicationDelivery < ActiveDelivery::Base
  # Add notifier line to you delivery
  register_line :notifier, ActiveDelivery::Lines::Notifier,
                # you may provide a resolver, which infers notifier class
                # from delivery name (resolver is a callable).
    resolver: ->(name) { resolve_somehow(name) }
end

NOTE: we automatically add :notifier line with "*Delivery" -> *Notifier resolution mechanism if #safe_constantize method is defined for String, i.e., you don't have to configure the default notifier line when running Rails.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/palkan/abstract_notifier.

License

The gem is available as open source under the terms of the MIT License.

abstract_notifier's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

abstract_notifier's Issues

RSpec matchers doesn't work

I have the same issue with testing like described here:

You must pass an argument rather than a block to use the provided matcher (have not delivered to MyDelivery), or the matcher must implement `supports_block_expectations?

when spring is running.

So adding require "abstract_notifier/testing/rspec" makes a trick! Let's add notice to the docs:


NOTE: test mode activated automatically if RAILS_ENV or RACK_ENV env variable is equal to "test". Otherwise add require "abstract_notifier/testing/rspec" to your spec_helper.rb / rails_helper.rb manually. This is also required if you're using Spring in test environment (e.g. with help of spring-commands-rspec).


Let me know if I could send PR or you will do it on yours end.

Thanks, for the great gem and all your open source works!

Wrap around notification call similar to the around_action

Is your feature request related to a problem? Please describe.

For the sake of easiness and reduce verbosity in mailers it's possible to set locale of current user like:

  before_action do
    if params
      @user = params[:user]
    end
  end

  around_action do
    I18n.with_locale(@user.locale, &block)
  end

I found there are no around wrappers in abstract notifier code.

Describe the solution you'd like

Would be nice to have similar ability to wrap some code around notification call, similar to the actionmailer.

Describe alternatives you've considered

Currently it's only possible to wrap public_send which calls notifications like so:

  def initialize(notification_name, user:, **params)
    @user = user
    super
  end

  def public_send(*)
    I18n.with_locale(@user.locale) do
      super
    end
  end

Additional context

Lets discuss and I'm able to provide a solution.

Option to disable default `notifier` line with ActiveDelivery?

Would you be open to having an option (somewhere, not sure where) that would disable creating the default notifier line for ActiveDelivery? Looking at these lines here:

    # Only automatically register line when we can resolve the class
    # easily.
    if "".respond_to?(:safe_constantize)
      ActiveDelivery::Base.register_line :notifier, Notifier, resolver: Notifier::DEFAULT_RESOLVER
    end

I'm using name other than notifier and it would be nice to be able to disable the default so I don't have an extra line registering that I don't need. Happy to do a pull request if you have insight on how you wan options handled. Thanks for releasing these gems!

Ruby 2.7/3.0 support

Is your feature request related to a problem? Please describe.

When using this gem on projects with ruby 2.7 , there a few deprecation warnings that will break on ruby 3.0 (related to how ruby 3.0 handles keyword arguments).

Describe the solution you'd like

Release a major version that drops support for ruby <= 2.6 (because this version already hit EOL) that fixes those warnings

Describe alternatives you've considered

I've considered forking the project and fixing the deprecation warnings locally

Additional context

I am currently working on fix those warnings. I hope to open a PR soon. Let me know if there's something else I can do here.

have_enqueued_notification fails when 2 notifications with same message are enqueued

When testing a method that calls 2 different notification methods which sends exactly same message, the expect with have_enqueued_notification fails, saying that it wasn't enqueued not even once:

expected to enqueue notification: {:body=>"Here we go again"} exactly once, but haven't enqueued anything

I didn't test similar cases, like enqueue twice w/ same method, but at least the problem exists for this specific case.

An error raised in an ActiveDelivery line behaves differently than one raised in an ActiveMailer line

Am using ActiveDelivery in combination with AbstractNotifier. Thanks so much for both contributions. I have a few custom abstract notifier lines setup as well as a mailer line (default). Great results!

I have a question/issue with error handling and job queueing.

What I am observing is that when an error is raised inside the AbstractNotifier it halts the execution of all the other delivery lines during the process of the delivery (even when using #notify to enqueue the jobs). Whereas if an error is raised inside of an ActiveMailer line the error does not stop the delivery lines from being enqueued (and instead the error actually gets raised when the enqueued job runs).

Simple example:

class FooBarDelivery < ApplicationDelivery; end

class FooBarNotifier < AbstractNotifier::Base
  def foo
    raise "This will halt the operating of `FooBarDelivery.notify(:foo)`"
  end
end

class FooBarMailer < ApplicationMailer
  def foo
    raise "This will *not* halt the operation of `FooBarDelviery.notify(foo)`"
  end
end

# => FooBarDelivery.with(foo: :bar).notify(:foo)

So the raised error in the abstract notifier halts everything; the raised error in ApplicationMailer does not. This is tripping me up in particular with nested job queues because I had one abstract notifier line that would error but all the other lines were being retried resulting in a lot of "spam" (as the one line never was getting delivered).

I would have expected them all to behave like ActiveMailer β€”Β they aren't actually executed until after the job is queued.

I wonder if I have my driver setup wrong or is this just how it happens to be?

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.