auto_alert 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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +175 -0
- data/Rakefile +13 -0
- data/lib/auto_alert.rb +9 -0
- data/lib/auto_alert/acts_as_alert.rb +27 -0
- data/lib/auto_alert/acts_as_alertable.rb +50 -0
- data/lib/auto_alert/checker.rb +75 -0
- data/lib/auto_alert/railtie.rb +4 -0
- data/lib/auto_alert/version.rb +3 -0
- data/lib/tasks/auto_alert_tasks.rake +4 -0
- metadata +77 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6115b3cc9d444af3c92d33b3ad3c3152040e0912254a36959d46e732dd3e3634
|
4
|
+
data.tar.gz: 2da6fae49f957aeb5be28a00dd9921feb699ad8d63ea7b83d906e073e9ba6079
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7934f6257a750d9fa24814e3789e2c30f81c84d903d55688c8fd38a11d6c3ee11f46645cef1101fe43f29dbea8227c19538baff07987d5ae6bda92e7105fc8f7
|
7
|
+
data.tar.gz: dbbb8a284baf98324de67846bc216ec48ef9912f74e09b1ef9e5120849cb6970bdf8dbe0f1bc61272532abbec2d672022db01e22ad5eec5800d8dcdee1e5d738
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2021 Brendan Tang
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
# auto_alert
|
2
|
+
|
3
|
+
|
4
|
+
[`auto_alert`](https://github.com/brendantang/auto_alert) provides a simple DSL to declare conditions that should raise alerts on your ActiveRecord models.
|
5
|
+
Alerts are saved in a database table with a [polymorphic association](https://guides.rubyonrails.org/association_basics.html#polymorphic-associations) to the model which triggered their creation.
|
6
|
+
It's possible to define different conditions to raise, resolve, and re-raise an alert.
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
## Quick example
|
11
|
+
|
12
|
+
|
13
|
+
Imagine a to-do list application where tasks with a due date in the past should be flagged with an alert.
|
14
|
+
With `auto_alert`, you can modify your Task model like so:
|
15
|
+
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
class Task < ApplicationRecord
|
19
|
+
acts_as_alertable
|
20
|
+
|
21
|
+
raises_alert :past_due,
|
22
|
+
on: :past_due_date?,
|
23
|
+
message: "was due"
|
24
|
+
|
25
|
+
private
|
26
|
+
def past_due_date?
|
27
|
+
due_date < Date.current and !done
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
|
32
|
+
Then you can call the `scan_for_alerts!` on any task and, if `past_due_date?` returns `true`, an alert record belonging to that task will be created.
|
33
|
+
Instances of `Task` will also have a `past_due_alert` getter method to fetch their associated `past_due` alert, if any.
|
34
|
+
|
35
|
+
|
36
|
+
|
37
|
+
## Installation
|
38
|
+
|
39
|
+
|
40
|
+
### Install `auto_alert`
|
41
|
+
|
42
|
+
Add this line to your application's Gemfile:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
gem 'auto_alert'
|
46
|
+
```
|
47
|
+
|
48
|
+
And then execute:
|
49
|
+
```bash
|
50
|
+
$ bundle
|
51
|
+
```
|
52
|
+
|
53
|
+
### Create `Alert` model
|
54
|
+
|
55
|
+
`auto_alert` assumes there is an `Alert` model to store your alerts in the database.
|
56
|
+
|
57
|
+
An appropriate model can be built using the Rails generator by running something like, `rails generate model Alert alertable:references resolved:boolean kind:string message:string`.
|
58
|
+
Then edit the resulting migration so that it looks like [this example](test/dummy/db/migrate/20210613222049_create_alerts.rb).
|
59
|
+
|
60
|
+
Make sure to edit the alert model file to register it with `auto_alert`:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
# app/models/alert.rb
|
64
|
+
class Alert < ApplicationRecord
|
65
|
+
acts_as_alert # add this line
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
(In the future I'd like to add an `install` rake task to automate this process.)
|
70
|
+
|
71
|
+
`auto_alert` assumes the alert relation uses the table called `alerts`, but you can specify a different relation:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
# app/models/task.rb
|
75
|
+
class Task < ApplicationRecord
|
76
|
+
acts_as_alertable # will default to using the Alert model
|
77
|
+
end
|
78
|
+
|
79
|
+
# app/models/task_list.rb
|
80
|
+
class TaskList < ApplicationRecord
|
81
|
+
acts_as_alertable with_table: :special_alerts # will look for a SpecialAlert model
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
## Usage
|
88
|
+
|
89
|
+
|
90
|
+
Calling `acts_as_alertable` in your model definition:
|
91
|
+
- Allows you to use the `raises_alert` class method to declare the conditions that should raise or resolve an alert
|
92
|
+
- Registers that model instances have alerts
|
93
|
+
- Uses [polymorphic association](https://guides.rubyonrails.org/association_basics.html#polymorphic-associations), so you can use all the instance methods it provides
|
94
|
+
- Also creates convenience getters for each kind of alert that model can raise (i.e. `task.past_due_alert`)
|
95
|
+
- Provides the instance method `scan_for_alerts!`, which creates or updates alert records according to the declared `raises_alert` conditions.
|
96
|
+
|
97
|
+
|
98
|
+
### Scanning for alerts
|
99
|
+
|
100
|
+
`auto_alert` makes no assumptions about when you'd like to check if alerts should be raised or resolved.
|
101
|
+
|
102
|
+
For simple use cases, it can make sense to call `scan_for_alerts!` on specific instances from your controller, or to hook into one of the ActiveRecord [callbacks](https://guides.rubyonrails.org/active_record_callbacks.html):
|
103
|
+
```ruby
|
104
|
+
class MyModel < ApplicationRecord
|
105
|
+
acts_as_alertable
|
106
|
+
# ...
|
107
|
+
after_save :scan_for_alerts!
|
108
|
+
```
|
109
|
+
|
110
|
+
For more complicated use cases, especially where alert conditions rely on factors external to the model instance itself, it can make sense to use a [job](https://guides.rubyonrails.org/active_job_basics.html) to asynchronously call `scan_for_alerts!` on big batches of records.
|
111
|
+
|
112
|
+
|
113
|
+
### Alert records
|
114
|
+
|
115
|
+
`auto_alert` assumes there is an ActiveRecord model for the table `alerts`, with at least:
|
116
|
+
- A polymorphic `alertable` reference (pointing to the record which triggered the alert)
|
117
|
+
- A boolean `resolved` attribute (indicating the alert is no longer relevant)
|
118
|
+
- A `kind` attribute (each alertable record has only one alert of each kind—`past_due` in the example above)
|
119
|
+
- Can be a text column or a Rails enumerable using a numeric column ([example](./test/dummy/app/models/special_alert.rb))
|
120
|
+
- A string `message` attribute describing the alert details
|
121
|
+
|
122
|
+
Instructions for creating such a model are in the Installation secion under [Create Alert model](#create-alert-model).
|
123
|
+
|
124
|
+
|
125
|
+
### Declaring rules to raise and resolve alerts
|
126
|
+
|
127
|
+
The `on` parameter to `raises_alert` can take a method name as [above](#quick-example), or a proc:
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
raises_alert :past_due,
|
131
|
+
on: ->(t) { t.due_date < Date.current and !t.done }
|
132
|
+
```
|
133
|
+
|
134
|
+
You can also pass a `resolve_on` parameter to specify the condition for marking the alert `resolved`.
|
135
|
+
A proc or method name is acceptable.
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
class Order < ApplicationRecord
|
139
|
+
acts_as_alertable
|
140
|
+
|
141
|
+
raises_alert :week_old,
|
142
|
+
on: ->(order) { order.placed <= Date.current - 1.week },
|
143
|
+
resolve_on: :shipped
|
144
|
+
# Now changing the order date won't resolve the alert, but marking it `shipped` will.
|
145
|
+
```
|
146
|
+
|
147
|
+
If no `resolve_on` parameter is passed, the inverse of the `on` condition is used.
|
148
|
+
|
149
|
+
|
150
|
+
### Re-raising alerts
|
151
|
+
|
152
|
+
By default, an alert which is `resolved` will not be re-raised, even if the `on` condition is met again.
|
153
|
+
If you pass the `reraise` option `true`, the alert's `resolved` attribute will be updated to `false` every time the `on` condition is met.
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
class Temperature < ApplicationRecord
|
157
|
+
acts_as_alertable
|
158
|
+
|
159
|
+
raises_alert :too_cold,
|
160
|
+
on: ->(temp) { temp.farenheit < 32 },
|
161
|
+
reraise: true
|
162
|
+
```
|
163
|
+
|
164
|
+
You can also give a proc or method name to the `reraise` option to use a condition other than the initial `on` condition.
|
165
|
+
|
166
|
+
|
167
|
+
### Messages
|
168
|
+
|
169
|
+
Alert messages can be built using a string, a proc, or a method name.
|
170
|
+
When an alert is re-raised, its message will be built again.
|
171
|
+
|
172
|
+
|
173
|
+
|
174
|
+
## License
|
175
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/lib/auto_alert.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module AutoAlert
|
2
|
+
module ActsAsAlert
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
# Class method to register a model as alertable
|
7
|
+
def acts_as_alert
|
8
|
+
belongs_to :alertable, polymorphic: true
|
9
|
+
validates :kind,
|
10
|
+
uniqueness: {
|
11
|
+
scope: :alertable,
|
12
|
+
message: ->(record, data) { "This #{record.alertable_type} already has a '#{data[:value]}' alert." },
|
13
|
+
}
|
14
|
+
include AutoAlert::ActsAsAlert::LocalInstanceMethods
|
15
|
+
extend AutoAlert::ActsAsAlert::SingletonMethods
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module LocalInstanceMethods
|
20
|
+
end
|
21
|
+
|
22
|
+
module SingletonMethods
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
ActiveRecord::Base.send(:include, AutoAlert::ActsAsAlert)
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module AutoAlert
|
2
|
+
module ActsAsAlertable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
# Class method to register a model as alertable
|
7
|
+
def acts_as_alertable(with_table: :alerts)
|
8
|
+
cattr_accessor :alerts_table_name, default: with_table
|
9
|
+
alias_attribute :alerts, with_table unless with_table.to_sym == :alerts
|
10
|
+
cattr_accessor :alert_checkers, default: []
|
11
|
+
has_many with_table.to_sym, as: :alertable, dependent: :destroy
|
12
|
+
include AutoAlert::ActsAsAlertable::LocalInstanceMethods
|
13
|
+
extend AutoAlert::ActsAsAlertable::SingletonMethods
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module LocalInstanceMethods
|
18
|
+
def unresolved_alerts
|
19
|
+
alerts.where(resolved: false)
|
20
|
+
end
|
21
|
+
|
22
|
+
def has_unresolved_alerts?
|
23
|
+
unresolved_alerts.size > 0
|
24
|
+
end
|
25
|
+
|
26
|
+
# Check if any alerts should be raised or resolveed
|
27
|
+
def scan_for_alerts!
|
28
|
+
self.class.alert_checkers.each do |checker|
|
29
|
+
checker.check(self)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module SingletonMethods
|
35
|
+
def raises_alert(kind, on:, resolve_on: nil, message: nil, reraise: false)
|
36
|
+
checker = AutoAlert::Checker.new(kind, on, resolve_on, reraise, message)
|
37
|
+
alert_checkers << checker
|
38
|
+
define_method "#{kind}_alert" do
|
39
|
+
alerts.find_by(kind: kind)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def alert_kinds
|
44
|
+
alert_checkers.map do |checker| checker.kind end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
ActiveRecord::Base.send(:include, AutoAlert::ActsAsAlertable)
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module AutoAlert
|
2
|
+
class Checker
|
3
|
+
attr_reader :kind
|
4
|
+
|
5
|
+
def initialize(alert_kind, raise_condition, resolve_condition, reraise_condition = false, message_builder)
|
6
|
+
@kind = alert_kind
|
7
|
+
@raise_condition = normalize_check_condition raise_condition
|
8
|
+
@resolve_condition =
|
9
|
+
resolve_condition ?
|
10
|
+
normalize_check_condition(resolve_condition) :
|
11
|
+
->(a) { not @raise_condition.call(a) }
|
12
|
+
@reraise_condition = if !reraise_condition # If nil or false
|
13
|
+
nil
|
14
|
+
elsif reraise_condition == true
|
15
|
+
@raise_condition
|
16
|
+
else
|
17
|
+
normalize_check_condition reraise_condition
|
18
|
+
end
|
19
|
+
@message_builder = message_builder
|
20
|
+
end
|
21
|
+
|
22
|
+
def check(alertable)
|
23
|
+
existing_alert = alertable.send("#{@kind}_alert")
|
24
|
+
|
25
|
+
case [existing_alert, @reraise_condition, existing_alert&.resolved]
|
26
|
+
|
27
|
+
# Check for alert for the first time
|
28
|
+
in nil, _, _
|
29
|
+
if @raise_condition.call(alertable)
|
30
|
+
alertable.alerts.create(kind: @kind, message: message(alertable), resolved: false)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Check if alert should be resolveed
|
34
|
+
in alert, _, false
|
35
|
+
alert.update(resolved: true) if @resolve_condition.call(alertable)
|
36
|
+
|
37
|
+
# Resolved alert should not be re-raised
|
38
|
+
in alert, nil, true
|
39
|
+
return
|
40
|
+
|
41
|
+
# Check if resolved alert should be re-raised
|
42
|
+
in alert, reraise_condition, true
|
43
|
+
if reraise_condition.call(alertable)
|
44
|
+
alert.update(message: message(alertable), resolved: false)
|
45
|
+
end
|
46
|
+
|
47
|
+
else
|
48
|
+
return
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def message(alertable)
|
53
|
+
case @message_builder
|
54
|
+
in Proc => p
|
55
|
+
p.call(alertable)
|
56
|
+
in Symbol => s if alertable.respond_to?(s, true)
|
57
|
+
alertable.send(@message_builder)
|
58
|
+
else
|
59
|
+
@message_builder
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def normalize_check_condition(condition)
|
66
|
+
if condition.respond_to?(:call)
|
67
|
+
condition
|
68
|
+
else
|
69
|
+
lambda do |record|
|
70
|
+
record.send(condition)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: auto_alert
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brendan Tang
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-06-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 6.1.3
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 6.1.3.2
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 6.1.3
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 6.1.3.2
|
33
|
+
description: A plugin for Rails which makes it easy to specify conditions to raise
|
34
|
+
or resolve alert records associated with your ActiveRecord models.
|
35
|
+
email:
|
36
|
+
- b@brendantang.net
|
37
|
+
executables: []
|
38
|
+
extensions: []
|
39
|
+
extra_rdoc_files: []
|
40
|
+
files:
|
41
|
+
- MIT-LICENSE
|
42
|
+
- README.md
|
43
|
+
- Rakefile
|
44
|
+
- lib/auto_alert.rb
|
45
|
+
- lib/auto_alert/acts_as_alert.rb
|
46
|
+
- lib/auto_alert/acts_as_alertable.rb
|
47
|
+
- lib/auto_alert/checker.rb
|
48
|
+
- lib/auto_alert/railtie.rb
|
49
|
+
- lib/auto_alert/version.rb
|
50
|
+
- lib/tasks/auto_alert_tasks.rake
|
51
|
+
homepage: https://github.com/brendantang/auto_alert
|
52
|
+
licenses:
|
53
|
+
- MIT
|
54
|
+
metadata:
|
55
|
+
homepage_uri: https://github.com/brendantang/auto_alert
|
56
|
+
source_code_uri: https://github.com/brendantang/auto_alert
|
57
|
+
changelog_uri: https://github.com/brendantang/auto_alert
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements: []
|
73
|
+
rubygems_version: 3.2.16
|
74
|
+
signing_key:
|
75
|
+
specification_version: 4
|
76
|
+
summary: Automatically raise and dismiss alerts on your ActiveRecord models.
|
77
|
+
test_files: []
|