user_notification 0.0.1

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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +1 -0
  5. data/Rakefile +18 -0
  6. data/lib/generators/user_notification/migration/migration_generator.rb +17 -0
  7. data/lib/generators/user_notification/migration/templates/migration.rb +24 -0
  8. data/lib/generators/user_notification/notification/notification_generator.rb +17 -0
  9. data/lib/generators/user_notification/notification/templates/notification.rb +3 -0
  10. data/lib/generators/user_notification.rb +14 -0
  11. data/lib/user_notification/actions/creation.rb +15 -0
  12. data/lib/user_notification/actions/destruction.rb +15 -0
  13. data/lib/user_notification/actions/update.rb +15 -0
  14. data/lib/user_notification/activity.rb +6 -0
  15. data/lib/user_notification/common.rb +342 -0
  16. data/lib/user_notification/config.rb +63 -0
  17. data/lib/user_notification/models/activist.rb +9 -0
  18. data/lib/user_notification/models/adapter.rb +5 -0
  19. data/lib/user_notification/models/notification.rb +13 -0
  20. data/lib/user_notification/models/trackable.rb +9 -0
  21. data/lib/user_notification/orm/active_record/activist.rb +48 -0
  22. data/lib/user_notification/orm/active_record/adapter.rb +16 -0
  23. data/lib/user_notification/orm/active_record/notification.rb +24 -0
  24. data/lib/user_notification/orm/active_record/trackable.rb +15 -0
  25. data/lib/user_notification/orm/active_record.rb +5 -0
  26. data/lib/user_notification/orm/mongo_mapper/activist.rb +46 -0
  27. data/lib/user_notification/orm/mongo_mapper/adapter.rb +12 -0
  28. data/lib/user_notification/orm/mongo_mapper/notification.rb +33 -0
  29. data/lib/user_notification/orm/mongo_mapper/trackable.rb +11 -0
  30. data/lib/user_notification/orm/mongo_mapper.rb +4 -0
  31. data/lib/user_notification/orm/mongoid/activist.rb +45 -0
  32. data/lib/user_notification/orm/mongoid/adapter.rb +12 -0
  33. data/lib/user_notification/orm/mongoid/notification.rb +26 -0
  34. data/lib/user_notification/orm/mongoid/trackable.rb +11 -0
  35. data/lib/user_notification/orm/mongoid.rb +4 -0
  36. data/lib/user_notification/renderable.rb +118 -0
  37. data/lib/user_notification/roles/deactivatable.rb +42 -0
  38. data/lib/user_notification/roles/tracked.rb +183 -0
  39. data/lib/user_notification/utility/store_controller.rb +37 -0
  40. data/lib/user_notification/utility/view_helpers.rb +26 -0
  41. data/lib/user_notification/version.rb +4 -0
  42. data/lib/user_notification.rb +68 -0
  43. data/test/migrations/001_create_notifications.rb +24 -0
  44. data/test/migrations/002_create_articles.rb +11 -0
  45. data/test/migrations/003_create_users.rb +8 -0
  46. data/test/migrations/004_add_nonstandard_to_notifications.rb +7 -0
  47. data/test/mongo_mapper.yml +4 -0
  48. data/test/mongoid.yml +6 -0
  49. data/test/test_activist.rb +56 -0
  50. data/test/test_common.rb +168 -0
  51. data/test/test_controller_integration.rb +41 -0
  52. data/test/test_generators.rb +30 -0
  53. data/test/test_helper.rb +124 -0
  54. data/test/test_notification.rb +67 -0
  55. data/test/test_tracking.rb +378 -0
  56. data/test/test_view_helpers.rb +36 -0
  57. data/test/views/layouts/_notification.erb +1 -0
  58. data/test/views/user_notification/_test.erb +8 -0
  59. metadata +260 -0
@@ -0,0 +1,67 @@
1
+ require 'test_helper'
2
+
3
+ describe 'UserNotification::Notification Rendering' do
4
+ describe '#text' do
5
+ subject { UserNotification::Notification.new(:key => 'notification.test', :parameters => {:one => 1}) }
6
+
7
+ specify '#text uses translations' do
8
+ subject.save
9
+ I18n.config.backend.store_translations(:en,
10
+ {:notification => {:test => '%{one} %{two}'}}
11
+ )
12
+ subject.text(:two => 2).must_equal('1 2')
13
+ subject.parameters.must_equal({:one => 1})
14
+ end
15
+ end
16
+
17
+ describe '#render' do
18
+ subject do
19
+ s = UserNotification::Notification.new(:key => 'notification.test', :parameters => {:one => 1})
20
+ s.save && s
21
+ end
22
+
23
+ let(:template_output) { "<strong>1, 2</strong>\n<em>notification.test, #{subject.id}</em>\n" }
24
+ before { @controller.view_paths << File.expand_path('../views', __FILE__) }
25
+
26
+ it 'uses view partials when available' do
27
+ UserNotification.set_controller(Struct.new(:current_user).new('fake'))
28
+ subject.render(self, :two => 2)
29
+ rendered.must_equal template_output + "fake\n"
30
+ end
31
+
32
+ it 'uses requested partial'
33
+
34
+ it 'uses view partials without controller' do
35
+ UserNotification.set_controller(nil)
36
+ subject.render(self, :two => 2)
37
+ rendered.must_equal template_output + "\n"
38
+ end
39
+
40
+ it 'provides local variables' do
41
+ UserNotification.set_controller(nil)
42
+ subject.render(self, locals: {two: 2})
43
+ rendered.chomp.must_equal "2"
44
+ end
45
+
46
+ it 'uses translations only when requested' do
47
+ I18n.config.backend.store_translations(:en,
48
+ {:notification => {:test => '%{one} %{two}'}}
49
+ )
50
+ @controller.view_paths.paths.clear
51
+ subject.render(self, two: 2, display: :i18n)
52
+ rendered.must_equal '1 2'
53
+ end
54
+
55
+ it "uses specified layout" do
56
+ UserNotification.set_controller(nil)
57
+ subject.render(self, :layout => "notification")
58
+ rendered.must_include "Here be the layouts"
59
+
60
+ subject.render(self, :layout => "layouts/notification")
61
+ rendered.must_include "Here be the layouts"
62
+
63
+ subject.render(self, :layout => :notification)
64
+ rendered.must_include "Here be the layouts"
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,378 @@
1
+ require 'test_helper'
2
+
3
+ describe UserNotification::Tracked do
4
+ describe 'defining instance options' do
5
+ subject { article.new }
6
+ let :options do
7
+ { :key => 'key',
8
+ :params => {:a => 1},
9
+ :owner => User.create,
10
+ :recipient => User.create }
11
+ end
12
+ before(:each) { subject.notification(options) }
13
+ let(:notification){ subject.save; subject.notifications.last }
14
+
15
+ specify { subject.notification_key.must_be_same_as options[:key] }
16
+ specify { notification.key.must_equal options[:key] }
17
+
18
+ specify { subject.notification_owner.must_be_same_as options[:owner] }
19
+ specify { notification.owner.must_equal options[:owner] }
20
+
21
+ specify { subject.notification_params.must_be_same_as options[:params] }
22
+ specify { notification.parameters.must_equal options[:params] }
23
+
24
+ specify { subject.notification_recipient.must_be_same_as options[:recipient] }
25
+ specify { notification.recipient.must_equal options[:recipient] }
26
+ end
27
+
28
+ it 'can be tracked and be an activist at the same time' do
29
+ case UserNotification.config.orm
30
+ when :mongoid
31
+ class ActivistAndTrackedArticle
32
+ include Mongoid::Document
33
+ include Mongoid::Timestamps
34
+ include UserNotification::Model
35
+
36
+ belongs_to :user
37
+
38
+ field :name, type: String
39
+ field :published, type: Boolean
40
+ tracked
41
+ activist
42
+ end
43
+ when :mongo_mapper
44
+ class ActivistAndTrackedArticle
45
+ include MongoMapper::Document
46
+ include UserNotification::Model
47
+
48
+ belongs_to :user
49
+
50
+ key :name, String
51
+ key :published, Boolean
52
+ tracked
53
+ activist
54
+ timestamps!
55
+ end
56
+ when :active_record
57
+ class ActivistAndTrackedArticle < ActiveRecord::Base
58
+ self.table_name = 'articles'
59
+ include UserNotification::Model
60
+ tracked
61
+ activist
62
+
63
+ if ::ActiveRecord::VERSION::MAJOR < 4
64
+ attr_accessible :name, :published, :user
65
+ end
66
+ belongs_to :user
67
+ end
68
+ end
69
+
70
+ art = ActivistAndTrackedArticle.new
71
+ art.save
72
+ art.notifications.last.trackable_id.must_equal art.id
73
+ art.notifications.last.owner_id.must_equal nil
74
+ end
75
+
76
+ describe 'custom fields' do
77
+ describe 'global' do
78
+ it 'should resolve symbols' do
79
+ a = article(nonstandard: :name).new(name: 'Symbol resolved')
80
+ a.save
81
+ a.notifications.last.nonstandard.must_equal 'Symbol resolved'
82
+ end
83
+
84
+ it 'should resolve procs' do
85
+ a = article(nonstandard: proc {|_, model| model.name}).new(name: 'Proc resolved')
86
+ a.save
87
+ a.notifications.last.nonstandard.must_equal 'Proc resolved'
88
+ end
89
+ end
90
+
91
+ describe 'instance' do
92
+ it 'should resolve symbols' do
93
+ a = article.new(name: 'Symbol resolved')
94
+ a.notification nonstandard: :name
95
+ a.save
96
+ a.notifications.last.nonstandard.must_equal 'Symbol resolved'
97
+ end
98
+
99
+ it 'should resolve procs' do
100
+ a = article.new(name: 'Proc resolved')
101
+ a.notification nonstandard: proc {|_, model| model.name}
102
+ a.save
103
+ a.notifications.last.nonstandard.must_equal 'Proc resolved'
104
+ end
105
+ end
106
+ end
107
+
108
+ it 'should reset instance options on successful create_notification' do
109
+ a = article.new
110
+ a.notification key: 'test', params: {test: 1}
111
+ a.save
112
+ a.notifications.count.must_equal 1
113
+ ->{a.create_notification}.must_raise UserNotification::NoKeyProvided
114
+ a.notification_params.must_be_empty
115
+ a.notification key: 'asd'
116
+ a.create_notification
117
+ ->{a.create_notification}.must_raise UserNotification::NoKeyProvided
118
+ end
119
+
120
+ it 'should not accept global key option' do
121
+ # this example tests the lack of presence of sth that should not be here
122
+ a = article(key: 'asd').new
123
+ a.save
124
+ ->{a.create_notification}.must_raise UserNotification::NoKeyProvided
125
+ a.notifications.count.must_equal 1
126
+ end
127
+
128
+ it 'should not change global custom fields' do
129
+ a = article(nonstandard: 'global').new
130
+ a.notification nonstandard: 'instance'
131
+ a.save
132
+ a.class.notification_custom_fields_global.must_equal nonstandard: 'global'
133
+ end
134
+
135
+ describe 'disabling functionality' do
136
+ it 'allows for global disable' do
137
+ UserNotification.enabled = false
138
+ notification_count_before = UserNotification::Notification.count
139
+
140
+ @article = article().new
141
+ @article.save
142
+ UserNotification::Notification.count.must_equal notification_count_before
143
+
144
+ UserNotification.enabled = true
145
+ end
146
+
147
+ it 'allows for class-wide disable' do
148
+ notification_count_before = UserNotification::Notification.count
149
+
150
+ klass = article
151
+ klass.user_notification_off
152
+ @article = klass.new
153
+ @article.save
154
+ UserNotification::Notification.count.must_equal notification_count_before
155
+
156
+ klass.user_notification_on
157
+ @article.save
158
+ UserNotification::Notification.count.must_be :>, notification_count_before
159
+ end
160
+ end
161
+
162
+ describe '#tracked' do
163
+ subject { article(options) }
164
+ let(:options) { {} }
165
+
166
+ it 'allows skipping the tracking on CRUD actions' do
167
+ case UserNotification.config.orm
168
+ when :mongoid
169
+ art = Class.new do
170
+ include Mongoid::Document
171
+ include Mongoid::Timestamps
172
+ include UserNotification::Model
173
+
174
+ belongs_to :user
175
+
176
+ field :name, type: String
177
+ field :published, type: Boolean
178
+ tracked :skip_defaults => true
179
+ end
180
+ when :mongo_mapper
181
+ art = Class.new do
182
+ include MongoMapper::Document
183
+ include UserNotification::Model
184
+
185
+ belongs_to :user
186
+
187
+ key :name, String
188
+ key :published, Boolean
189
+ tracked :skip_defaults => true
190
+
191
+ timestamps!
192
+ end
193
+ when :active_record
194
+ art = article(:skip_defaults => true)
195
+ end
196
+
197
+ art.must_include UserNotification::Common
198
+ art.wont_include UserNotification::Creation
199
+ art.wont_include UserNotification::Update
200
+ art.wont_include UserNotification::Destruction
201
+ end
202
+
203
+ describe 'default options' do
204
+ subject { article }
205
+
206
+ specify { subject.must_include UserNotification::Creation }
207
+ specify { subject.must_include UserNotification::Destruction }
208
+ specify { subject.must_include UserNotification::Update }
209
+
210
+ specify { subject._create_callbacks.select do |c|
211
+ c.kind.eql?(:after) && c.filter == :notification_on_create
212
+ end.wont_be_empty }
213
+
214
+ specify { subject._update_callbacks.select do |c|
215
+ c.kind.eql?(:after) && c.filter == :notification_on_update
216
+ end.wont_be_empty }
217
+
218
+ specify { subject._destroy_callbacks.select do |c|
219
+ c.kind.eql?(:before) && c.filter == :notification_on_destroy
220
+ end.wont_be_empty }
221
+ end
222
+
223
+ it 'accepts :except option' do
224
+ case UserNotification.config.orm
225
+ when :mongoid
226
+ art = Class.new do
227
+ include Mongoid::Document
228
+ include Mongoid::Timestamps
229
+ include UserNotification::Model
230
+
231
+ belongs_to :user
232
+
233
+ field :name, type: String
234
+ field :published, type: Boolean
235
+ tracked :except => [:create]
236
+ end
237
+ when :mongo_mapper
238
+ art = Class.new do
239
+ include MongoMapper::Document
240
+ include UserNotification::Model
241
+
242
+ belongs_to :user
243
+
244
+ key :name, String
245
+ key :published, Boolean
246
+ tracked :except => [:create]
247
+
248
+ timestamps!
249
+ end
250
+ when :active_record
251
+ art = article(:except => [:create])
252
+ end
253
+
254
+ art.wont_include UserNotification::Creation
255
+ art.must_include UserNotification::Update
256
+ art.must_include UserNotification::Destruction
257
+ end
258
+
259
+ it 'accepts :only option' do
260
+ case UserNotification.config.orm
261
+ when :mongoid
262
+ art = Class.new do
263
+ include Mongoid::Document
264
+ include Mongoid::Timestamps
265
+ include UserNotification::Model
266
+
267
+ belongs_to :user
268
+
269
+ field :name, type: String
270
+ field :published, type: Boolean
271
+
272
+ tracked :only => [:create, :update]
273
+ end
274
+ when :mongo_mapper
275
+ art = Class.new do
276
+ include MongoMapper::Document
277
+ include UserNotification::Model
278
+
279
+ belongs_to :user
280
+
281
+ key :name, String
282
+ key :published, Boolean
283
+
284
+ tracked :only => [:create, :update]
285
+ end
286
+ when :active_record
287
+ art = article({:only => [:create, :update]})
288
+ end
289
+
290
+ art.must_include UserNotification::Creation
291
+ art.wont_include UserNotification::Destruction
292
+ art.must_include UserNotification::Update
293
+ end
294
+
295
+ it 'accepts :owner option' do
296
+ owner = mock('owner')
297
+ subject.tracked(:owner => owner)
298
+ subject.notification_owner_global.must_equal owner
299
+ end
300
+
301
+ it 'accepts :params option' do
302
+ params = {:a => 1}
303
+ subject.tracked(:params => params)
304
+ subject.notification_params_global.must_equal params
305
+ end
306
+
307
+ it 'accepts :on option' do
308
+ on = {:a => lambda{}, :b => proc {}}
309
+ subject.tracked(:on => on)
310
+ subject.notification_hooks.must_equal on
311
+ end
312
+
313
+ it 'accepts :on option with string keys' do
314
+ on = {'a' => lambda {}}
315
+ subject.tracked(:on => on)
316
+ subject.notification_hooks.must_equal on.symbolize_keys
317
+ end
318
+
319
+ it 'accepts :on values that are procs' do
320
+ on = {:unpassable => 1, :proper => lambda {}, :proper_proc => proc {}}
321
+ subject.tracked(:on => on)
322
+ subject.notification_hooks.must_include :proper
323
+ subject.notification_hooks.must_include :proper_proc
324
+ subject.notification_hooks.wont_include :unpassable
325
+ end
326
+
327
+ describe 'global options' do
328
+ subject { article(recipient: :test, owner: :test2, params: {a: 'b'}) }
329
+
330
+ specify { subject.notification_recipient_global.must_equal :test }
331
+ specify { subject.notification_owner_global.must_equal :test2 }
332
+ specify { subject.notification_params_global.must_equal(a: 'b') }
333
+ end
334
+ end
335
+
336
+ describe 'notification hooks' do
337
+ subject { s = article; s.notification_hooks = {:test => hook}; s }
338
+ let(:hook) { lambda {} }
339
+
340
+ it 'retrieves hooks' do
341
+ assert_same hook, subject.get_hook(:test)
342
+ end
343
+
344
+ it 'retrieves hooks by string keys' do
345
+ assert_same hook, subject.get_hook('test')
346
+ end
347
+
348
+ it 'returns nil when no matching hook is present' do
349
+ nil.must_be_same_as subject.get_hook(:nonexistent)
350
+ end
351
+
352
+ it 'allows hooks to decide if notification should be created' do
353
+ subject.tracked
354
+ @article = subject.new(:name => 'Some Name')
355
+ UserNotification.set_controller(mock('controller'))
356
+ pf = proc { |model, controller|
357
+ controller.must_be_same_as UserNotification.get_controller
358
+ model.name.must_equal 'Some Name'
359
+ false
360
+ }
361
+ pt = proc { |model, controller|
362
+ controller.must_be_same_as UserNotification.get_controller
363
+ model.name.must_equal 'Other Name'
364
+ true # this will save the notification with *.update key
365
+ }
366
+ @article.class.notification_hooks = {:create => pf, :update => pt, :destroy => pt}
367
+
368
+ @article.notifications.to_a.must_be_empty
369
+ @article.save # create
370
+ @article.name = 'Other Name'
371
+ @article.save # update
372
+ @article.destroy # destroy
373
+
374
+ @article.notifications.count.must_equal 2
375
+ @article.notifications.first.key.must_equal 'article.update'
376
+ end
377
+ end
378
+ end
@@ -0,0 +1,36 @@
1
+ require 'test_helper'
2
+
3
+ describe 'ViewHelpers Rendering' do
4
+ include UserNotification::ViewHelpers
5
+
6
+ # is this a proper test?
7
+ it 'provides render_notification helper' do
8
+ notification = mock('notification')
9
+ notification.stubs(:is_a?).with(UserNotification::Notification).returns(true)
10
+ notification.expects(:render).with(self, {})
11
+ render_notification(notification)
12
+ end
13
+
14
+ it 'handles multiple notifications' do
15
+ notification = mock('notification')
16
+ notification.expects(:render).with(self, {})
17
+ render_notifications([notification])
18
+ end
19
+
20
+ it 'flushes content_for between partials renderes' do
21
+ @view_flow = mock('view_flow')
22
+ @view_flow.expects(:set).twice.with('name', ActiveSupport::SafeBuffer.new)
23
+
24
+ single_content_for('name', 'content')
25
+ @name.must_equal 'name'
26
+ @content.must_equal 'content'
27
+ single_content_for('name', 'content2')
28
+ @name.must_equal 'name'
29
+ @content.must_equal 'content2'
30
+ end
31
+
32
+ def content_for(name, content, &block)
33
+ @name = name
34
+ @content = content
35
+ end
36
+ end
@@ -0,0 +1 @@
1
+ <h2>Here be the layouts</h2><%= yield %>
@@ -0,0 +1,8 @@
1
+ <% if defined?(two) == 'local-variable' %>
2
+ <% # output local_variable if defined %>
3
+ <%= two %>
4
+ <% else %>
5
+ <strong><%= p[:one] %>, <%= params['two'] %></strong>
6
+ <em><%= a.key %>, <%= notification.id %></em>
7
+ <%= current_user %>
8
+ <% end %>