Skip to content

Commit 66ad3ad

Browse files
committed
Added intervals.
1 parent afff784 commit 66ad3ad

File tree

8 files changed

+196
-45
lines changed

8 files changed

+196
-45
lines changed

.rubocop.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Metrics:
22
Enabled: false
33

4-
Metrics/LineLength:
4+
Layout/LineLength:
55
Max: 500
66
Enabled: false
77

.rubocop_todo.yml

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# This configuration was generated by
22
# `rubocop --auto-gen-config`
3-
# on 2019-07-29 10:07:11 -0400 using RuboCop version 0.73.0.
3+
# on 2020-04-20 08:35:29 -0400 using RuboCop version 0.81.0.
44
# The point is for the user to remove these configuration records
55
# one by one as the offenses are removed from the code base.
66
# Note that changes in the inspected code, or installation of new
@@ -10,12 +10,23 @@
1010
# Cop supports --auto-correct.
1111
# Configuration parameters: EnforcedStyle.
1212
# SupportedStyles: squiggly, active_support, powerpack, unindent
13-
Layout/IndentHeredoc:
13+
Layout/HeredocIndentation:
1414
Exclude:
1515
- 'lib/slack-ruby-bot-server/info.rb'
1616
- 'sample_apps/sample_app_activerecord/commands/help.rb'
1717
- 'sample_apps/sample_app_mongoid/commands/help.rb'
1818

19+
# Offense count: 1
20+
Lint/AmbiguousOperator:
21+
Exclude:
22+
- 'spec/slack-ruby-bot-server/service_spec.rb'
23+
24+
# Offense count: 2
25+
# Cop supports --auto-correct.
26+
Lint/NonDeterministicRequireOrder:
27+
Exclude:
28+
- 'spec/spec_helper.rb'
29+
1930
# Offense count: 2
2031
# Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts, AllowedAcronyms.
2132
# AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS
@@ -25,18 +36,34 @@ Naming/FileName:
2536
- 'lib/slack-ruby-bot-server/ext/slack-ruby-bot.rb'
2637

2738
# Offense count: 3
28-
# Configuration parameters: Blacklist.
29-
# Blacklist: (?-mix:(^|\s)(EO[A-Z]{1}|END)(\s|$))
39+
# Configuration parameters: ForbiddenDelimiters.
40+
# ForbiddenDelimiters: (?-mix:(^|\s)(EO[A-Z]{1}|END)(\s|$))
3041
Naming/HeredocDelimiterNaming:
3142
Exclude:
3243
- 'lib/slack-ruby-bot-server/info.rb'
3344
- 'sample_apps/sample_app_activerecord/commands/help.rb'
3445
- 'sample_apps/sample_app_mongoid/commands/help.rb'
3546

47+
# Offense count: 1
48+
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
49+
# AllowedNames: io, id, to, by, on, in, at, ip, db, os, pp
50+
Naming/MethodParameterName:
51+
Exclude:
52+
- 'lib/slack-ruby-bot-server/service.rb'
53+
3654
# Offense count: 1
3755
# Cop supports --auto-correct.
3856
# Configuration parameters: EnforcedStyle, Autocorrect.
39-
# SupportedStyles: module_function, extend_self
57+
# SupportedStyles: module_function, extend_self, forbidden
4058
Style/ModuleFunction:
4159
Exclude:
4260
- 'lib/slack-ruby-bot-server/config.rb'
61+
62+
# Offense count: 1
63+
# Cop supports --auto-correct.
64+
# Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods.
65+
# SupportedStyles: predicate, comparison
66+
Style/NumericPredicate:
67+
Exclude:
68+
- 'spec/**/*'
69+
- 'lib/slack-ruby-bot-server/service.rb'

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
### Changelog
22

3-
#### 0.11.2 (Next)
3+
#### 0.12.0 (Next)
44

55
* Your contribution here.
6+
* [#113](https://github.com/slack-ruby/slack-ruby-bot-server/pull/113): Added support for intervals with `.every` - [@dblock](https://github.com/dblock).
7+
* [#112](https://github.com/slack-ruby/slack-ruby-bot-server/pull/112): Added support for multiple `.on` events as an argument - [@dblock](https://github.com/dblock).
68
* [#111](https://github.com/slack-ruby/slack-ruby-bot-server/pull/111): Removed dependency on Virtus - [@dblock](https://github.com/dblock).
79
* [#110](https://github.com/slack-ruby/slack-ruby-bot-server/pull/110): Fix ActiveRecord sample app - [@CeeBeeUK](https://github.com/CeeBeeUK).
8-
* [#112](https://github.com/slack-ruby/slack-ruby-bot-server/pull/112): Added support for multiple `.on` events as an argument - [@dblock](https://github.com/dblock).
910

1011
#### 0.11.1 (2019/5/17)
1112

Gemfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ group :development, :test do
3131
gem 'rack-test'
3232
gem 'rake'
3333
gem 'rspec'
34-
gem 'rubocop', '0.73.0'
34+
gem 'rubocop', '0.81.0'
3535
gem 'selenium-webdriver', '~> 3.4.4'
3636
gem 'vcr'
3737
gem 'webmock'

README.md

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ MyApp.instance.prepare!
133133

134134
#### Service Manager
135135

136+
##### Lifecycle Callbacks
137+
136138
You can introduce custom behavior into the service lifecycle via callbacks. This can be useful when new team has been registered via the API or a team has been deactivated from Slack.
137139

138140
```ruby
@@ -171,7 +173,6 @@ The following callbacks are supported. All callbacks receive a `team`, except `e
171173
| deactivating | a team is being deactivated |
172174
| deactivated | a team has been deactivated |
173175

174-
175176
The [Add to Slack button](https://api.slack.com/docs/slack-button) also allows for an optional `state` parameter that will be returned on completion of the request. The `creating` and `created` callbacks include an options hash where this value can be accessed (to check for forgery attacks for instance).
176177
```ruby
177178
auth = OpenSSL::HMAC.hexdigest("SHA256", "key", "data")
@@ -186,9 +187,43 @@ instance.on :creating do |team, error, options|
186187
end
187188
```
188189

189-
A number of extensions use service manager callbacks to implement useful functionality.
190+
##### Service Timers
191+
192+
You can introduce custom behavior into the service lifecycle on a timer. For example, check whether a team's trial has expired, or periodically cleanup data.
193+
194+
Note that unlike callbacks, timers are global for the entire service.
195+
196+
```ruby
197+
instance = SlackRubyBotServer::Service.instance
198+
199+
instance.every :hour do
200+
Team.each do |team|
201+
begin
202+
# do something with every team once an hour
203+
rescue StandardError
204+
end
205+
end
206+
end
207+
208+
instance.every :minute do
209+
# called every minute
210+
end
211+
212+
instance.every :second do
213+
# called every second
214+
end
215+
216+
instance.every 30 do
217+
# called every 30 seconds
218+
end
219+
```
220+
221+
##### Extensions
222+
223+
A number of extensions use service manager callbacks and service timers to implement useful functionality.
190224

191225
* [slack-ruby-bot-server-mailchimp](https://github.com/slack-ruby/slack-ruby-bot-server-mailchimp): Subscribes new bot users to a Mailchimp mailing list.
226+
* [slack-ruby-bot-server-stripe](https://github.com/slack-ruby/slack-ruby-bot-server-stripe): Enables paid bots with trial periods and commerce through Stripe.
192227

193228
#### Server Class
194229

lib/slack-ruby-bot-server/service.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ def self.instance
1515

1616
def initialize
1717
@callbacks = Hash.new { |h, k| h[k] = [] }
18+
@intervals = Hash.new { |h, k| h[k] = [] }
1819
end
1920

2021
def on(*types, &block)
@@ -23,6 +24,22 @@ def on(*types, &block)
2324
end
2425
end
2526

27+
def every(*intervals, &block)
28+
Array(intervals).each do |interval|
29+
case interval
30+
when :minute
31+
interval = 60
32+
when :hour
33+
interval = 60 * 60
34+
when :day
35+
interval = 60 * 60 * 24
36+
end
37+
raise "Invalid interval \"#{interval}\"." unless interval.is_a?(Integer) && interval > 0
38+
39+
@intervals[interval] << block
40+
end
41+
end
42+
2643
def create!(team, options = {})
2744
run_callbacks :creating, team, nil, options
2845
start!(team)
@@ -66,6 +83,15 @@ def start_from_database!
6683
start!(team)
6784
run_callbacks :booted, team
6885
end
86+
start_intervals!
87+
end
88+
89+
def start_intervals!
90+
@intervals.each_pair do |period, calls|
91+
_every period do
92+
calls.each(&:call)
93+
end
94+
end
6995
end
7096

7197
def deactivate!(team)
@@ -88,6 +114,19 @@ def self.reset!
88114

89115
private
90116

117+
def _every(tt, &_block)
118+
::Async::Reactor.run do |task|
119+
loop do
120+
begin
121+
task.sleep tt
122+
yield
123+
rescue StandardError => e
124+
logger.error e
125+
end
126+
end
127+
end
128+
end
129+
91130
def start_server!(team, server, wait = 1)
92131
team.server = server
93132
server.start_async
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module SlackRubyBotServer
2-
VERSION = '0.11.2'.freeze
2+
VERSION = '0.12.0'.freeze
33
end

spec/slack-ruby-bot-server/service_spec.rb

Lines changed: 82 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,10 @@ def initialize(options = {})
6060
end
6161
end
6262
context 'callbacks' do
63-
before do
64-
@events = []
65-
SlackRubyBotServer::Service.instance.tap do |instance|
63+
let(:instance) { SlackRubyBotServer::Service.instance }
64+
context 'single' do
65+
before do
66+
@events = []
6667
[:starting].each do |event|
6768
instance.on event do |team, _e|
6869
expect(team).to_not be_nil
@@ -78,49 +79,97 @@ def initialize(options = {})
7879
end
7980
end
8081
end
82+
it 'invokes start and stop callbacks' do
83+
allow_any_instance_of(SlackRubyBotServer::Server).to receive(:start_async)
84+
instance.start!(team)
85+
allow_any_instance_of(SlackRubyBotServer::Server).to receive(:stop!)
86+
instance.stop!(team)
87+
expect(@events).to eq %w[starting started stopping stopped]
88+
end
8189
end
82-
it 'invokes start and stop callbacks' do
83-
allow_any_instance_of(SlackRubyBotServer::Server).to receive(:start_async)
84-
SlackRubyBotServer::Service.instance.start!(team)
85-
allow_any_instance_of(SlackRubyBotServer::Server).to receive(:stop!)
86-
SlackRubyBotServer::Service.instance.stop!(team)
87-
expect(@events).to eq %w[starting started stopping stopped]
88-
end
89-
end
90-
context 'multiple callbacks' do
91-
before do
92-
@events = []
93-
SlackRubyBotServer::Service.instance.tap do |instance|
90+
context 'multiple callbacks' do
91+
before do
92+
@events = []
9493
instance.on :starting, :stopping do |team|
9594
expect(team).to_not be_nil
9695
@events << 'call'
9796
end
9897
end
98+
it 'invokes starting and stopping callbacks' do
99+
allow_any_instance_of(SlackRubyBotServer::Server).to receive(:start_async)
100+
instance.start!(team)
101+
allow_any_instance_of(SlackRubyBotServer::Server).to receive(:stop!)
102+
instance.stop!(team)
103+
expect(@events).to eq %w[call call]
104+
end
99105
end
100-
it 'invokes starting and stopping callbacks' do
101-
allow_any_instance_of(SlackRubyBotServer::Server).to receive(:start_async)
102-
SlackRubyBotServer::Service.instance.start!(team)
103-
allow_any_instance_of(SlackRubyBotServer::Server).to receive(:stop!)
104-
SlackRubyBotServer::Service.instance.stop!(team)
105-
expect(@events).to eq %w[call call]
106-
end
107-
end
108-
context 'multiple callback blocks' do
109-
before do
110-
@events = []
111-
SlackRubyBotServer::Service.instance.tap do |instance|
106+
context 'multiple callback blocks' do
107+
before do
108+
@events = []
112109
instance.on :starting, :starting do |team|
113110
expect(team).to_not be_nil
114111
@events << 'starting'
115112
end
116113
end
114+
it 'invokes starting and stopping callbacks' do
115+
allow_any_instance_of(SlackRubyBotServer::Server).to receive(:start_async)
116+
instance.start!(team)
117+
allow_any_instance_of(SlackRubyBotServer::Server).to receive(:stop!)
118+
instance.stop!(team)
119+
expect(@events).to eq %w[starting starting]
120+
end
117121
end
118-
it 'invokes starting and stopping callbacks' do
119-
allow_any_instance_of(SlackRubyBotServer::Server).to receive(:start_async)
120-
SlackRubyBotServer::Service.instance.start!(team)
121-
allow_any_instance_of(SlackRubyBotServer::Server).to receive(:stop!)
122-
SlackRubyBotServer::Service.instance.stop!(team)
123-
expect(@events).to eq %w[starting starting]
122+
end
123+
context 'timers' do
124+
let(:instance) { SlackRubyBotServer::Service.instance }
125+
context 'without timers' do
126+
it 'noop' do
127+
Async::Reactor.run do |task|
128+
instance.start_intervals!
129+
task.stop
130+
end
131+
expect(instance.instance_variable_get(:@intervals).keys).to eq []
132+
end
133+
end
134+
context 'invalid interval' do
135+
it 'string' do
136+
expect { instance.every 'invalid' }.to raise_error 'Invalid interval "invalid".'
137+
end
138+
it 'symbol' do
139+
expect { instance.every :invalid }.to raise_error 'Invalid interval "invalid".'
140+
end
141+
it 'zero' do
142+
expect { instance.every 0 }.to raise_error 'Invalid interval "0".'
143+
end
144+
it 'negative' do
145+
expect { instance.every -1 }.to raise_error 'Invalid interval "-1".'
146+
end
147+
end
148+
context 'with timers' do
149+
before do
150+
@events = []
151+
instance.every :hour, :day do
152+
@events << '1h, 1d'
153+
end
154+
instance.every 1, 2 do
155+
@events << '1-2s'
156+
end
157+
instance.every :minute do
158+
@events << '1m'
159+
end
160+
instance.every 1 do
161+
@events << '1s'
162+
end
163+
end
164+
it 'sets up timers' do
165+
Async::Reactor.run do |task|
166+
instance.start_intervals!
167+
task.sleep 3
168+
task.stop
169+
end
170+
expect(instance.instance_variable_get(:@intervals).keys).to eq [3600, 86_400, 1, 2, 60]
171+
expect(@events.sort.uniq).to eq %w[1-2s 1s]
172+
end
124173
end
125174
end
126175
context 'overriding service_class' do

0 commit comments

Comments
 (0)