active_delivery 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](https://badge.fury.io/rb/active_delivery)
|
2
|
+
[](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:
|