acts-as-approvable 0.6.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 (61) hide show
  1. data/.gitignore +6 -0
  2. data/Appraisals +18 -0
  3. data/CHANGELOG +10 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +50 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +108 -0
  8. data/Rakefile +81 -0
  9. data/VERSION +1 -0
  10. data/acts-as-approvable.gemspec +31 -0
  11. data/gemfiles/rails2.gemfile +7 -0
  12. data/gemfiles/rails2.gemfile.lock +50 -0
  13. data/gemfiles/rails30.gemfile +8 -0
  14. data/gemfiles/rails30.gemfile.lock +90 -0
  15. data/gemfiles/rails31.gemfile +8 -0
  16. data/gemfiles/rails31.gemfile.lock +101 -0
  17. data/gemfiles/rails32.gemfile +8 -0
  18. data/gemfiles/rails32.gemfile.lock +99 -0
  19. data/generators/acts_as_approvable/USAGE +3 -0
  20. data/generators/acts_as_approvable/acts_as_approvable_generator.rb +86 -0
  21. data/generators/acts_as_approvable/templates/approvals_controller.rb +97 -0
  22. data/generators/acts_as_approvable/templates/create_approvals.rb +26 -0
  23. data/generators/acts_as_approvable/templates/initializer.rb +3 -0
  24. data/generators/acts_as_approvable/templates/views/erb/_owner_select.html.erb +4 -0
  25. data/generators/acts_as_approvable/templates/views/erb/_table.html.erb +26 -0
  26. data/generators/acts_as_approvable/templates/views/erb/index.html.erb +15 -0
  27. data/generators/acts_as_approvable/templates/views/haml/_owner_select.html.haml +3 -0
  28. data/generators/acts_as_approvable/templates/views/haml/_table.html.haml +19 -0
  29. data/generators/acts_as_approvable/templates/views/haml/index.html.haml +13 -0
  30. data/init.rb +1 -0
  31. data/lib/acts-as-approvable/version.rb +3 -0
  32. data/lib/acts_as_approvable/acts_as_approvable.rb +291 -0
  33. data/lib/acts_as_approvable/approval.rb +179 -0
  34. data/lib/acts_as_approvable/error.rb +31 -0
  35. data/lib/acts_as_approvable/ownership.rb +117 -0
  36. data/lib/acts_as_approvable/railtie.rb +7 -0
  37. data/lib/acts_as_approvable.rb +66 -0
  38. data/lib/generators/acts_as_approvable/USAGE +1 -0
  39. data/lib/generators/acts_as_approvable/acts_as_approvable_generator.rb +73 -0
  40. data/lib/generators/acts_as_approvable/templates/approvals_controller.rb +97 -0
  41. data/lib/generators/acts_as_approvable/templates/create_approvals.rb +26 -0
  42. data/lib/generators/acts_as_approvable.rb +0 -0
  43. data/lib/generators/erb/acts_as_approvable_generator.rb +44 -0
  44. data/lib/generators/erb/templates/_owner_select.html.erb +4 -0
  45. data/lib/generators/erb/templates/_table.html.erb +26 -0
  46. data/lib/generators/erb/templates/index.html.erb +15 -0
  47. data/lib/generators/haml/acts_as_approvable_generator.rb +44 -0
  48. data/lib/generators/haml/templates/_owner_select.html.haml +3 -0
  49. data/lib/generators/haml/templates/_table.html.haml +19 -0
  50. data/lib/generators/haml/templates/index.html.haml +13 -0
  51. data/lib/tasks/acts_as_approvable.rake +4 -0
  52. data/rails/init.rb +1 -0
  53. data/test/acts_as_approvable_model_test.rb +428 -0
  54. data/test/acts_as_approvable_ownership_test.rb +132 -0
  55. data/test/acts_as_approvable_schema_test.rb +13 -0
  56. data/test/acts_as_approvable_test.rb +8 -0
  57. data/test/database.yml +7 -0
  58. data/test/schema.rb +44 -0
  59. data/test/support.rb +19 -0
  60. data/test/test_helper.rb +60 -0
  61. metadata +225 -0
@@ -0,0 +1,428 @@
1
+ require 'test_helper'
2
+
3
+ class ActsAsApprovableModelTest < Test::Unit::TestCase
4
+ load_schema
5
+
6
+ def teardown
7
+ truncate
8
+ end
9
+
10
+ context 'A record with update only approval' do
11
+ context 'and ignored fields' do
12
+ setup { @project = Project.create }
13
+
14
+ should 'have no approvals' do
15
+ assert @project.approvals.empty?
16
+ end
17
+
18
+ context 'which updates an ignored column' do
19
+ setup { @project.update_attribute(:title, 'Ignore Me') }
20
+
21
+ should 'not have an approval' do
22
+ assert @project.approvals.empty?
23
+ end
24
+ end
25
+
26
+ context 'which updates an ignore column and a non-ignored column' do
27
+ setup { @project.update_attributes(:title => 'Ignore Me', :description => 'Must Review') }
28
+
29
+ should 'have one approval' do
30
+ assert_equal 1, @project.approvals.size
31
+ end
32
+
33
+ should 'not update the records ignored column' do
34
+ assert_equal nil, @project.description
35
+ end
36
+
37
+ should 'update the records non-ignored columns' do
38
+ assert_equal 'Ignore Me', @project.title
39
+ end
40
+
41
+ should 'have the description on the approval' do
42
+ assert @project.approvals.last.object.key?('description')
43
+ assert_equal 'Must Review', @project.approvals.last.object['description']
44
+ end
45
+ end
46
+
47
+ context 'that is altered using #without_approval' do
48
+ should 'not have an approval object' do
49
+ @project.without_approval { |i| i.update_attribute(:description, 'updated') }
50
+ assert @project.approvals.empty?
51
+ end
52
+
53
+ should 'correctly restore approval queue state' do
54
+ assert @project.approvals_on?
55
+ @project.approvals_off
56
+ assert !@project.approvals_on?
57
+ @project.without_approval { |i| i.update_attribute(:description, 'updated') }
58
+ assert !@project.approvals_on?
59
+ end
60
+ end
61
+ end
62
+
63
+ context 'and "only" fields' do
64
+ setup { @game = Game.create }
65
+
66
+ should 'have no approvals' do
67
+ assert @game.approvals.empty?
68
+ end
69
+
70
+ context 'which updates an only column' do
71
+ setup { @game.update_attribute(:title, 'review') }
72
+
73
+ should 'have an approval' do
74
+ assert_equal 1, @game.approvals.size
75
+ end
76
+
77
+ should 'have pending changes' do
78
+ assert @game.pending_changes?
79
+ end
80
+ end
81
+
82
+ context 'which updates an only column and another column' do
83
+ setup { @game.update_attributes(:title => 'review', :description => 'no review') }
84
+
85
+ should 'have one approval' do
86
+ assert_equal 1, @game.approvals.size
87
+ end
88
+
89
+ should 'not update the records only column' do
90
+ assert_equal nil, @game.title
91
+ end
92
+
93
+ should 'update the records other fields' do
94
+ assert_equal 'no review', @game.description
95
+ end
96
+
97
+ should 'have the title on the approval' do
98
+ assert @game.approvals.last.object.key?('title')
99
+ assert_equal 'review', @game.approvals.last.object['title']
100
+ end
101
+ end
102
+
103
+ context 'that is altered using #without_approval' do
104
+ setup { @game.without_approval { |i| i.update_attribute(:title, 'updated') } }
105
+
106
+ should 'not have an approval object' do
107
+ assert @game.approvals.empty?
108
+ end
109
+ end
110
+
111
+ context 'with approval queue disabled' do
112
+ context 'at the record level' do
113
+ setup do
114
+ @game.approvals_off
115
+ @game.update_attributes(:title => 'review')
116
+ end
117
+
118
+ teardown { @game.approvals_on }
119
+
120
+ should 'have approvals off' do
121
+ assert @game.approvals_disabled?
122
+ end
123
+
124
+ should 'not have an approval object' do
125
+ assert @game.approvals.empty?
126
+ end
127
+ end
128
+
129
+ context 'at the model level' do
130
+ setup do
131
+ Game.approvals_off
132
+ @game.update_attributes(:title => 'review')
133
+ end
134
+
135
+ teardown { Game.approvals_on }
136
+
137
+ should 'have approvals off' do
138
+ assert @game.approvals_disabled?
139
+ end
140
+
141
+ should 'not have an approval object' do
142
+ assert @game.approvals.empty?
143
+ end
144
+ end
145
+
146
+ context 'at the global level' do
147
+ setup do
148
+ ActsAsApprovable.disable
149
+ @game.update_attributes(:title => 'review')
150
+ end
151
+
152
+ teardown { ActsAsApprovable.enable }
153
+
154
+ should 'have approvals off' do
155
+ assert @game.approvals_disabled?
156
+ end
157
+
158
+ should 'not have an approval object' do
159
+ assert @game.approvals.empty?
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ context 'An approval record' do
167
+ setup do
168
+ @project = Project.create
169
+ @project.update_attribute(:description, 'review')
170
+ @approval = @project.approvals.last
171
+ end
172
+
173
+ should 'not be locked by default' do
174
+ assert @approval.unlocked?
175
+ assert !@approval.locked?
176
+ end
177
+
178
+ should 'be pending' do
179
+ assert @approval.pending?
180
+ assert !@approval.approved?
181
+ assert !@approval.rejected?
182
+ end
183
+
184
+ should 'check attributes in object' do
185
+ @approval.object['foo'] = 'bar'
186
+ @approval.approve!
187
+ assert_equal @approval.object['description'], @approval.item.description
188
+ end
189
+
190
+ context 'that is accepted' do
191
+ setup { @approval.approve! }
192
+
193
+ should 'be locked' do
194
+ assert @approval.locked?
195
+ assert !@approval.unlocked?
196
+ end
197
+
198
+ should 'be approved' do
199
+ assert @approval.approved?
200
+ assert !@approval.rejected?
201
+ assert !@approval.pending?
202
+ end
203
+
204
+ should 'update the target item' do
205
+ assert_equal @approval.object['description'], @approval.item.description
206
+ end
207
+
208
+ should 'raise an error if approved again' do
209
+ assert_raise(ActsAsApprovable::Error::Locked) { @approval.approve! }
210
+ end
211
+
212
+ should 'raise an error if rejected' do
213
+ assert_raise(ActsAsApprovable::Error::Locked) { @approval.reject! }
214
+ end
215
+ end
216
+
217
+ context 'that is rejected' do
218
+ setup { @approval.reject! }
219
+
220
+ should 'be locked' do
221
+ assert @approval.locked?
222
+ assert !@approval.unlocked?
223
+ end
224
+
225
+ should 'be rejected' do
226
+ assert @approval.rejected?
227
+ assert !@approval.approved?
228
+ assert !@approval.pending?
229
+ end
230
+
231
+ should 'not update the target item' do
232
+ assert_equal nil, @approval.item.description
233
+ end
234
+
235
+ should 'raise an error if approved' do
236
+ assert_raise(ActsAsApprovable::Error::Locked) { @approval.approve! }
237
+ end
238
+
239
+ should 'raise an error if rejected again' do
240
+ assert_raise(ActsAsApprovable::Error::Locked) { @approval.reject! }
241
+ end
242
+ end
243
+
244
+ context 'that is stale' do
245
+ setup { @approval.update_attributes(:created_at => 10.days.ago) }
246
+
247
+ should 'be stale' do
248
+ assert @approval.stale?
249
+ assert !@approval.fresh?
250
+ end
251
+
252
+ should 'raise an error if approved' do
253
+ assert_raise(ActsAsApprovable::Error::Stale) { @approval.approve! }
254
+ assert @approval.pending?
255
+ end
256
+
257
+ should 'not raise an error if rejected' do
258
+ assert_nothing_raised { @approval.reject! }
259
+ assert @approval.rejected?
260
+ end
261
+
262
+ should 'allow approval when forced' do
263
+ assert_nothing_raised { @approval.approve!(true) }
264
+ assert @approval.approved?
265
+ end
266
+ end
267
+ end
268
+
269
+ context 'A record with create only approval' do
270
+ setup { @user = User.create }
271
+
272
+ should 'be pending by default' do
273
+ assert @user.pending?
274
+ assert !@user.approved?
275
+ assert !@user.rejected?
276
+ end
277
+
278
+ should 'have an approval object' do
279
+ assert_equal 1, @user.approvals.size
280
+ end
281
+
282
+ should 'set the local state' do
283
+ assert_equal 'pending', @user.state
284
+ end
285
+
286
+ context 'when approved' do
287
+ setup do
288
+ @user.approve!
289
+ @user.reload
290
+ end
291
+
292
+ should 'be approved' do
293
+ assert @user.approved?
294
+ assert !@user.rejected?
295
+ assert !@user.pending?
296
+ end
297
+
298
+ should 'update the local state' do
299
+ assert_equal 'approved', @user.state
300
+ end
301
+ end
302
+
303
+ context 'when rejected' do
304
+ setup do
305
+ @user.reject!
306
+ @user.reload
307
+ end
308
+
309
+ should 'be rejected' do
310
+ assert @user.rejected?
311
+ assert !@user.approved?
312
+ assert !@user.pending?
313
+ end
314
+
315
+ should 'update the local state' do
316
+ assert_equal 'rejected', @user.state
317
+ end
318
+ end
319
+
320
+ context '.without_approval' do
321
+ should 'disable approvals for the given block' do
322
+ @user = User.without_approval { |m| m.create }
323
+ assert @user.approval.nil?
324
+ end
325
+ end
326
+ end
327
+
328
+ context 'A record with default settings' do
329
+ setup { @employee = Employee.create }
330
+
331
+ should 'be pending by default' do
332
+ assert @employee.pending?
333
+ assert !@employee.approved?
334
+ assert !@employee.rejected?
335
+ end
336
+
337
+ should 'get the state from the approval record' do
338
+ assert_equal @employee.approval_state, @employee.approval.state
339
+ end
340
+
341
+ context 'when updated' do
342
+ setup { @employee.update_attributes(:name => 'John Doe') }
343
+
344
+ should 'not update the attribute' do
345
+ assert_equal nil, @employee.name
346
+ end
347
+
348
+ should 'create an approval record' do
349
+ assert_equal 1, @employee.update_approvals.size
350
+ end
351
+ end
352
+
353
+ context 'when approving' do
354
+ setup { @approval = @employee.approval }
355
+
356
+ should 'call before_approve hook' do
357
+ @approval.item.expects(:before_approve).once
358
+ @approval.approve!
359
+ end
360
+
361
+ should 'call after_approve hook' do
362
+ @approval.item.expects(:after_approve).once
363
+ @approval.approve!
364
+ end
365
+
366
+ should 'halt if before_approve returns false' do
367
+ @approval.item.stubs(:before_approve).returns(false)
368
+ @approval.approve!
369
+ assert @approval.item.pending?
370
+ end
371
+ end
372
+
373
+ context 'when rejecting' do
374
+ setup { @approval = @employee.approval }
375
+
376
+ should 'call before_reject hook' do
377
+ @approval.item.expects(:before_reject).once
378
+ @approval.reject!
379
+ end
380
+
381
+ should 'call after_reject hook' do
382
+ @approval.item.expects(:after_reject).once
383
+ @approval.reject!
384
+ end
385
+
386
+ should 'halt if before_reject returns false' do
387
+ @approval.item.stubs(:before_reject).returns(false)
388
+ @approval.reject!
389
+ assert @approval.item.pending?
390
+ end
391
+ end
392
+ end
393
+
394
+ context '.options_for_state' do
395
+ should 'return an array' do
396
+ assert_kind_of Array, Approval.options_for_state
397
+ end
398
+
399
+ should 'contain our states' do
400
+ assert Approval.options_for_state.include?(['All', -1])
401
+ assert Approval.options_for_state.include?(['Pending', 0])
402
+ assert Approval.options_for_state.include?(['Approved', 1])
403
+ assert Approval.options_for_state.include?(['Rejected', 2])
404
+ end
405
+ end
406
+
407
+ context '.options_for_type' do
408
+ context 'without approval records' do
409
+ should 'be empty' do
410
+ assert Approval.options_for_type.empty?
411
+ end
412
+ end
413
+
414
+ context 'with approval records' do
415
+ setup do
416
+ Project.create.update_attributes(:description => 'review')
417
+ Game.create.update_attributes(:title => 'review')
418
+ User.create
419
+ end
420
+
421
+ should 'contain all types with approvals' do
422
+ assert Approval.options_for_type.include?('Project')
423
+ assert Approval.options_for_type.include?('Game')
424
+ assert Approval.options_for_type.include?('User')
425
+ end
426
+ end
427
+ end
428
+ end
@@ -0,0 +1,132 @@
1
+ require 'test_helper'
2
+
3
+ class ActsAsApprovableOwnershipTest < Test::Unit::TestCase
4
+ load_schema
5
+
6
+ def teardown
7
+ truncate
8
+ end
9
+
10
+ context 'with default configuration' do
11
+ setup do
12
+ # Reset from test below
13
+ ActsAsApprovable::Ownership.configure do
14
+ def self.available_owners
15
+ owner_class.all
16
+ end
17
+ end
18
+ end
19
+
20
+ should 'respond to .owner_class' do
21
+ assert_respond_to Approval, :owner_class
22
+ end
23
+
24
+ should 'respond to .available_owners' do
25
+ assert_respond_to Approval, :available_owners
26
+ end
27
+
28
+ should 'respond to .options_for_available_owners' do
29
+ assert_respond_to Approval, :options_for_available_owners
30
+ end
31
+
32
+ should 'respond to .assigned_owners' do
33
+ assert_respond_to Approval, :assigned_owners
34
+ end
35
+
36
+ should 'respond to .options_for_assigned_owners' do
37
+ assert_respond_to Approval, :options_for_assigned_owners
38
+ end
39
+
40
+ context 'and some objects' do
41
+ setup do
42
+ @employee = Employee.create
43
+ @approval = @employee.approval
44
+
45
+ @user1, @user2 = User.without_approval { |m| [m.create, m.create] }
46
+ end
47
+
48
+ context '#assign' do
49
+ should 'raise an error when the record is not assignable' do
50
+ assert_raise(ActsAsApprovable::Error::InvalidOwner) { @approval.assign(@employee) }
51
+ end
52
+
53
+ should 'allow assignment of a valid record' do
54
+ assert_nothing_raised { @approval.assign(@user1) }
55
+ assert_equal @approval.owner, @user1
56
+ end
57
+ end
58
+
59
+ context '#unassign' do
60
+ setup do
61
+ @approval.owner = @user1
62
+ @approval.unassign
63
+ end
64
+
65
+ should 'nullify the owner' do
66
+ assert @approval.owner.nil?
67
+ end
68
+ end
69
+
70
+ context '.available_owners' do
71
+ should 'contain all available owners' do
72
+ assert_equal [@user1, @user2], Approval.available_owners
73
+ end
74
+ end
75
+
76
+ context '.options_for_available_owners' do
77
+ should 'format all available owners for a #options_for_select' do
78
+ assert_equal Approval.options_for_available_owners, [
79
+ [@user1.to_str, @user1.id],
80
+ [@user2.to_str, @user2.id]
81
+ ]
82
+ end
83
+
84
+ should 'insert a prompt if requested' do
85
+ assert_equal ['(none)', nil], Approval.options_for_available_owners(true).first
86
+ end
87
+ end
88
+
89
+ context 'with some assigned owners' do
90
+ setup do
91
+ @user3 = User.without_approval { |m| m.create }
92
+
93
+ @approval.assign(@user1)
94
+ Employee.create.approval.assign(@user3)
95
+ end
96
+
97
+ context '.assigned_owners' do
98
+ should 'contain all assigned owners' do
99
+ assert_equal [@user1, @user3], Approval.assigned_owners
100
+ end
101
+ end
102
+
103
+ context '.options_for_assigned_owners' do
104
+ should 'format all assigned owners for #options_for_select' do
105
+ assert_equal Approval.options_for_assigned_owners, [
106
+ [@user1.to_str, @user1.id],
107
+ [@user3.to_str, @user3.id]
108
+ ]
109
+ end
110
+
111
+ should 'insert a prompt if requested' do
112
+ assert_equal ['All Users', nil], Approval.options_for_assigned_owners(true).first
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ context 'given a block' do
120
+ setup do
121
+ ActsAsApprovable::Ownership.configure do
122
+ def self.available_owners
123
+ [1, 2, 3]
124
+ end
125
+ end
126
+ end
127
+
128
+ should 'allow overriding methods' do
129
+ assert_equal [1, 2, 3], Approval.available_owners
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,13 @@
1
+ require 'test_helper'
2
+
3
+ class ActsAsApprovableSchemaTest < Test::Unit::TestCase
4
+ def setup
5
+ load_schema
6
+ end
7
+
8
+ def test_schema_has_loaded_correctly
9
+ assert_equal [], User.all
10
+ assert_equal [], Project.all
11
+ assert_equal [], Approval.all
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ require 'test_helper'
2
+
3
+ class ActsAsApprovableTest < Test::Unit::TestCase
4
+ should 'set VERSION contanst to file contents' do
5
+ contents = File.read(File.expand_path(File.join(File.dirname(__FILE__), '..', 'VERSION'))).chomp
6
+ assert_equal ActsAsApprovable::VERSION, contents
7
+ end
8
+ end
data/test/database.yml ADDED
@@ -0,0 +1,7 @@
1
+ sqlite:
2
+ :adapter: sqlite
3
+ :database: test/acts_as_approvable_plugin.sqlite.db
4
+
5
+ sqlite3:
6
+ :adapter: sqlite3
7
+ :database: test/acts_as_approvable_plugin.sqlite3.db
data/test/schema.rb ADDED
@@ -0,0 +1,44 @@
1
+ ActiveRecord::Schema.define(:version => 0) do
2
+ create_table :users, :force => true do |t|
3
+ t.string :login
4
+ t.string :state
5
+
6
+ t.timestamps
7
+ end
8
+
9
+ create_table :projects, :force => true do |t|
10
+ t.string :title
11
+ t.text :description
12
+
13
+ t.timestamps
14
+ end
15
+
16
+ create_table :games, :force => true do |t|
17
+ t.string :title
18
+ t.text :description
19
+
20
+ t.timestamps
21
+ end
22
+
23
+ create_table :employees, :force => true do |t|
24
+ t.string :name
25
+
26
+ t.timestamps
27
+ end
28
+
29
+ create_table :approvals, :force => true do |t|
30
+ t.string :item_type, :null => false
31
+ t.integer :item_id, :null => false
32
+ t.string :event, :null => false
33
+ t.integer :state, :null => false, :default => 0
34
+ t.integer :owner_id
35
+ t.text :object
36
+ t.text :reason
37
+
38
+ t.timestamps
39
+ end
40
+
41
+ add_index :approvals, [:state, :event]
42
+ add_index :approvals, [:item_type, :item_id]
43
+ add_index :approvals, [:owner_id]
44
+ end
data/test/support.rb ADDED
@@ -0,0 +1,19 @@
1
+ class User < ActiveRecord::Base
2
+ acts_as_approvable :on => :create, :state_field => :state
3
+
4
+ def to_str
5
+ login || "user-#{id}"
6
+ end
7
+ end
8
+
9
+ class Project < ActiveRecord::Base
10
+ acts_as_approvable :on => :update, :ignore => :title
11
+ end
12
+
13
+ class Game < ActiveRecord::Base
14
+ acts_as_approvable :on => :update, :only => :title
15
+ end
16
+
17
+ class Employee < ActiveRecord::Base
18
+ acts_as_approvable
19
+ end