noticent 0.0.1.pre.pre → 0.0.5
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/.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
|