acts-as-approvable 0.6.7 → 0.6.8.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 (64) hide show
  1. data/.gitignore +2 -5
  2. data/.rspec +2 -0
  3. data/.travis.yml +5 -0
  4. data/Appraisals +14 -10
  5. data/CHANGELOG +16 -0
  6. data/Gemfile.lock +60 -20
  7. data/README.md +1 -2
  8. data/Rakefile +44 -16
  9. data/VERSION +1 -1
  10. data/acts-as-approvable.gemspec +21 -11
  11. data/features/create_approval.feature +26 -0
  12. data/features/step_definitions/cucumber_steps.rb +86 -0
  13. data/features/support/env.rb +14 -0
  14. data/features/support/large.txt +29943 -0
  15. data/features/support/second_large.txt +31798 -0
  16. data/features/update_approval.feature +34 -0
  17. data/gemfiles/Gemfile.ci +14 -0
  18. data/gemfiles/Gemfile.ci.lock +98 -0
  19. data/gemfiles/mysql2.gemfile +7 -0
  20. data/gemfiles/mysql2.gemfile.lock +92 -0
  21. data/gemfiles/rails2.gemfile +1 -0
  22. data/gemfiles/rails2.gemfile.lock +59 -17
  23. data/gemfiles/rails30.gemfile +1 -0
  24. data/gemfiles/rails30.gemfile.lock +54 -14
  25. data/gemfiles/rails31.gemfile +1 -0
  26. data/gemfiles/rails31.gemfile.lock +54 -14
  27. data/gemfiles/sqlite.gemfile +7 -0
  28. data/generators/acts_as_approvable/templates/create_approvals.rb +8 -7
  29. data/lib/acts-as-approvable.rb +2 -3
  30. data/lib/acts_as_approvable/approval.rb +3 -2
  31. data/lib/acts_as_approvable/model.rb +55 -0
  32. data/lib/acts_as_approvable/model/class_methods.rb +63 -0
  33. data/lib/acts_as_approvable/model/create_instance_methods.rb +83 -0
  34. data/lib/acts_as_approvable/model/instance_methods.rb +89 -0
  35. data/lib/acts_as_approvable/model/update_instance_methods.rb +61 -0
  36. data/lib/acts_as_approvable/railtie.rb +1 -1
  37. data/lib/generators/acts_as_approvable/templates/create_approvals.rb +8 -7
  38. data/spec/acts_as_approvable/approval_spec.rb +475 -0
  39. data/spec/acts_as_approvable/model/class_methods_spec.rb +219 -0
  40. data/spec/acts_as_approvable/model/create_instance_methods_spec.rb +149 -0
  41. data/spec/acts_as_approvable/model/instance_methods_spec.rb +328 -0
  42. data/spec/acts_as_approvable/model/update_instance_methods_spec.rb +111 -0
  43. data/spec/acts_as_approvable/model_spec.rb +90 -0
  44. data/spec/acts_as_approvable/ownership/class_methods_spec.rb +101 -0
  45. data/spec/acts_as_approvable/ownership/instance_methods_spec.rb +32 -0
  46. data/spec/acts_as_approvable/ownership_spec.rb +51 -0
  47. data/spec/acts_as_approvable_spec.rb +29 -0
  48. data/spec/spec_helper.rb +51 -0
  49. data/spec/support/database.rb +46 -0
  50. data/spec/support/database.yml +12 -0
  51. data/spec/support/matchers.rb +87 -0
  52. data/spec/support/models.rb +60 -0
  53. data/{test → spec/support}/schema.rb +15 -9
  54. metadata +156 -53
  55. data/gemfiles/rails32.gemfile +0 -8
  56. data/gemfiles/rails32.gemfile.lock +0 -99
  57. data/lib/acts_as_approvable/acts_as_approvable.rb +0 -291
  58. data/test/acts_as_approvable_model_test.rb +0 -459
  59. data/test/acts_as_approvable_ownership_test.rb +0 -132
  60. data/test/acts_as_approvable_schema_test.rb +0 -13
  61. data/test/acts_as_approvable_test.rb +0 -8
  62. data/test/database.yml +0 -7
  63. data/test/support.rb +0 -19
  64. data/test/test_helper.rb +0 -62
@@ -0,0 +1,61 @@
1
+ module ActsAsApprovable
2
+ module Model
3
+ ##
4
+ # Instance methods that apply to the :update event specifically.
5
+ module UpdateInstanceMethods
6
+ ##
7
+ # Retrieve all approval records for `:update` events.
8
+ def update_approvals(all = true)
9
+ all ? approvals.find_all_by_event('update') : approvals.find_all_by_event_and_state('update', 0)
10
+ end
11
+
12
+ ##
13
+ # Returns true if the record has any `#update_approvals` that are pending
14
+ # approval.
15
+ def pending_changes?
16
+ !update_approvals(false).empty?
17
+ end
18
+
19
+ ##
20
+ # Returns true if any notable (eg. not ignored) fields have been changed.
21
+ def changed_notably?
22
+ notably_changed.any?
23
+ end
24
+
25
+ ##
26
+ # Returns an array of any notable (eg. not ignored) fields that have not
27
+ # been changed.
28
+ #
29
+ # @return [Array] a list of changed fields.
30
+ def notably_changed
31
+ approvable_fields.select { |field| changed.include?(field) }
32
+ end
33
+
34
+ ##
35
+ # Returns a list of fields that require approval.
36
+ def approvable_fields
37
+ self.class.approvable_fields
38
+ end
39
+
40
+ private
41
+ def approvable_update?
42
+ approvals_enabled? and approvable_on?(:update) and changed_notably?
43
+ end
44
+
45
+ def approvable_update
46
+ changed = {}
47
+ originals = {}
48
+
49
+ notably_changed.each do |attr|
50
+ original, changed_to = changes[attr]
51
+
52
+ write_attribute(attr.to_s, original)
53
+ changed[attr] = changed_to
54
+ originals[attr] = original
55
+ end
56
+
57
+ @approval = approvals.build(:event => 'update', :state => 'pending', :object => changed, :original => originals)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -1,7 +1,7 @@
1
1
  module ActsAsApprovable
2
2
  class Railtie < Rails::Railtie
3
3
  initializer 'acts_as_approvable.configure_rails_initialization' do |app|
4
- ActiveRecord::Base.send :include, ActsAsApprovable::Model
4
+ ActiveRecord::Base.send :extend, ActsAsApprovable::Model
5
5
  end
6
6
  end
7
7
  end
@@ -1,13 +1,14 @@
1
1
  class CreateApprovals < ActiveRecord::Migration
2
2
  def self.up
3
3
  create_table :approvals do |t|
4
- t.string :item_type, :null => false
5
- t.integer :item_id, :null => false
6
- t.string :event, :null => false
7
- t.integer :state, :null => false, :default => 0
8
- <% if options[:owner] %> t.integer :owner_id
9
- <% end %> t.text :object
10
- t.text :reason
4
+ t.string :item_type, :null => false
5
+ t.integer :item_id, :null => false
6
+ t.string :event, :null => false
7
+ t.integer :state, :null => false, :default => 0
8
+ <% if options[:owner] %> t.integer :owner_id
9
+ <% end %> t.text :object, :limit => 16777216
10
+ t.text :original, :limit => 16777216
11
+ t.text :reason
11
12
 
12
13
  t.timestamps
13
14
  end
@@ -0,0 +1,475 @@
1
+ require 'spec_helper'
2
+
3
+ describe Approval do
4
+ before(:each) do
5
+ subject.stub(:save! => true, :save => true)
6
+ end
7
+
8
+ it 'should serialize :object' do
9
+ described_class.serialized_attributes.keys.should include('object')
10
+ end
11
+
12
+ describe '.associations' do
13
+ it { should belong_to(:item) }
14
+ end
15
+
16
+ describe '.validates' do
17
+ it { should validate_presence_of(:item) }
18
+ it { should validate_inclusion_of(:event).in(%w(create update)) }
19
+ it { should validate_numericality_of(:state) }
20
+ it { should ensure_inclusion_of(:state).in_range(0..(described_class::STATES.length - 1)).with_low_message(/greater than/).with_high_message(/less than/)}
21
+ end
22
+
23
+ describe '.enumerate_state' do
24
+ it 'enumerates "pending" to 0' do
25
+ described_class.enumerate_state('pending').should be(0)
26
+ end
27
+
28
+ it 'enumerates "approved" to 1' do
29
+ described_class.enumerate_state('approved').should be(1)
30
+ end
31
+
32
+ it 'enumerates "rejected" to 2' do
33
+ described_class.enumerate_state('rejected').should be(2)
34
+ end
35
+
36
+ it 'enumerates other values to nil' do
37
+ described_class.enumerate_state('not_a_state').should_not be
38
+ end
39
+ end
40
+
41
+ describe '.enumerate_states' do
42
+ it 'enumerates many states at once' do
43
+ described_class.enumerate_states('pending', 'approved', 'rejected').should == [0, 1, 2]
44
+ end
45
+
46
+ it 'ignores states it does not know' do
47
+ described_class.enumerate_states('pending', 'not_a_state', 'rejected').should == [0, 2]
48
+ end
49
+ end
50
+
51
+ describe '.options_for_state' do
52
+ it 'returns an array usable by #options_for_select' do
53
+ described_class.options_for_state.should be_an_options_array
54
+ end
55
+
56
+ it 'includes an "all" option' do
57
+ described_class.options_for_state.should include(['All', -1])
58
+ end
59
+
60
+ it 'includes the pending state' do
61
+ described_class.options_for_state.should include(['Pending', 0])
62
+ end
63
+
64
+ it 'includes the approved state' do
65
+ described_class.options_for_state.should include(['Approved', 1])
66
+ end
67
+
68
+ it 'includes the rejected state' do
69
+ described_class.options_for_state.should include(['Rejected', 2])
70
+ end
71
+ end
72
+
73
+ describe '.options_for_type' do
74
+ before(:each) do
75
+ @default = DefaultApprovable.create
76
+ @creates = CreatesApprovable.create
77
+ @updates = UpdatesApprovable.create
78
+ end
79
+
80
+ it 'returns an array usable by #options_for_select' do
81
+ described_class.options_for_type.should be_an_options_array
82
+ end
83
+
84
+ it 'includes all types with approvals' do
85
+ described_class.options_for_type.should include('DefaultApprovable')
86
+ described_class.options_for_type.should include('CreatesApprovable')
87
+ end
88
+
89
+ it 'does not include types without approvals' do
90
+ described_class.options_for_type.should_not include('UpdatesApprovable')
91
+ end
92
+
93
+ it 'includes a prompt if requested' do
94
+ described_class.options_for_type(true).should include(['All Types', nil])
95
+ end
96
+
97
+ it 'does not includes a prompt by default' do
98
+ described_class.options_for_type.should_not include(['All Types', nil])
99
+ end
100
+ end
101
+
102
+ describe '#state' do
103
+ it 'returns the state as a string' do
104
+ subject.state.should be_a(String)
105
+ end
106
+
107
+ it 'attempts to read the state attribute' do
108
+ subject.should_receive(:read_attribute).with(:state)
109
+ subject.state
110
+ end
111
+ end
112
+
113
+ describe '#state_was' do
114
+ it 'returns the state as a string' do
115
+ subject.state_was.should be_a(String)
116
+ end
117
+
118
+ it 'attempts to read the changed state attribute' do
119
+ subject.should_receive(:changed_attributes).and_return({:state => 1})
120
+ subject.state_was.should == 'approved'
121
+ end
122
+ end
123
+
124
+ describe '#state=' do
125
+ it 'writes the attribute value' do
126
+ subject.should_receive(:write_attribute)
127
+ subject.send(:state=, 'pending')
128
+ end
129
+
130
+ it 'enumerates the given state' do
131
+ subject.should_receive(:write_attribute).with(:state, 0)
132
+ subject.send(:state=, 'pending')
133
+ end
134
+
135
+ it 'skips enumeration for numeric values' do
136
+ subject.should_receive(:write_attribute).with(:state, 10)
137
+ subject.send(:state=, 10)
138
+ end
139
+ end
140
+
141
+ context 'when the state is pending' do
142
+ before(:each) do
143
+ subject.stub(:state => 'pending')
144
+ end
145
+
146
+ it { should be_pending }
147
+ it { should_not be_approved }
148
+ it { should_not be_rejected }
149
+ it { should_not be_locked }
150
+ it { should be_unlocked }
151
+ end
152
+
153
+ context 'when the state is approved' do
154
+ before(:each) do
155
+ subject.stub(:state => 'approved')
156
+ end
157
+
158
+ it { should_not be_pending }
159
+ it { should be_approved }
160
+ it { should_not be_rejected }
161
+ it { should be_locked }
162
+ it { should_not be_unlocked }
163
+ end
164
+
165
+ context 'when the state is rejected' do
166
+ before(:each) do
167
+ subject.stub(:state => 'rejected')
168
+ end
169
+
170
+ it { should_not be_pending }
171
+ it { should_not be_approved }
172
+ it { should be_rejected }
173
+ it { should be_locked }
174
+ it { should_not be_unlocked }
175
+ end
176
+
177
+ context 'when the event is :update' do
178
+ before(:each) do
179
+ subject.stub(:event => 'update')
180
+ end
181
+
182
+ it { should be_update }
183
+ it { should_not be_create }
184
+ end
185
+
186
+ context 'when the event is :create' do
187
+ before(:each) do
188
+ subject.stub(:event => 'create')
189
+ end
190
+
191
+ it { should_not be_update }
192
+ it { should be_create }
193
+ end
194
+
195
+ context 'when the approval is unlocked' do
196
+ before(:each) do
197
+ @item = DefaultApprovable.without_approval { |m| m.create }
198
+ subject.stub(:locked? => false, :created_at => Time.now, :item => @item)
199
+ @item.stub(:updated_at => Time.now)
200
+ end
201
+
202
+ describe '#able_to_save?' do
203
+ it { should be_able_to_save }
204
+
205
+ it 'does not check the what the state was' do
206
+ subject.should_not_receive(:state_was)
207
+ subject.able_to_save?
208
+ end
209
+ end
210
+
211
+ describe '#stale?' do
212
+ it 'checks when the item was changed' do
213
+ @item.should_receive(:has_attribute?).with(:updated_at).and_return(true)
214
+ subject.stale?
215
+ end
216
+ end
217
+
218
+ describe '#fresh?' do
219
+ it 'checks when the item was changed' do
220
+ @item.should_receive(:has_attribute?).with(:updated_at).and_return(true)
221
+ subject.fresh?
222
+ end
223
+ end
224
+
225
+ context 'when the approval is newer than the last update' do
226
+ before(:each) do
227
+ subject.stub(:created_at => @item.updated_at + 60)
228
+ end
229
+
230
+ it { should_not be_stale }
231
+ it { should be_fresh }
232
+ end
233
+
234
+ context 'when the approval is older than the last update' do
235
+ before(:each) do
236
+ subject.stub(:created_at => @item.updated_at - 60)
237
+ end
238
+
239
+ it { should be_stale }
240
+ it { should_not be_fresh }
241
+ end
242
+ end
243
+
244
+ context 'when the approval is locked' do
245
+ before(:each) do
246
+ subject.stub(:locked? => true)
247
+ end
248
+
249
+ it { should_not be_stale }
250
+ it { should be_fresh }
251
+
252
+ describe '#able_to_save?' do
253
+ it 'checks the what the state was' do
254
+ subject.should_receive(:state_was)
255
+ subject.able_to_save?
256
+ end
257
+ end
258
+
259
+ describe '#stale?' do
260
+ it 'does not check when the item was changed' do
261
+ subject.should_not_receive(:item)
262
+ subject.stale?
263
+ end
264
+ end
265
+
266
+ describe '#fresh?' do
267
+ it 'does not check when the item was changed' do
268
+ subject.should_not_receive(:item)
269
+ subject.fresh?
270
+ end
271
+ end
272
+
273
+ describe '#approve!' do
274
+ it 'raises a Locked exception' do
275
+ expect { subject.approve! }.to raise_error(ActsAsApprovable::Error::Locked)
276
+ end
277
+
278
+ it 'leaves the approval in a pending state' do
279
+ begin; subject.approve!; rescue ActsAsApprovable::Error::Locked; end
280
+ subject.should be_pending
281
+ end
282
+ end
283
+
284
+ describe '#reject!' do
285
+ it 'raises a Locked exception' do
286
+ expect { subject.reject! }.to raise_error(ActsAsApprovable::Error::Locked)
287
+ end
288
+
289
+ it 'leaves the approval in a pending state' do
290
+ begin; subject.reject!; rescue ActsAsApprovable::Error::Locked; end
291
+ subject.should be_pending
292
+ end
293
+ end
294
+
295
+ context 'and the state is pending' do
296
+ before(:each) do
297
+ subject.stub(:state_was => 'pending')
298
+ end
299
+
300
+ it { should be_able_to_save }
301
+ end
302
+
303
+ context 'and the state is approved' do
304
+ before(:each) do
305
+ subject.stub(:state_was => 'approved')
306
+ end
307
+
308
+ it { should_not be_able_to_save }
309
+ end
310
+
311
+ context 'and the state is rejected' do
312
+ before(:each) do
313
+ subject.stub(:state_was => 'rejected')
314
+ end
315
+
316
+ it { should_not be_able_to_save }
317
+ end
318
+ end
319
+
320
+ context 'when the approval is stale' do
321
+ before(:each) do
322
+ @item = DefaultApprovable.without_approval { |m| m.create }
323
+ subject.stub(:stale? => true, :item => @item)
324
+ end
325
+
326
+ it { should be_stale }
327
+ it { should_not be_fresh }
328
+
329
+ describe '#approve!' do
330
+ it 'raises a Stale exception' do
331
+ expect { subject.approve! }.to raise_error(ActsAsApprovable::Error::Stale)
332
+ end
333
+
334
+ it 'leaves the approval in a pending state' do
335
+ begin; subject.approve!; rescue ActsAsApprovable::Error::Stale; end
336
+ subject.should be_pending
337
+ end
338
+
339
+ context 'when approval is forced' do
340
+ it 'does not raise a Stale exception' do
341
+ expect { subject.approve!(true) }.to_not raise_error(ActsAsApprovable::Error::Stale)
342
+ end
343
+
344
+ it 'leaves the approval in a pending state' do
345
+ subject.approve!(true)
346
+ subject.should be_approved
347
+ end
348
+ end
349
+ end
350
+
351
+ describe '#reject!' do
352
+ it 'does not raise a Stale exception' do
353
+ expect { subject.reject! }.to_not raise_error(ActsAsApprovable::Error::Stale)
354
+ end
355
+
356
+ it 'moves the approval to a rejected state' do
357
+ subject.reject!
358
+ subject.should be_rejected
359
+ end
360
+ end
361
+ end
362
+
363
+ context 'when the approval is unlocked and fresh' do
364
+ before(:each) do
365
+ @item = DefaultApprovable.without_approval { |m| m.create }
366
+ subject.stub(:locked? => false, :stale? => false, :item => @item, :object => {})
367
+ end
368
+
369
+ it { should_not be_stale }
370
+ it { should be_fresh }
371
+
372
+ describe '#approve!' do
373
+ it 'does not raise an exception' do
374
+ expect { subject.approve! }.to_not raise_error
375
+ end
376
+
377
+ it 'moves the approval to an approved state' do
378
+ subject.approve!
379
+ subject.should be_approved
380
+ end
381
+
382
+ it 'calls the before and after callbacks' do
383
+ subject.should_receive(:run_item_callback).with(:before_approve).once.and_return(true)
384
+ subject.should_receive(:run_item_callback).with(:after_approve).once
385
+ subject.approve!
386
+ end
387
+
388
+ context 'when the event is :update' do
389
+ before(:each) do
390
+ subject.stub(:event => 'update')
391
+ end
392
+
393
+ it 'sets the item attributes' do
394
+ @item.should_receive(:attributes=)
395
+ subject.approve!
396
+ end
397
+
398
+ it 'does not set the local item state' do
399
+ @item.should_not_receive(:set_approval_state)
400
+ subject.approve!
401
+ end
402
+ end
403
+
404
+ context 'when the event is :create' do
405
+ before(:each) do
406
+ subject.stub(:event => 'create')
407
+ end
408
+
409
+ it 'does not set the item attributes' do
410
+ @item.should_not_receive(:attributes=)
411
+ subject.approve!
412
+ end
413
+
414
+ it 'sets the local item state' do
415
+ @item.should_receive(:set_approval_state).with('approved')
416
+ subject.approve!
417
+ end
418
+ end
419
+ end
420
+
421
+ describe '#reject!' do
422
+ it 'does not raise an exception' do
423
+ expect { subject.reject! }.to_not raise_error
424
+ end
425
+
426
+ it 'moves the approval to a rejected state' do
427
+ subject.reject!
428
+ subject.should be_rejected
429
+ end
430
+
431
+ it 'sets the reason if given' do
432
+ subject.reject!('reason')
433
+ subject.reason.should == 'reason'
434
+ end
435
+
436
+ it 'calls the before and after callbacks' do
437
+ subject.should_receive(:run_item_callback).with(:before_reject).once.and_return(true)
438
+ subject.should_receive(:run_item_callback).with(:after_reject).once
439
+ subject.reject!
440
+ end
441
+
442
+ context 'when the event is :update' do
443
+ before(:each) do
444
+ subject.stub(:event => 'update')
445
+ end
446
+
447
+ it 'does not set the item attributes' do
448
+ @item.should_not_receive(:attributes=)
449
+ subject.reject!
450
+ end
451
+
452
+ it 'does not set the local item state' do
453
+ @item.should_not_receive(:set_approval_state)
454
+ subject.reject!
455
+ end
456
+ end
457
+
458
+ context 'when the event is :create' do
459
+ before(:each) do
460
+ subject.stub(:event => 'create')
461
+ end
462
+
463
+ it 'does not set the item attributes' do
464
+ @item.should_not_receive(:attributes=)
465
+ subject.reject!
466
+ end
467
+
468
+ it 'sets the local item state' do
469
+ @item.should_receive(:set_approval_state).with('rejected')
470
+ subject.reject!
471
+ end
472
+ end
473
+ end
474
+ end
475
+ end