user_notification 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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 %>