active_delivery 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.rspec +0 -1
- data/.rubocop.yml +25 -1
- data/.travis.yml +26 -0
- data/Gemfile +9 -1
- data/README.md +95 -0
- data/Rakefile +3 -1
- data/active_delivery.gemspec +3 -8
- data/gemfiles/rails42.gemfile +6 -0
- data/gemfiles/railsmaster.gemfile +6 -0
- data/lib/active_delivery/base.rb +108 -0
- data/lib/active_delivery/callbacks.rb +82 -0
- data/lib/active_delivery/lines/base.rb +55 -0
- data/lib/active_delivery/lines/mailer.rb +25 -0
- data/lib/active_delivery/testing/rspec.rb +154 -0
- data/lib/active_delivery/testing.rb +46 -0
- data/lib/active_delivery/version.rb +1 -1
- data/lib/active_delivery.rb +6 -3
- metadata +25 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 49196fc041ea378e47c1a43a527d555b9822398570553d094b5d4ac051fe215b
|
4
|
+
data.tar.gz: ad331e02883c5e9bd8c7b07f9acdd531d7aa64cbec0a589dc24ac5ed2f30ad74
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2c9f5f9cd717c0bba343c2f2ec2f82390c680a6c5df1561e70ef4e6e913f6dd7204456f0238e0a2c60df25274bf09bd55b2081a5441e793ff9e39c22f40b2fc5
|
7
|
+
data.tar.gz: 0c423ef6827bb61f279f18094a9f4e57033086bc8aaac067e8a0c11a71f81e8005d7084b15d263f3d25908bccabdcee302dcec8f427927bc8c3bb27169788a72
|
data/.gitignore
CHANGED
data/.rspec
CHANGED
data/.rubocop.yml
CHANGED
@@ -1,2 +1,26 @@
|
|
1
|
+
require:
|
2
|
+
- standard/cop/semantic_blocks
|
3
|
+
|
1
4
|
inherit_gem:
|
2
|
-
|
5
|
+
standard: config/base.yml
|
6
|
+
|
7
|
+
AllCops:
|
8
|
+
Exclude:
|
9
|
+
- 'bin/*'
|
10
|
+
- 'tmp/**/*'
|
11
|
+
- 'Gemfile'
|
12
|
+
- 'node_modules/**/*'
|
13
|
+
- 'vendor/**/*'
|
14
|
+
DisplayCopNames: true
|
15
|
+
|
16
|
+
Standard/SemanticBlocks:
|
17
|
+
Enabled: false
|
18
|
+
|
19
|
+
Style/TrailingCommaInArrayLiteral:
|
20
|
+
EnforcedStyleForMultiline: no_comma
|
21
|
+
|
22
|
+
Style/TrailingCommaInHashLiteral:
|
23
|
+
EnforcedStyleForMultiline: no_comma
|
24
|
+
|
25
|
+
Layout/AlignParameters:
|
26
|
+
EnforcedStyle: with_first_parameter
|
data/.travis.yml
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
sudo: false
|
2
|
+
language: ruby
|
3
|
+
rvm:
|
4
|
+
- 2.5.1
|
5
|
+
|
6
|
+
notifications:
|
7
|
+
email: false
|
8
|
+
|
9
|
+
matrix:
|
10
|
+
fast_finish: true
|
11
|
+
include:
|
12
|
+
- rvm: ruby-head
|
13
|
+
gemfile: gemfiles/railsmaster.gemfile
|
14
|
+
- rvm: 2.5.1
|
15
|
+
gemfile: Gemfile
|
16
|
+
- rvm: 2.5.1
|
17
|
+
gemfile: Gemfile
|
18
|
+
env:
|
19
|
+
- NO_RAILS=1
|
20
|
+
- rvm: 2.4.3
|
21
|
+
gemfile: Gemfile
|
22
|
+
- rvm: 2.3.1
|
23
|
+
gemfile: gemfiles/rails42.gemfile
|
24
|
+
allow_failures:
|
25
|
+
- rvm: ruby-head
|
26
|
+
gemfile: gemfiles/railsmaster.gemfile
|
data/Gemfile
CHANGED
@@ -2,4 +2,12 @@ source "https://rubygems.org"
|
|
2
2
|
|
3
3
|
gemspec
|
4
4
|
|
5
|
-
gem "
|
5
|
+
gem "pry-byebug"
|
6
|
+
|
7
|
+
local_gemfile = File.join(__dir__, "Gemfile.local")
|
8
|
+
|
9
|
+
if File.exist?(local_gemfile)
|
10
|
+
eval(File.read(local_gemfile)) # rubocop:disable Security/Eval
|
11
|
+
else
|
12
|
+
gem "rails", "~> 5.2"
|
13
|
+
end
|
data/README.md
CHANGED
@@ -1,7 +1,49 @@
|
|
1
|
+
[![Gem Version](https://badge.fury.io/rb/active_delivery.svg)](https://badge.fury.io/rb/active_delivery)
|
2
|
+
[![Build Status](https://travis-ci.org/palkan/active_delivery.svg?branch=master)](https://travis-ci.org/palkan/active_delivery)
|
3
|
+
|
1
4
|
# Active Delivery
|
2
5
|
|
3
6
|
Framework providing an entrypoint (single _interface_) for all types of notifications: mailers, push notifications, whatever you want.
|
4
7
|
|
8
|
+
<a href="https://evilmartians.com/?utm_source=action_policy">
|
9
|
+
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
|
10
|
+
|
11
|
+
Requirements:
|
12
|
+
- Ruby ~> 2.3
|
13
|
+
|
14
|
+
**NOTE**: although most of the examples in this readme are Rails-specific, this gem could be used without Rails/ActiveSupport.
|
15
|
+
|
16
|
+
## The problem
|
17
|
+
|
18
|
+
We need a way to handle different notifications _channel_ (mail, push) in one place.
|
19
|
+
|
20
|
+
From the business-logic point of view we want to _notify_ a user, hence we need a _separate abstraction layer_ as an entrypoint to different types of notifications.
|
21
|
+
|
22
|
+
## The solution
|
23
|
+
|
24
|
+
Here comes the _Active Delivery_.
|
25
|
+
|
26
|
+
In the simplest case when we have only mailers Active Delivery is just a wrapper for Mailer with (possibly) some additional logic provided (e.g. preventing emails to unsubscribed users).
|
27
|
+
|
28
|
+
Motivations behind Active Delivery:
|
29
|
+
- organize notifications related logic:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
# Before
|
33
|
+
def after_some_action
|
34
|
+
MyMailer.with(user: user).some_action.deliver_later if user.receive_emails?
|
35
|
+
NotifyService.send_notification(user, "action") if whatever_else?
|
36
|
+
end
|
37
|
+
|
38
|
+
# After
|
39
|
+
def after_some_action
|
40
|
+
MyDelivery.with(user: user).notify(:some_action)
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
- better testability (see [Testing](#testing)).
|
45
|
+
|
46
|
+
|
5
47
|
## Usage
|
6
48
|
|
7
49
|
_Delivery_ class is used to trigger notifications. It describes how to notify a user (e.g. via email or via push notification or both):
|
@@ -43,6 +85,59 @@ PostsMailer.with(user: user).published(post)
|
|
43
85
|
|
44
86
|
See [Rails docs](https://api.rubyonrails.org/classes/ActionMailer/Parameterized.html) for more information on parameterized mailers.
|
45
87
|
|
88
|
+
## Callbacks support
|
89
|
+
|
90
|
+
**NOTE:** callbacks are only available if ActiveSupport is present in the app's env.
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
# Run method before delivering notification
|
94
|
+
# NOTE: when `false` is returned the executation is halted
|
95
|
+
before_notify :do_something
|
96
|
+
|
97
|
+
# You can specify a notification method (to run callback only for that method)
|
98
|
+
before_notify :do_mail_something, on: :mailer
|
99
|
+
|
100
|
+
# after_ and around_ callbacks are also supported
|
101
|
+
after_notify :cleanup
|
102
|
+
|
103
|
+
around_notify :set_context
|
104
|
+
```
|
105
|
+
|
106
|
+
## Testing
|
107
|
+
|
108
|
+
**NOTE:** RSpec only for the time being.
|
109
|
+
|
110
|
+
Active Delivery provides an elegant way to test deliveries in your code (i.e. when you want to test whether a notification has been sent) through a `have_delivered_to` matcher:
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
it "delivers notification" do
|
114
|
+
expect { subject }.to have_delivered_to(Community::EventsDelivery, :modified, event)
|
115
|
+
.with(profile: profile)
|
116
|
+
```
|
117
|
+
|
118
|
+
You can also use such RSpec features as [compound expectations](https://relishapp.com/rspec/rspec-expectations/docs/compound-expectations) and [composed matchers](https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/composing-matchers):
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
it "delivers to rsvped members via .notify" do
|
122
|
+
expect { subject }.
|
123
|
+
to have_delivered_to(Community::EventsDelivery, :canceled, an_instance_of(event)).with(
|
124
|
+
a_hash_including(profile: another_profile)
|
125
|
+
).and have_delivered_to(Community::EventsDelivery, :canceled, event).with(
|
126
|
+
profile: profile
|
127
|
+
)
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
If you want to test that no notification is deliver you can use negation:
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
specify "when event is not found" do
|
135
|
+
expect do
|
136
|
+
described_class.perform_now(profile.id, "123", "one_hour_before")
|
137
|
+
end.not_to have_delivered_to(Community::EventsDelivery)
|
138
|
+
end
|
139
|
+
```
|
140
|
+
|
46
141
|
## Contributing
|
47
142
|
|
48
143
|
Bug reports and pull requests are welcome on GitHub at https://github.com/palkan/active_delivery.
|
data/Rakefile
CHANGED
data/active_delivery.gemspec
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
|
2
3
|
lib = File.expand_path("../lib", __FILE__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
@@ -14,17 +15,11 @@ Gem::Specification.new do |spec|
|
|
14
15
|
spec.homepage = "https://github.com/palkan/active_delivery"
|
15
16
|
spec.license = "MIT"
|
16
17
|
|
17
|
-
|
18
|
-
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
19
|
-
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
20
|
-
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
21
|
-
end
|
22
|
-
|
23
|
-
spec.bindir = "exe"
|
24
|
-
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
18
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
25
19
|
spec.require_paths = ["lib"]
|
26
20
|
|
27
21
|
spec.add_development_dependency "bundler", "~> 1.16"
|
28
22
|
spec.add_development_dependency "rake", "~> 10.0"
|
29
23
|
spec.add_development_dependency "rspec", "~> 3.0"
|
24
|
+
spec.add_development_dependency "standard", "~> 0.0.12"
|
30
25
|
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveDelivery
|
4
|
+
# Base class for deliveries.
|
5
|
+
#
|
6
|
+
# Delivery object describes how to notify a user about
|
7
|
+
# an event (e.g. via email or via push notification or both).
|
8
|
+
#
|
9
|
+
# Delivery class acts like a proxy in front of the different delivery channels
|
10
|
+
# (i.e. mailers, notifiers). That means that calling a method on delivery class invokes the
|
11
|
+
# same method on the corresponding class, e.g.:
|
12
|
+
#
|
13
|
+
# EventsDelivery.notify(:one_hour_before, profile, event)
|
14
|
+
#
|
15
|
+
# # under the hood it calls
|
16
|
+
# EventsMailer.one_hour_before(profile, event).deliver_later
|
17
|
+
#
|
18
|
+
# # and
|
19
|
+
# EventsNotifier.one_hour_before(profile, event).notify_later
|
20
|
+
#
|
21
|
+
# Delivery also supports _parameterized_ calling:
|
22
|
+
#
|
23
|
+
# EventsDelivery.with(profile: profile).notify(:canceled, event)
|
24
|
+
#
|
25
|
+
# The parameters could be accessed through `params` instance method (e.g.
|
26
|
+
# to implement guard-like logic).
|
27
|
+
#
|
28
|
+
# When params are presents the parametrized mailer is used, i.e.:
|
29
|
+
#
|
30
|
+
# EventsMailer.with(profile: profile).canceled(event)
|
31
|
+
#
|
32
|
+
# See https://api.rubyonrails.org/classes/ActionMailer/Parameterized.html
|
33
|
+
class Base
|
34
|
+
class << self
|
35
|
+
alias with new
|
36
|
+
|
37
|
+
# Enqueues delivery (i.e. uses #deliver_later for mailers)
|
38
|
+
def notify(*args)
|
39
|
+
new.notify(*args)
|
40
|
+
end
|
41
|
+
|
42
|
+
# The same as .notify but delivers synchronously
|
43
|
+
# (i.e. #deliver_now for mailers)
|
44
|
+
def notify!(mid, *args, **hargs)
|
45
|
+
notify(mid, *args, **hargs, sync: true)
|
46
|
+
end
|
47
|
+
|
48
|
+
def delivery_lines
|
49
|
+
@lines ||= begin
|
50
|
+
if superclass.respond_to?(:delivery_lines)
|
51
|
+
superclass.delivery_lines.each_with_object({}) do |(key, val), acc|
|
52
|
+
acc[key] = val.dup_for(self)
|
53
|
+
end
|
54
|
+
else
|
55
|
+
{}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def register_line(line_id, line_class, **options)
|
61
|
+
delivery_lines[line_id] = line_class.new(id: line_id, owner: self, **options)
|
62
|
+
|
63
|
+
instance_eval <<~CODE, __FILE__, __LINE__ + 1
|
64
|
+
def #{line_id}(val)
|
65
|
+
delivery_lines[:#{line_id}].handler_class = val
|
66
|
+
end
|
67
|
+
|
68
|
+
def #{line_id}_class
|
69
|
+
delivery_lines[:#{line_id}].handler_class
|
70
|
+
end
|
71
|
+
CODE
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
attr_reader :params
|
76
|
+
|
77
|
+
def initialize(**params)
|
78
|
+
@params = params
|
79
|
+
@params.freeze
|
80
|
+
end
|
81
|
+
|
82
|
+
# Enqueues delivery (i.e. uses #deliver_later for mailers)
|
83
|
+
def notify(mid, *args, sync: false)
|
84
|
+
delivery_lines.each do |type, line|
|
85
|
+
next if line.handler_class.nil?
|
86
|
+
next unless line.notify?(mid)
|
87
|
+
|
88
|
+
notify_line(type, mid, *args, params: params, sync: sync)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# The same as .notify but delivers synchronously
|
93
|
+
# (i.e. #deliver_now for mailers)
|
94
|
+
def notify!(mid, *args, **hargs)
|
95
|
+
notify(mid, *args, **hargs, sync: true)
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def notify_line(type, mid, *args)
|
101
|
+
delivery_lines[type].notify(mid, *args)
|
102
|
+
end
|
103
|
+
|
104
|
+
def delivery_lines
|
105
|
+
self.class.delivery_lines
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/version"
|
4
|
+
require "active_support/callbacks"
|
5
|
+
require "active_support/concern"
|
6
|
+
|
7
|
+
module ActiveDelivery
|
8
|
+
# Add callbacks support to Active Delivery (requires ActiveSupport::Callbacks)
|
9
|
+
#
|
10
|
+
# # Run method before delivering notification
|
11
|
+
# # NOTE: when `false` is returned the executation is halted
|
12
|
+
# before_notify :do_something
|
13
|
+
#
|
14
|
+
# # You can specify a notification method (to run callback only for that method)
|
15
|
+
# before_notify :do_mail_something, on: :mail
|
16
|
+
#
|
17
|
+
# # or for push notifications
|
18
|
+
# before_notify :do_mail_something, on: :push
|
19
|
+
#
|
20
|
+
# # after_ and around_ callbacks are also supported
|
21
|
+
# after_notify :cleanup
|
22
|
+
#
|
23
|
+
# around_notify :set_context
|
24
|
+
module Callbacks
|
25
|
+
extend ActiveSupport::Concern
|
26
|
+
|
27
|
+
include ActiveSupport::Callbacks
|
28
|
+
|
29
|
+
CALLBACK_TERMINATOR = if ::ActiveSupport::VERSION::MAJOR >= 5
|
30
|
+
->(_target, result) { result.call == false }
|
31
|
+
else
|
32
|
+
->(_target, result) { result == false }
|
33
|
+
end
|
34
|
+
|
35
|
+
included do
|
36
|
+
# Define "global" callbacks
|
37
|
+
define_line_callbacks :notify
|
38
|
+
|
39
|
+
prepend InstanceExt
|
40
|
+
singleton_class.prepend SingltonExt
|
41
|
+
end
|
42
|
+
|
43
|
+
module InstanceExt
|
44
|
+
def notify(*)
|
45
|
+
run_callbacks(:notify) { super }
|
46
|
+
end
|
47
|
+
|
48
|
+
def notify_line(type, *)
|
49
|
+
run_callbacks(type) { super }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
module SingltonExt
|
54
|
+
def register_line(line_id, *args)
|
55
|
+
super
|
56
|
+
define_line_callbacks line_id
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class_methods do
|
61
|
+
def define_line_callbacks(name)
|
62
|
+
define_callbacks name,
|
63
|
+
terminator: CALLBACK_TERMINATOR,
|
64
|
+
skip_after_callbacks_if_terminated: true
|
65
|
+
end
|
66
|
+
|
67
|
+
def before_notify(method_name, on: :notify)
|
68
|
+
set_callback on, :before, method_name
|
69
|
+
end
|
70
|
+
|
71
|
+
def after_notify(method_name, on: :notify)
|
72
|
+
set_callback on, :after, method_name
|
73
|
+
end
|
74
|
+
|
75
|
+
def around_notify(method_name, on: :notify)
|
76
|
+
set_callback on, :around, method_name
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
ActiveDelivery::Base.include ActiveDelivery::Callbacks
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module ActiveDelivery
|
2
|
+
module Lines
|
3
|
+
class Base
|
4
|
+
attr_reader :id, :options
|
5
|
+
attr_accessor :owner
|
6
|
+
attr_writer :handler_class
|
7
|
+
|
8
|
+
def initialize(id:, owner:, **options)
|
9
|
+
@id = id
|
10
|
+
@owner = owner
|
11
|
+
@options = options.tap(&:freeze)
|
12
|
+
end
|
13
|
+
|
14
|
+
def dup_for(new_owner)
|
15
|
+
self.class.new(id: id, **options, owner: new_owner)
|
16
|
+
end
|
17
|
+
|
18
|
+
def resolve_class(name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def notify?(method_name)
|
22
|
+
handler_class.respond_to?(method_name)
|
23
|
+
end
|
24
|
+
|
25
|
+
def notify_now(handler, mid, *args)
|
26
|
+
end
|
27
|
+
|
28
|
+
def notify_later(handler, mid, *args)
|
29
|
+
end
|
30
|
+
|
31
|
+
def notify(mid, *args, params:, sync:)
|
32
|
+
clazz = params.empty? ? handler_class : handler_class.with(params)
|
33
|
+
sync ? notify_now(clazz, mid, *args) : notify_later(clazz, mid, *args)
|
34
|
+
end
|
35
|
+
|
36
|
+
def handler_class
|
37
|
+
return @handler_class if instance_variable_defined?(:@handler_class)
|
38
|
+
|
39
|
+
@handler_class = resolve_class(owner.name) ||
|
40
|
+
superclass_handler
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def superclass_handler
|
46
|
+
handler_method = "#{id}_class"
|
47
|
+
|
48
|
+
return if ActiveDelivery::Base == owner.superclass
|
49
|
+
return unless owner.superclass.respond_to?(handler_method)
|
50
|
+
|
51
|
+
owner.superclass.public_send(handler_method)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ActiveDelivery
|
2
|
+
module Lines
|
3
|
+
class Mailer < Base
|
4
|
+
alias mailer_class handler_class
|
5
|
+
|
6
|
+
def resolve_class(name)
|
7
|
+
name.gsub(/Delivery$/, "Mailer").safe_constantize
|
8
|
+
end
|
9
|
+
|
10
|
+
def notify?(method_name)
|
11
|
+
mailer_class.action_methods.include?(method_name.to_s)
|
12
|
+
end
|
13
|
+
|
14
|
+
def notify_now(mailer, mid, *args)
|
15
|
+
mailer.public_send(mid, *args).deliver_now
|
16
|
+
end
|
17
|
+
|
18
|
+
def notify_later(mailer, mid, *args)
|
19
|
+
mailer.public_send(mid, *args).deliver_later
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
ActiveDelivery::Base.register_line :mailer, Mailer
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
module ActiveDelivery
|
2
|
+
class HaveDeliveredTo < RSpec::Matchers::BuiltIn::BaseMatcher
|
3
|
+
attr_reader :delivery_class, :event, :args, :params, :sync_value
|
4
|
+
|
5
|
+
def initialize(delivery_class, event = nil, *args)
|
6
|
+
@delivery_class = delivery_class
|
7
|
+
@event = event
|
8
|
+
@args = args
|
9
|
+
set_expected_number(:exactly, 1)
|
10
|
+
end
|
11
|
+
|
12
|
+
def with(params)
|
13
|
+
@params = params
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def synchronously
|
18
|
+
@sync_value = true
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def exactly(count)
|
23
|
+
set_expected_number(:exactly, count)
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def at_least(count)
|
28
|
+
set_expected_number(:at_least, count)
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def at_most(count)
|
33
|
+
set_expected_number(:at_most, count)
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def times
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def once
|
42
|
+
exactly(:once)
|
43
|
+
end
|
44
|
+
|
45
|
+
def twice
|
46
|
+
exactly(:twice)
|
47
|
+
end
|
48
|
+
|
49
|
+
def thrice
|
50
|
+
exactly(:thrice)
|
51
|
+
end
|
52
|
+
|
53
|
+
def supports_block_expectations?
|
54
|
+
true
|
55
|
+
end
|
56
|
+
|
57
|
+
def matches?(proc)
|
58
|
+
raise ArgumentError, "have_delivered_to only supports block expectations" unless Proc === proc
|
59
|
+
|
60
|
+
TestDelivery.enable { proc.call }
|
61
|
+
|
62
|
+
actual_deliveries = TestDelivery.store
|
63
|
+
|
64
|
+
@matching_deliveries, @unmatching_deliveries =
|
65
|
+
actual_deliveries.partition do |(delivery, actual_event, actual_args, options)|
|
66
|
+
next false unless delivery_class === delivery
|
67
|
+
|
68
|
+
next false unless event.nil? || event == actual_event
|
69
|
+
next false unless params.nil? || params === delivery.params
|
70
|
+
|
71
|
+
next false unless args.each.with_index.all? do |arg, i|
|
72
|
+
arg === actual_args[i]
|
73
|
+
end
|
74
|
+
|
75
|
+
next false if !sync_value.nil? && (options.fetch(:sync, false) != sync_value)
|
76
|
+
|
77
|
+
true
|
78
|
+
end
|
79
|
+
|
80
|
+
@matching_count = @matching_deliveries.size
|
81
|
+
|
82
|
+
case @expectation_type
|
83
|
+
when :exactly then @expected_number == @matching_count
|
84
|
+
when :at_most then @expected_number >= @matching_count
|
85
|
+
when :at_least then @expected_number <= @matching_count
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def failure_message
|
90
|
+
(+"expected to deliver").tap do |msg|
|
91
|
+
msg << " :#{event} notification" if event
|
92
|
+
msg << " via #{delivery_class}#{sync_value ? " (sync)" : ""} with:"
|
93
|
+
msg << "\n - params: #{params_description(params)}" if params
|
94
|
+
msg << "\n - args: #{args.present? ? args : "<none>"}"
|
95
|
+
msg << "\n#{message_expectation_modifier}, but"
|
96
|
+
|
97
|
+
if @unmatching_deliveries.any?
|
98
|
+
msg << " delivered the following notifications:"
|
99
|
+
@unmatching_deliveries.each do |(delivery, event, args, options)|
|
100
|
+
msg << "\n :#{event} via #{delivery.class}" \
|
101
|
+
"#{options[:sync] ? " (sync)" : ""}" \
|
102
|
+
" with:" \
|
103
|
+
"\n - params: #{delivery.params.present? ? delivery.params.to_s : "<none>"}" \
|
104
|
+
"\n - args: #{args}"
|
105
|
+
end
|
106
|
+
else
|
107
|
+
msg << " haven't delivered anything"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def set_expected_number(relativity, count)
|
115
|
+
@expectation_type = relativity
|
116
|
+
@expected_number =
|
117
|
+
case count
|
118
|
+
when :once then 1
|
119
|
+
when :twice then 2
|
120
|
+
when :thrice then 3
|
121
|
+
else Integer(count)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def failure_message_when_negated
|
126
|
+
"expected not to deliver #{event ? " :#{event} notification" : ""} via #{delivery_class}"
|
127
|
+
end
|
128
|
+
|
129
|
+
def message_expectation_modifier
|
130
|
+
number_modifier = @expected_number == 1 ? "once" : "#{@expected_number} times"
|
131
|
+
case @expectation_type
|
132
|
+
when :exactly then "exactly #{number_modifier}"
|
133
|
+
when :at_most then "at most #{number_modifier}"
|
134
|
+
when :at_least then "at least #{number_modifier}"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def params_description(data)
|
139
|
+
if data.is_a?(RSpec::Matchers::Composable)
|
140
|
+
data.description
|
141
|
+
else
|
142
|
+
data
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
RSpec.configure do |config|
|
149
|
+
config.include(Module.new do
|
150
|
+
def have_delivered_to(*args)
|
151
|
+
ActiveDelivery::HaveDeliveredTo.new(*args)
|
152
|
+
end
|
153
|
+
end)
|
154
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module ActiveDelivery
|
2
|
+
module TestDelivery
|
3
|
+
class << self
|
4
|
+
def enable
|
5
|
+
raise ArgumentError, "block is reauired" unless block_given?
|
6
|
+
begin
|
7
|
+
clear
|
8
|
+
Thread.current[:active_delivery_testing] = true
|
9
|
+
yield
|
10
|
+
ensure
|
11
|
+
Thread.current[:active_delivery_testing] = false
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def enabled?
|
16
|
+
Thread.current[:active_delivery_testing] == true
|
17
|
+
end
|
18
|
+
|
19
|
+
def track(delivery, event, args, options)
|
20
|
+
store << [delivery, event, args, options]
|
21
|
+
end
|
22
|
+
|
23
|
+
def store
|
24
|
+
@store ||= []
|
25
|
+
end
|
26
|
+
|
27
|
+
def clear
|
28
|
+
store.clear
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def notify(event, *args, **options)
|
33
|
+
return super unless test?
|
34
|
+
TestDelivery.track(self, event, args, options)
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def test?
|
39
|
+
TestDelivery.enabled?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
ActiveDelivery::Base.prepend ActiveDelivery::TestDelivery
|
45
|
+
|
46
|
+
require "active_delivery/testing/rspec" if defined?(RSpec)
|
data/lib/active_delivery.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
require "active_delivery/version"
|
2
|
+
require "active_delivery/base"
|
3
|
+
require "active_delivery/callbacks" if defined?(ActiveSupport)
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
|
5
|
+
require "active_delivery/lines/base"
|
6
|
+
require "active_delivery/lines/mailer" if defined?(ActionMailer)
|
7
|
+
|
8
|
+
require "active_delivery/testing" if ENV["RACK_ENV"] == "test" || ENV["RAILS_ENV"] == "test"
|
metadata
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_delivery
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vladimir Dementyev
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
date: 2018-12-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: standard
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.0.12
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.0.12
|
55
69
|
description: Rails framework for managing all types of notifications in one place
|
56
70
|
email:
|
57
71
|
- dementiev.vm@gmail.com
|
@@ -62,6 +76,7 @@ files:
|
|
62
76
|
- ".gitignore"
|
63
77
|
- ".rspec"
|
64
78
|
- ".rubocop.yml"
|
79
|
+
- ".travis.yml"
|
65
80
|
- Gemfile
|
66
81
|
- LICENSE.txt
|
67
82
|
- README.md
|
@@ -69,7 +84,15 @@ files:
|
|
69
84
|
- active_delivery.gemspec
|
70
85
|
- bin/console
|
71
86
|
- bin/setup
|
87
|
+
- gemfiles/rails42.gemfile
|
88
|
+
- gemfiles/railsmaster.gemfile
|
72
89
|
- lib/active_delivery.rb
|
90
|
+
- lib/active_delivery/base.rb
|
91
|
+
- lib/active_delivery/callbacks.rb
|
92
|
+
- lib/active_delivery/lines/base.rb
|
93
|
+
- lib/active_delivery/lines/mailer.rb
|
94
|
+
- lib/active_delivery/testing.rb
|
95
|
+
- lib/active_delivery/testing/rspec.rb
|
73
96
|
- lib/active_delivery/version.rb
|
74
97
|
homepage: https://github.com/palkan/active_delivery
|
75
98
|
licenses:
|