noticent 0.0.1.pre.pre → 0.0.5
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/.ruby-version +1 -2
- data/Gemfile.lock +154 -74
- data/LICENSE +201 -0
- data/README.md +81 -14
- data/config.ru +9 -0
- data/lib/noticent.rb +2 -1
- data/lib/noticent/active_record_opt_in_provider.rb +10 -1
- data/lib/noticent/channel.rb +24 -31
- data/lib/noticent/config.rb +77 -18
- data/lib/noticent/definitions/alert.rb +80 -6
- data/lib/noticent/definitions/channel.rb +7 -2
- data/lib/noticent/definitions/scope.rb +10 -6
- data/lib/noticent/dispatcher.rb +16 -10
- data/lib/noticent/version.rb +1 -1
- data/lib/noticent/view.rb +16 -14
- data/noticent.gemspec +27 -21
- data/testing/channels/email.rb +1 -0
- data/testing/channels/exclusive.rb +9 -0
- data/testing/channels/simple.rb +9 -0
- data/testing/payloads/comment_payload.rb +10 -0
- data/testing/payloads/post_payload.rb +44 -0
- data/testing/views/email/some_event.html.erb +3 -0
- data/testing/views/email/some_event.txt.erb +2 -0
- data/testing/views/layouts/layout.html.erb +1 -1
- data/testing/views/simple/default.html.erb +1 -0
- metadata +85 -24
data/README.md
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
<img src="http://cdn2-cloud66-com.s3.amazonaws.com/images/oss-sponsorship.png" width=150/>
|
2
|
+
|
1
3
|
# Noticent
|
2
4
|
|
3
5
|
Noticent is a Ruby gem for user notification management. It is written to deliver a developer friendly way to managing application notifications in a typical web application. Many applications have user notification: sending emails when a task is done or support for webhooks or Slack upon certain events. Noticent makes it easy to write maintainable code for notification subscription and delivery in a typical web application.
|
@@ -13,6 +15,9 @@ The primary design goal for Noticent is developer friendliness. Using Noticent,
|
|
13
15
|
|
14
16
|
## Installation
|
15
17
|
|
18
|
+
### Notice on Rails version
|
19
|
+
Noticent 0.0.5 has been upgraded to work with Rails 6.0. If you would like to use it with an older version of Rails, please use Noticent 0.0.4
|
20
|
+
|
16
21
|
Add this line to your application's Gemfile:
|
17
22
|
|
18
23
|
```ruby
|
@@ -93,12 +98,8 @@ Noticent.configure do
|
|
93
98
|
channel :email
|
94
99
|
|
95
100
|
scope :account do
|
96
|
-
alert
|
97
|
-
|
98
|
-
end
|
99
|
-
alert :new_team_member do
|
100
|
-
notify :users
|
101
|
-
end
|
101
|
+
alert(:new_signup) { notify :owner}
|
102
|
+
alert(:new_team_member) { notify :users }
|
102
103
|
end
|
103
104
|
end
|
104
105
|
```
|
@@ -117,7 +118,7 @@ class Email < ::Noticent::Channel
|
|
117
118
|
end
|
118
119
|
```
|
119
120
|
|
120
|
-
Now that we have our channel, we can define a Payload. We can do this in `app/
|
121
|
+
Now that we have our channel, we can define a Payload. We can do this in `app/models/noticent/account_payload.rb`:
|
121
122
|
|
122
123
|
```ruby
|
123
124
|
class AccountPayload
|
@@ -167,7 +168,6 @@ In the channel, you can use this:
|
|
167
168
|
|
168
169
|
```ruby
|
169
170
|
class EmailChannel < ::Noticent::Channel
|
170
|
-
|
171
171
|
def new_member
|
172
172
|
data, content = render
|
173
173
|
send_email(subject: data[:subject], content: content) # this is an example code
|
@@ -195,20 +195,25 @@ Noticent.configure do
|
|
195
195
|
product :product_buzz
|
196
196
|
product :product_bar
|
197
197
|
|
198
|
-
scope :account do
|
198
|
+
scope :account, check_constructor: false do
|
199
199
|
alert :new_user do
|
200
200
|
applies.to :product_foo
|
201
201
|
notify :users
|
202
202
|
notify(:staff).on(:internal)
|
203
203
|
notify :owners
|
204
|
+
|
205
|
+
default true
|
204
206
|
end
|
205
207
|
end
|
206
208
|
|
207
209
|
scope :comment do
|
208
|
-
alert :new_comment do
|
210
|
+
alert :new_comment, constructor_name: :some_constructor do
|
209
211
|
applies.not_to :product_buzz
|
210
212
|
notify :commenter
|
211
|
-
notify :
|
213
|
+
notify :author
|
214
|
+
|
215
|
+
default true
|
216
|
+
default(false) { on(:email) }
|
212
217
|
end
|
213
218
|
alert :comment_updated do
|
214
219
|
notify :commenter
|
@@ -232,6 +237,11 @@ account_payload = AccountPayload.new(1, user.first)
|
|
232
237
|
Noticent.notify(:new_user, account_payload)
|
233
238
|
```
|
234
239
|
|
240
|
+
While it is possible to define and use alert names as symbols, Noticent also creates a constant with the name of the alert under the `Noticent` namespace to help with the use of alert names.
|
241
|
+
By using the constants you can make sure alert names are free of typos.
|
242
|
+
|
243
|
+
For example, if you have an alert called `some_event` then after configuration there will be a constant called `Noticent::ALERT_SOME_EVENT` available to use with the value `:some_event`.
|
244
|
+
|
235
245
|
### Using Each Noticent Component
|
236
246
|
|
237
247
|
#### Payload
|
@@ -250,6 +260,10 @@ end
|
|
250
260
|
|
251
261
|
If specified, the type of the payload is checked against this class at runtime (when `Notify` is called).
|
252
262
|
|
263
|
+
To enforce development type consistency payload should have class method constructors that are named after the alert names. This can be turned off by setting `check_constructor` on scopes to `false`.
|
264
|
+
To share the same class method constructor for different alerts, you can use the `constructor_name` on alert to tell Noticent to look for a constructor that is not named after the alert itself.
|
265
|
+
This is a validation step only and doesn't affect the performance of Noticent.
|
266
|
+
|
253
267
|
#### Channel
|
254
268
|
|
255
269
|
Channels should be derived from `::Noticent::Channel` class and called the same as with the name of the channel with a `Channel` suffix: `email` would be `EmailChannel` and `slack` will be `SlackChannel`. Also, channels should have a method for each type of alert they are supposed to handle. Channel class can be changed using the `klass` argument during definition.
|
@@ -273,7 +287,22 @@ end
|
|
273
287
|
```
|
274
288
|
|
275
289
|
In the example above, we are creating 2 flavors of the slack channel, one called `team_slack` but using the same class and configured differently. When `using` is used in a channel, any attribute passed into `using` will be called on the channel after creation with the given values.
|
276
|
-
For example, in this example, the `Slack` class is instantiated and attribute `fuzz` is set to `:buzz` on it before the alert method is called.
|
290
|
+
For example, in this example, the `Slack` class is instantiated and attribute `fuzz` is set to `:buzz` on it before the alert method is called.
|
291
|
+
|
292
|
+
You can use `on` with a channel name instead of a channel group name instead:
|
293
|
+
|
294
|
+
```ruby
|
295
|
+
Noticent.configure do
|
296
|
+
channel :email
|
297
|
+
channel :private_emails, group: :internal
|
298
|
+
channel :slack
|
299
|
+
|
300
|
+
alert :some_event do
|
301
|
+
notify(:users).on(:internal) # this is a group name
|
302
|
+
notify(:staff).on(:slack) # this is a channel name
|
303
|
+
end
|
304
|
+
end
|
305
|
+
```
|
277
306
|
|
278
307
|
You can use `render` in the channel code to render and return the view file and its front matter (if available). By default, channel will look for `html` and `erb` as the file content and format. You can change these both when calling `render` or at the top of the controller:
|
279
308
|
|
@@ -305,7 +334,7 @@ Views are like Rails views. Noticent supports rendering ERB files. You can also
|
|
305
334
|
```html
|
306
335
|
This is at the top
|
307
336
|
|
308
|
-
<%=
|
337
|
+
<%= @content %>
|
309
338
|
|
310
339
|
This is at the bottom
|
311
340
|
```
|
@@ -327,6 +356,28 @@ Noticent uses a combination of channel, alert and scope to determine if a recipi
|
|
327
356
|
|
328
357
|
Use `Noticent.configuration.opt_in_provider`'s `opt_in`, `opt_out` and `opted_in?` methods to change the opt-in state of each recipient.
|
329
358
|
|
359
|
+
## Default Values
|
360
|
+
|
361
|
+
You can specify a default opt-in value for each alert. By default alerts have a default value of `false` (no opt-in) unless this is globally changed (see Customization section).
|
362
|
+
|
363
|
+
The default value for an alert can be set while this can also be changed per channel. For example:
|
364
|
+
|
365
|
+
```ruby
|
366
|
+
Noticent.configure do
|
367
|
+
channel :email
|
368
|
+
channel :slack
|
369
|
+
channel :webhook
|
370
|
+
|
371
|
+
scope :post do
|
372
|
+
alert :foo do
|
373
|
+
notify :users
|
374
|
+
default(true) # sets the default value for all channels for this alert to true
|
375
|
+
default(false) { on(:slack) } # sets the default value for this alert to false for the slack channel only
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
```
|
380
|
+
|
330
381
|
## Migration
|
331
382
|
|
332
383
|
Noticent provides a method to add new alerts or remove deprecated alerts from the existing recipients. To add a new alert type, you can use `ActiveRecordOptInProvider.add_alert` method:
|
@@ -343,7 +394,17 @@ To remove any deprecated alert, use the `ActiveRecordOptInProvider.remove_alert`
|
|
343
394
|
Noticent.opt_in_provider.remove_alert(scope: :foo, alert_name: :some_old_alert)
|
344
395
|
```
|
345
396
|
|
346
|
-
This removes all instances of the old alert from the opt-ins.
|
397
|
+
This removes all instances of the old alert from the opt-ins.
|
398
|
+
|
399
|
+
## New Recipient Sign up
|
400
|
+
|
401
|
+
When a new recipient signs up, you might want to make sure they have all the default alerts setup for them. You can achieve this by calling `Noticent.setup_recipient`:
|
402
|
+
|
403
|
+
```ruby
|
404
|
+
Noticent.setup_recipient(recipient_id: 1, scope: :post, entity_ids: [2])
|
405
|
+
```
|
406
|
+
|
407
|
+
This will adds the default opt-ins for recipient 1 on all channels that are applicable to it on scope `post` for entity 2.
|
347
408
|
|
348
409
|
## Validation
|
349
410
|
|
@@ -379,6 +440,12 @@ The following items can be customized:
|
|
379
440
|
|
380
441
|
`halt_on_error`: Should notification fail after the first incident of an error during rendering. Default is `false`
|
381
442
|
|
443
|
+
`default_value`: Default value for all alerts unless explicitly specified. Default is `false`
|
444
|
+
|
445
|
+
`use_sub_modules`: If set to true, Noticent will look for Channel and Scope classes in sub modules under the `base_module_name`.
|
446
|
+
With `use_sub_modules` set to false, a channel named `:email` should be called `Noticent::Email` (if `base_module_name` is `Noticent`), while with `use_sub_modules` set to true, the same class should be `Noticent::Channels::Email`.
|
447
|
+
For Payloads, the sub module name will be `Payloads`.
|
448
|
+
|
382
449
|
|
383
450
|
## Development
|
384
451
|
|
data/config.ru
ADDED
data/lib/noticent.rb
CHANGED
@@ -8,7 +8,7 @@ module Noticent
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def opt_out(recipient_id:, scope:, entity_id:, alert_name:, channel_name:)
|
11
|
-
Noticent::OptIn.where(recipient_id: recipient_id, scope: scope, entity_id: entity_id, alert_name: alert_name, channel_name: channel_name).
|
11
|
+
Noticent::OptIn.where(recipient_id: recipient_id, scope: scope, entity_id: entity_id, alert_name: alert_name, channel_name: channel_name).destroy_all
|
12
12
|
end
|
13
13
|
|
14
14
|
def opted_in?(recipient_id:, scope:, entity_id:, alert_name:, channel_name:)
|
@@ -33,5 +33,14 @@ module Noticent
|
|
33
33
|
def remove_alert(scope:, alert_name:)
|
34
34
|
Noticent::OptIn.where('scope = ? AND alert_name = ?', scope, alert_name).destroy_all
|
35
35
|
end
|
36
|
+
|
37
|
+
def remove_entity(scope:, entity_id:)
|
38
|
+
Noticent::OptIn.where('scope = ? AND entity_id = ?', scope, entity_id).destroy_all
|
39
|
+
end
|
40
|
+
|
41
|
+
def remove_recipient(recipient_id:)
|
42
|
+
Noticent::OptIn.where(recipient_id: recipient_id).destroy_all
|
43
|
+
end
|
44
|
+
|
36
45
|
end
|
37
46
|
end
|
data/lib/noticent/channel.rb
CHANGED
@@ -2,41 +2,30 @@
|
|
2
2
|
|
3
3
|
module Noticent
|
4
4
|
class Channel
|
5
|
-
|
6
|
-
|
5
|
+
class_attribute :default_ext, default: :erb
|
6
|
+
class_attribute :default_format, default: :html
|
7
7
|
|
8
|
-
|
8
|
+
attr_accessor :data
|
9
|
+
|
10
|
+
def initialize(config, recipients, payload, configuration)
|
9
11
|
@config = config
|
10
12
|
@recipients = recipients
|
11
13
|
@payload = payload
|
12
|
-
@
|
14
|
+
@configuration = configuration
|
13
15
|
@current_user = payload.current_user if payload.respond_to? :current_user
|
16
|
+
@routes = Rails.application.routes.url_helpers
|
14
17
|
end
|
15
18
|
|
16
|
-
def render_within_context(template
|
17
|
-
|
18
|
-
template.nil? ?
|
19
|
+
def render_within_context(template:, content:, context:)
|
20
|
+
@content = ERB.new(content).result(context)
|
21
|
+
template.nil? ? @content : ERB.new(template).result(binding)
|
19
22
|
end
|
20
23
|
|
21
24
|
protected
|
22
25
|
|
23
26
|
attr_reader :payload
|
24
27
|
attr_reader :recipients
|
25
|
-
attr_reader :
|
26
|
-
|
27
|
-
class << self
|
28
|
-
def default_format(format)
|
29
|
-
@@default_format = format
|
30
|
-
end
|
31
|
-
|
32
|
-
def default_ext(ext)
|
33
|
-
@@default_ext = ext
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def get_binding
|
38
|
-
binding
|
39
|
-
end
|
28
|
+
attr_reader :configuration
|
40
29
|
|
41
30
|
def current_user
|
42
31
|
raise Noticent::NoCurrentUser if @current_user.nil?
|
@@ -44,15 +33,13 @@ module Noticent
|
|
44
33
|
@current_user
|
45
34
|
end
|
46
35
|
|
47
|
-
def render(format:
|
36
|
+
def render(format: default_format, ext: default_ext, layout: "")
|
48
37
|
alert_name = caller[0][/`.*'/][1..-2]
|
49
|
-
channel_name = self.class.name.split(
|
38
|
+
channel_name = self.class.name.split("::").last.underscore
|
50
39
|
view_filename, layout_filename = filenames(channel: channel_name, alert: alert_name, format: format, ext: ext, layout: layout)
|
51
40
|
|
52
|
-
raise Noticent::ViewNotFound, "view #{view_filename} not found" unless File.exist?(view_filename)
|
53
|
-
|
54
41
|
view = View.new(view_filename, template_filename: layout_filename, channel: self)
|
55
|
-
view.process
|
42
|
+
view.process(binding)
|
56
43
|
|
57
44
|
[view.data, view.content]
|
58
45
|
end
|
@@ -60,16 +47,22 @@ module Noticent
|
|
60
47
|
private
|
61
48
|
|
62
49
|
def view_file(channel:, alert:, format:, ext:)
|
63
|
-
File.join(@config.view_dir, channel, "#{alert}.#{format}.#{ext}")
|
50
|
+
view_filename = File.join(@config.view_dir, channel, "#{alert}.#{format}.#{ext}")
|
51
|
+
if !File.exist?(view_filename)
|
52
|
+
# no specific file found, use a convention
|
53
|
+
view_filename = File.join(@config.view_dir, channel, "default.#{format}.#{ext}")
|
54
|
+
raise Noticent::ViewNotFound, "view #{view_filename} not found" unless File.exist?(view_filename)
|
55
|
+
end
|
56
|
+
|
57
|
+
return view_filename
|
64
58
|
end
|
65
59
|
|
66
60
|
def filenames(channel:, alert:, format:, ext:, layout:)
|
67
61
|
view_filename = view_file(channel: channel, alert: alert, format: format, ext: ext)
|
68
|
-
layout_filename =
|
69
|
-
layout_filename = File.join(@config.view_dir,
|
62
|
+
layout_filename = ""
|
63
|
+
layout_filename = File.join(@config.view_dir, "layouts", "#{layout}.#{format}.#{ext}") unless layout == ""
|
70
64
|
|
71
65
|
return view_filename, layout_filename
|
72
66
|
end
|
73
|
-
|
74
67
|
end
|
75
68
|
end
|
data/lib/noticent/config.rb
CHANGED
@@ -2,17 +2,20 @@
|
|
2
2
|
|
3
3
|
module Noticent
|
4
4
|
def self.configure(options = {}, &block)
|
5
|
-
if ENV[
|
5
|
+
if ENV["NOTICENT_RSPEC"] == "1"
|
6
6
|
options = options.merge(
|
7
|
-
base_module_name:
|
7
|
+
base_module_name: "Noticent::Testing",
|
8
8
|
base_dir: File.expand_path("#{File.dirname(__FILE__)}/../../testing"),
|
9
|
-
halt_on_error: true
|
9
|
+
halt_on_error: true,
|
10
10
|
)
|
11
11
|
end
|
12
12
|
|
13
13
|
@config = Noticent::Config::Builder.new(options, &block).build
|
14
14
|
@config.validate!
|
15
15
|
|
16
|
+
# construct dynamics
|
17
|
+
@config.create_dynamics
|
18
|
+
|
16
19
|
@config
|
17
20
|
end
|
18
21
|
|
@@ -28,12 +31,38 @@ module Noticent
|
|
28
31
|
engine.dispatch
|
29
32
|
end
|
30
33
|
|
34
|
+
# recipient is the recipient object id
|
35
|
+
# entities is an array of all entity ids this recipient needs to opt in based on the alert defaults
|
36
|
+
# scope is the name of the scope these entities belong to
|
37
|
+
def self.setup_recipient(recipient_id:, scope:, entity_ids:)
|
38
|
+
raise ArgumentError, "no scope named '#{scope}' found" if @config.scopes[scope].nil?
|
39
|
+
|
40
|
+
alerts = @config.alerts_by_scope(scope)
|
41
|
+
|
42
|
+
alerts.each do |alert|
|
43
|
+
channels = @config.alert_channels(alert.name)
|
44
|
+
|
45
|
+
channels.each do |channel|
|
46
|
+
next unless alert.default_for(channel.name)
|
47
|
+
|
48
|
+
entity_ids.each do |entity_id|
|
49
|
+
@config.opt_in_provider.opt_in(recipient_id: recipient_id,
|
50
|
+
scope: scope,
|
51
|
+
entity_id: entity_id,
|
52
|
+
alert_name: alert.name,
|
53
|
+
channel_name: channel.name)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
31
59
|
class Config
|
32
60
|
attr_reader :hooks
|
33
61
|
attr_reader :channels
|
34
62
|
attr_reader :scopes
|
35
63
|
attr_reader :alerts
|
36
64
|
attr_reader :products
|
65
|
+
attr_reader :channel_groups
|
37
66
|
|
38
67
|
def initialize(options = {})
|
39
68
|
@options = options
|
@@ -46,12 +75,6 @@ module Noticent
|
|
46
75
|
@channels.values.select { |x| x.group == group }
|
47
76
|
end
|
48
77
|
|
49
|
-
def channel_groups
|
50
|
-
return [] if @channels.nil?
|
51
|
-
|
52
|
-
@channels.values.collect(&:group).uniq
|
53
|
-
end
|
54
|
-
|
55
78
|
def alert_channels(alert_name)
|
56
79
|
alert = @alerts[alert_name]
|
57
80
|
raise ArgumentError, "no alert #{alert_name} found" if alert.nil?
|
@@ -62,7 +85,7 @@ module Noticent
|
|
62
85
|
|
63
86
|
def products_by_alert(alert_name)
|
64
87
|
alert = @alerts[alert_name]
|
65
|
-
raise ArgumentError "no alert #{alert_name} found" if alert.nil?
|
88
|
+
raise ArgumentError, "no alert #{alert_name} found" if alert.nil?
|
66
89
|
|
67
90
|
alert.products
|
68
91
|
end
|
@@ -90,23 +113,46 @@ module Noticent
|
|
90
113
|
end
|
91
114
|
|
92
115
|
def halt_on_error
|
93
|
-
@options[:halt_on_error].nil?
|
116
|
+
@options[:halt_on_error].nil? ? false : @options[:halt_on_error]
|
117
|
+
end
|
118
|
+
|
119
|
+
def skip_alert_with_no_subscribers
|
120
|
+
@options[:skip_alert_with_no_subscribers].nil? ? false : @options[:skip_alert_with_no_subscribers]
|
121
|
+
end
|
122
|
+
|
123
|
+
def default_value
|
124
|
+
@options[:default_value].nil? ? false : @options[:default_value]
|
125
|
+
end
|
126
|
+
|
127
|
+
def use_sub_modules
|
128
|
+
@options[:use_sub_modules].nil? ? false : @options[:use_sub_modules]
|
94
129
|
end
|
95
130
|
|
96
131
|
def payload_dir
|
97
|
-
File.join(base_dir,
|
132
|
+
File.join(base_dir, "payloads")
|
98
133
|
end
|
99
134
|
|
100
135
|
def scope_dir
|
101
|
-
File.join(base_dir,
|
136
|
+
File.join(base_dir, "scopes")
|
102
137
|
end
|
103
138
|
|
104
139
|
def channel_dir
|
105
|
-
File.join(base_dir,
|
140
|
+
File.join(base_dir, "channels")
|
106
141
|
end
|
107
142
|
|
108
143
|
def view_dir
|
109
|
-
File.join(base_dir,
|
144
|
+
File.join(base_dir, "views")
|
145
|
+
end
|
146
|
+
|
147
|
+
def create_dynamics
|
148
|
+
return if alerts.nil?
|
149
|
+
|
150
|
+
alerts.keys.each do |alert|
|
151
|
+
const_name = "ALERT_#{alert.to_s.upcase}"
|
152
|
+
next if Noticent.const_defined?(const_name)
|
153
|
+
|
154
|
+
Noticent.const_set(const_name, alert)
|
155
|
+
end
|
110
156
|
end
|
111
157
|
|
112
158
|
def validate!
|
@@ -119,7 +165,7 @@ module Noticent
|
|
119
165
|
def initialize(options = {}, &block)
|
120
166
|
@options = options
|
121
167
|
@config = Noticent::Config.new(options)
|
122
|
-
raise BadConfiguration,
|
168
|
+
raise BadConfiguration, "no OptInProvider configured" if @config.opt_in_provider.nil?
|
123
169
|
|
124
170
|
instance_eval(&block) if block_given?
|
125
171
|
|
@@ -150,6 +196,14 @@ module Noticent
|
|
150
196
|
@options[:halt_on_error] = value
|
151
197
|
end
|
152
198
|
|
199
|
+
def skip_alert_with_no_subscribers=(value)
|
200
|
+
@options[:skip_alert_with_no_subscribers] = value
|
201
|
+
end
|
202
|
+
|
203
|
+
def use_sub_modules=(value)
|
204
|
+
@options[:use_sub_modules] = value
|
205
|
+
end
|
206
|
+
|
153
207
|
def hooks
|
154
208
|
if @config.hooks.nil?
|
155
209
|
@config.instance_variable_set(:@hooks, Noticent::Definitions::Hooks.new)
|
@@ -177,8 +231,12 @@ module Noticent
|
|
177
231
|
|
178
232
|
def channel(name, group: :default, klass: nil, &block)
|
179
233
|
channels = @config.instance_variable_get(:@channels) || {}
|
234
|
+
channel_groups = @config.instance_variable_get(:@channel_groups) || []
|
180
235
|
|
181
236
|
raise BadConfiguration, "channel '#{name}' already defined" if channels.include? name
|
237
|
+
raise BadConfiguration, "a channel group named '#{group}' already exists. channels and channel groups cannot have duplicates" if channel_groups.include? name
|
238
|
+
|
239
|
+
channel_groups << group
|
182
240
|
|
183
241
|
channel = Noticent::Definitions::Channel.new(@config, name, group: group, klass: klass)
|
184
242
|
hooks.run(:pre_channel_registration, channel)
|
@@ -188,15 +246,16 @@ module Noticent
|
|
188
246
|
channels[name] = channel
|
189
247
|
|
190
248
|
@config.instance_variable_set(:@channels, channels)
|
249
|
+
@config.instance_variable_set(:@channel_groups, channel_groups.uniq)
|
191
250
|
channel
|
192
251
|
end
|
193
252
|
|
194
|
-
def scope(name, payload_class: nil, &block)
|
253
|
+
def scope(name, payload_class: nil, check_constructor: true, &block)
|
195
254
|
scopes = @config.instance_variable_get(:@scopes) || {}
|
196
255
|
|
197
256
|
raise BadConfiguration, "scope '#{name}' already defined" if scopes.include? name
|
198
257
|
|
199
|
-
scope = Noticent::Definitions::Scope.new(@config, name, payload_class: payload_class)
|
258
|
+
scope = Noticent::Definitions::Scope.new(@config, name, payload_class: payload_class, check_constructor: check_constructor)
|
200
259
|
scope.instance_eval(&block)
|
201
260
|
|
202
261
|
scopes[name] = scope
|