noticent 0.0.1.pre.pre → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/config.ru ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubygems"
4
+ require "bundler"
5
+
6
+ Bundler.require :default, :development
7
+
8
+ Combustion.initialize! :all
9
+ run Combustion::Application
data/lib/noticent.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_support/all'
3
+ require "active_support/all"
4
+ require "rails/all"
4
5
 
5
6
  Dir["#{File.dirname(__FILE__)}/noticent/**/*.rb"].each { |f| load(f) }
6
7
  load "#{File.dirname(__FILE__)}/generators/noticent/noticent.rb"
@@ -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).delete
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
@@ -2,41 +2,30 @@
2
2
 
3
3
  module Noticent
4
4
  class Channel
5
- @@default_ext = :erb
6
- @@default_format = :html
5
+ class_attribute :default_ext, default: :erb
6
+ class_attribute :default_format, default: :html
7
7
 
8
- def initialize(config, recipients, payload, context)
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
- @context = context
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, content)
17
- rendered_content = ERB.new(content).result(get_binding)
18
- template.nil? ? rendered_content : ERB.new(template).result(get_binding { rendered_content })
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 :context
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: @@default_format, ext: @@default_ext, layout: '')
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('::').last.underscore
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, 'layouts', "#{layout}.#{format}.#{ext}") unless layout == ''
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
@@ -2,17 +2,20 @@
2
2
 
3
3
  module Noticent
4
4
  def self.configure(options = {}, &block)
5
- if ENV['NOTICENT_RSPEC'] == '1'
5
+ if ENV["NOTICENT_RSPEC"] == "1"
6
6
  options = options.merge(
7
- base_module_name: 'Noticent::Testing',
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?
@@ -90,23 +113,46 @@ module Noticent
90
113
  end
91
114
 
92
115
  def halt_on_error
93
- @options[:halt_on_error].nil? || false
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, 'payloads')
132
+ File.join(base_dir, "payloads")
98
133
  end
99
134
 
100
135
  def scope_dir
101
- File.join(base_dir, 'scopes')
136
+ File.join(base_dir, "scopes")
102
137
  end
103
138
 
104
139
  def channel_dir
105
- File.join(base_dir, 'channels')
140
+ File.join(base_dir, "channels")
106
141
  end
107
142
 
108
143
  def view_dir
109
- File.join(base_dir, 'views')
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, 'no OptInProvider configured' if @config.opt_in_provider.nil?
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
@@ -8,12 +8,15 @@ module Noticent
8
8
  attr_reader :notifiers
9
9
  attr_reader :config
10
10
  attr_reader :products
11
+ attr_reader :constructor_name
11
12
 
12
- def initialize(config, name:, scope:)
13
+ def initialize(config, name:, scope:, constructor_name:)
13
14
  @config = config
14
15
  @name = name
15
16
  @scope = scope
17
+ @constructor_name = constructor_name
16
18
  @products = Noticent::Definitions::ProductGroup.new(@config)
19
+ @defaults = { _any_: Noticent::Definitions::Alert::DefaultValue.new(self, :_any_, config.default_value) }
17
20
  end
18
21
 
19
22
  def notify(recipient, template: '')
@@ -27,21 +30,54 @@ module Noticent
27
30
  alert_notifier
28
31
  end
29
32
 
33
+ def default_for(channel)
34
+ raise ArgumentError, "no channel named '#{channel}' found" if @config.channels[channel].nil?
35
+
36
+ @defaults[channel].nil? ? @defaults[:_any_].value : @defaults[channel].value
37
+ end
38
+
39
+ def default_value
40
+ @defaults[:_any_].value
41
+ end
42
+
43
+ def default(value, &block)
44
+ defaults = @defaults
45
+
46
+ if block_given?
47
+ default = Noticent::Definitions::Alert::DefaultValue.new(self, :_any_, value)
48
+ default.instance_eval(&block)
49
+
50
+ defaults[default.channel] = default
51
+ else
52
+ defaults[:_any_].value = value
53
+ end
54
+
55
+ @defaults = defaults
56
+
57
+ default
58
+ end
59
+
30
60
  def applies
31
61
  @products
32
62
  end
33
63
 
34
64
  def validate!
35
65
  channels = @config.alert_channels(@name)
66
+ raise BadConfiguration, "no notifiers are assigned to alert '#{@name}'" if @notifiers.nil? || @notifiers.empty?
67
+
36
68
  channels.each do |channel|
37
69
  raise BadConfiguration, "channel #{channel.name} (#{channel.klass}) has no method called #{@name}" unless channel.klass.method_defined? @name
38
70
  end
71
+
72
+ # if a payload class is available, we can make sure it has a constructor with the name of the event
73
+ raise Noticent::BadConfiguration, "payload #{@scope.payload_class} doesn't have a class method called #{name}" if @scope.check_constructor && !@scope.payload_class.respond_to?(@constructor_name)
39
74
  end
40
75
 
41
76
  # holds a list of recipient + channel
42
77
  class Notifier
43
78
  attr_reader :recipient
44
- attr_reader :channel_group
79
+ attr_reader :channel_group # group to be notified
80
+ attr_reader :channel # channel to be notified
45
81
  attr_reader :template
46
82
 
47
83
  def initialize(alert, recipient, template: '')
@@ -50,13 +86,51 @@ module Noticent
50
86
  @config = alert.config
51
87
  @template = template
52
88
  @channel_group = :default
89
+ @channel = nil
90
+ end
91
+
92
+ def on(channel_group_or_name)
93
+ # is it a group or a channel name?
94
+ if @config.channel_groups.include? channel_group_or_name
95
+ # it's a group
96
+ @channel_group = channel_group_or_name
97
+ @channel = nil
98
+ elsif !@config.channels[channel_group_or_name].nil?
99
+ @channel_group = :_none_
100
+ @channel = @config.channels[channel_group_or_name]
101
+ else
102
+ # not a group and not a channel
103
+ raise ArgumentError, "no channel or channel group found named '#{channel_group_or_name}'"
104
+ end
105
+ end
106
+
107
+ # returns an array of all channels this notifier should send to
108
+ def applicable_channels
109
+ if @channel_group == :_none_
110
+ # it's a single channel
111
+ [@channel]
112
+ else
113
+ @config.channels_by_group(@channel_group)
114
+ end
53
115
  end
116
+ end
117
+
118
+ class DefaultValue
119
+ attr_reader :channel
120
+ attr_accessor :value
121
+
122
+ def initialize(alert, channel, value)
123
+ @alert = alert
124
+ @channel = channel
125
+ @value = value
126
+ end
127
+
128
+ def on(channel)
129
+ raise BadConfiguration, "no channel named '#{channel}'" if @alert.config.channels[channel].nil?
54
130
 
55
- def on(channel_group)
56
- # validate the group name
57
- raise ArgumentError, "no channel group found named '#{channel_group}'" if @config.channels_by_group(channel_group).empty?
131
+ @channel = channel
58
132
 
59
- @channel_group = channel_group
133
+ self
60
134
  end
61
135
  end
62
136
  end