acts-as-approvable 0.6.1

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