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
@@ -4,5 +4,6 @@ source "http://rubygems.org"
4
4
 
5
5
  gem "activerecord", "~> 3.1.0"
6
6
  gem "railties", "~> 3.1.0"
7
+ gem "sqlite3"
7
8
 
8
9
  gemspec :path=>"../"
@@ -32,21 +32,44 @@ GEM
32
32
  bundler
33
33
  rake
34
34
  arel (2.2.1)
35
+ binding_of_caller (0.6.7)
35
36
  builder (3.0.0)
36
37
  coderay (1.0.5)
38
+ coolline (0.1.0)
39
+ cucumber (1.1.0)
40
+ builder (>= 2.1.2)
41
+ diff-lcs (>= 1.1.2)
42
+ gherkin (~> 2.5.0)
43
+ json (>= 1.4.6)
44
+ term-ansicolor (>= 1.0.6)
45
+ diff-lcs (1.1.3)
37
46
  erubis (2.7.0)
47
+ gherkin (2.5.1)
48
+ json (>= 1.4.6)
38
49
  hike (1.2.1)
39
50
  i18n (0.6.0)
51
+ io-console (0.3)
40
52
  json (1.6.5)
41
- metaclass (0.0.1)
42
- method_source (0.7.0)
43
- mocha (0.10.4)
44
- metaclass (~> 0.0.1)
53
+ method_source (0.7.1)
45
54
  multi_json (1.0.4)
46
- pry (0.9.8)
55
+ plymouth (0.3.2)
56
+ pry-exception_explorer (>= 0.1.7)
57
+ pry (0.9.8.4)
47
58
  coderay (~> 1.0.5)
48
- method_source (~> 0.7)
49
- slop (>= 2.4.3, < 3)
59
+ method_source (~> 0.7.1)
60
+ slop (>= 2.4.4, < 3)
61
+ pry-coolline (0.1.1)
62
+ coolline (~> 0.1.0)
63
+ io-console (~> 0.3.0)
64
+ pry-exception_explorer (0.1.9)
65
+ pry-stack_explorer (>= 0.3.9)
66
+ pry-nav (0.1.0)
67
+ pry (~> 0.9.8.1)
68
+ pry-stack_explorer (0.4.1)
69
+ binding_of_caller (~> 0.6.2)
70
+ pry (~> 0.9.8.2)
71
+ pry-syntax-hacks (0.0.6)
72
+ pry (>= 0.9.8)
50
73
  rack (1.3.6)
51
74
  rack-cache (1.1)
52
75
  rack (>= 0.4)
@@ -67,19 +90,29 @@ GEM
67
90
  rdoc (3.12)
68
91
  json (~> 1.4)
69
92
  redcarpet (2.1.0)
93
+ rspec (2.8.0)
94
+ rspec-core (~> 2.8.0)
95
+ rspec-expectations (~> 2.8.0)
96
+ rspec-mocks (~> 2.8.0)
97
+ rspec-core (2.8.0)
98
+ rspec-expectations (2.8.0)
99
+ diff-lcs (~> 1.1.2)
100
+ rspec-mocks (2.8.0)
70
101
  shoulda (2.11.3)
71
102
  simplecov (0.5.4)
72
103
  multi_json (~> 1.0.3)
73
104
  simplecov-html (~> 0.5.3)
74
105
  simplecov-html (0.5.3)
75
- slop (2.4.3)
106
+ slop (2.4.4)
76
107
  sprockets (2.0.3)
77
108
  hike (~> 1.2)
78
109
  rack (~> 1.0)
79
110
  tilt (~> 1.1, != 1.3.0)
80
111
  sqlite3 (1.3.5)
112
+ term-ansicolor (1.0.6)
81
113
  thor (0.14.6)
82
114
  tilt (1.3.3)
115
+ timecop (0.3.5)
83
116
  tzinfo (0.3.31)
84
117
  yard (0.7.5)
85
118
 
@@ -89,13 +122,20 @@ PLATFORMS
89
122
  DEPENDENCIES
90
123
  activerecord (~> 3.1.0)
91
124
  acts-as-approvable!
92
- appraisal
93
- mocha
94
- pry
125
+ appraisal (~> 0.4.1)
126
+ cucumber (~> 1.1.0)
127
+ plymouth
128
+ pry (~> 0.9.8.1)
129
+ pry-coolline
130
+ pry-nav (~> 0.1.0)
131
+ pry-stack_explorer
132
+ pry-syntax-hacks
95
133
  railties (~> 3.1.0)
96
- rake
97
- redcarpet
98
- shoulda
134
+ rake (~> 0.9.2)
135
+ redcarpet (~> 2.1.0)
136
+ rspec (~> 2.8.0)
137
+ shoulda (~> 2.0)
99
138
  simplecov
100
139
  sqlite3
140
+ timecop (~> 0.3.5)
101
141
  yard
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "http://rubygems.org"
4
+
5
+ gem "sqlite"
6
+
7
+ gemspec :path=>"../"
@@ -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
@@ -1,9 +1,8 @@
1
1
  require 'active_record'
2
2
 
3
-
4
3
  $LOAD_PATH.unshift(File.dirname(__FILE__))
5
4
 
6
- require 'acts_as_approvable/acts_as_approvable'
5
+ require 'acts_as_approvable/model'
7
6
  require 'acts_as_approvable/approval'
8
7
  require 'acts_as_approvable/error'
9
8
  require 'acts_as_approvable/ownership'
@@ -12,7 +11,7 @@ require 'acts-as-approvable/version'
12
11
  if defined?(Rails) && Rails.version =~ /^3\./
13
12
  require 'acts_as_approvable/railtie'
14
13
  elsif defined?(ActiveRecord)
15
- ActiveRecord::Base.send :include, ActsAsApprovable::Model
14
+ ActiveRecord::Base.send :extend, ActsAsApprovable::Model
16
15
  end
17
16
 
18
17
  $LOAD_PATH.shift
@@ -10,8 +10,9 @@ class Approval < ActiveRecord::Base
10
10
  validates_numericality_of :state, :greater_than_or_equal_to => 0, :less_than => STATES.length
11
11
 
12
12
  serialize :object
13
+ serialize :original
13
14
 
14
- before_save :can_save?
15
+ before_save :able_to_save?
15
16
 
16
17
  ##
17
18
  # Find the enumerated value for a given state.
@@ -97,7 +98,7 @@ class Approval < ActiveRecord::Base
97
98
  ##
98
99
  # Returns true if the approval able to be saved. This requires an unlocked
99
100
  # approval, or an approval just leaving the 'pending' state.
100
- def can_save?
101
+ def able_to_save?
101
102
  unlocked? or state_was == 'pending'
102
103
  end
103
104
 
@@ -0,0 +1,55 @@
1
+ require 'acts_as_approvable/model/class_methods'
2
+ require 'acts_as_approvable/model/instance_methods'
3
+ require 'acts_as_approvable/model/create_instance_methods'
4
+ require 'acts_as_approvable/model/update_instance_methods'
5
+
6
+ module ActsAsApprovable
7
+ ##
8
+ # The meat of {ActsAsApprovable}. This applies methods for the configured approval events
9
+ # and configures the required relationships.
10
+ module Model
11
+ # Declare this in your model to require approval on new records or changes to fields.
12
+ #
13
+ # @param [Hash] options the options for this models approval workflow.
14
+ # @option options [Symbol,Array] :on The events to require approval on (`:create` or `:update`).
15
+ # @option options [String] :state_field The local field to store `:create` approval state.
16
+ # @option options [Array] :ignore A list of fields to ignore. By default we ignore `:created_at`, `:updated_at` and
17
+ # the field specified in `:state_field`.
18
+ # @option options [Array] :only A list of fields to explicitly require approval on. This list supercedes `:ignore`.
19
+ def acts_as_approvable(options = {})
20
+ extend ClassMethods
21
+ include InstanceMethods
22
+
23
+ cattr_accessor :approvable_on
24
+ self.approvable_on = Array.wrap(options.delete(:on) { [:create, :update] })
25
+
26
+ cattr_accessor :approvable_field
27
+ self.approvable_field = options.delete(:state_field)
28
+
29
+ cattr_accessor :approvable_ignore
30
+ ignores = Array.wrap(options.delete(:ignore) { [] })
31
+ ignores.push('created_at', 'updated_at', primary_key, self.approvable_field)
32
+ self.approvable_ignore = ignores.compact.uniq.map(&:to_s)
33
+
34
+ cattr_accessor :approvable_only
35
+ self.approvable_only = Array.wrap(options.delete(:only) { [] }).uniq.map(&:to_s)
36
+
37
+ cattr_accessor :approvals_disabled
38
+ self.approvals_disabled = false
39
+
40
+ has_many :approvals, :as => :item, :dependent => :destroy
41
+
42
+ if approvable_on?(:update)
43
+ include UpdateInstanceMethods
44
+ before_update :approvable_update, :if => :approvable_update?
45
+ end
46
+
47
+ if approvable_on?(:create)
48
+ include CreateInstanceMethods
49
+ before_create :approvable_create, :if => :approvable_create?
50
+ end
51
+
52
+ after_save :approvable_save, :if => :approvals_enabled?
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,63 @@
1
+ module ActsAsApprovable
2
+ module Model
3
+ ##
4
+ # Class methods available after acts_as_approvable has been called
5
+ module ClassMethods
6
+ ##
7
+ # Returns true if the approval queue is active at both the local and global
8
+ # level. Note that the global level supercedes the local level.
9
+ def approvals_enabled?
10
+ global_approvals_on? and approvals_on?
11
+ end
12
+
13
+ def approvals_disabled?
14
+ not approvals_enabled?
15
+ end
16
+
17
+ ##
18
+ # Enable the approval queue for this model.
19
+ def approvals_on
20
+ self.approvals_disabled = false
21
+ end
22
+
23
+ ##
24
+ # Disable the approval queue for this model.
25
+ def approvals_off
26
+ self.approvals_disabled = true
27
+ end
28
+
29
+ def approvals_on?
30
+ not self.approvals_disabled
31
+ end
32
+
33
+ def global_approvals_on?
34
+ ActsAsApprovable.enabled?
35
+ end
36
+
37
+ ##
38
+ # Returns true if the model is configured to use the approval queue on the
39
+ # given event (`:create` or `:update`).
40
+ def approvable_on?(event)
41
+ self.approvable_on.include?(event)
42
+ end
43
+
44
+ ##
45
+ # Returns a list of fields that require approval.
46
+ def approvable_fields
47
+ return self.approvable_only unless self.approvable_only.empty?
48
+ column_names - self.approvable_ignore
49
+ end
50
+
51
+ ##
52
+ # Execute a code block while the approval queue is temporarily disabled. The
53
+ # queue state will be returned to it's previous value, either on or off.
54
+ def without_approval(&block)
55
+ enable = self.approvals_on?
56
+ approvals_off
57
+ yield(self)
58
+ ensure
59
+ approvals_on if enable
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,83 @@
1
+ module ActsAsApprovable
2
+ module Model
3
+ ##
4
+ # Instance methods that apply to the `:create` event specifically.
5
+ module CreateInstanceMethods
6
+ ##
7
+ # Retrieve approval record for the creation event.
8
+ #
9
+ # @return [Approval]
10
+ def approval
11
+ approvals.find_by_event('create')
12
+ end
13
+
14
+ ##
15
+ # Get the approval state of the current record from either the local state
16
+ # field or, if no state field exists, the creation approval object.
17
+ #
18
+ # @return [String] one of `'pending'`, `'approved`' or `'rejected'`.
19
+ def approval_state
20
+ if self.class.approvable_field
21
+ send(self.class.approvable_field)
22
+ elsif approval.present?
23
+ approval.state
24
+ end
25
+ end
26
+
27
+ ##
28
+ # Set the records local approval state.
29
+ #
30
+ # @param [String] state one of `'pending'`, `'approved`' or `'rejected'`.
31
+ def set_approval_state(state)
32
+ return unless self.class.approvable_field
33
+ send("#{self.class.approvable_field}=".to_sym, state)
34
+ end
35
+
36
+ ##
37
+ # Returns true if the record is pending approval.
38
+ def pending?
39
+ approval_state.present? ? approval_state == 'pending' : approval.try(:pending?)
40
+ end
41
+
42
+ ##
43
+ # Returns true if the record has been approved.
44
+ def approved?
45
+ approval_state.present? ? approval_state == 'approved' : approval.try(:approved?)
46
+ end
47
+
48
+ ##
49
+ # Returns true if the record has been rejected.
50
+ def rejected?
51
+ approval_state.present? ? approval_state == 'rejected' : approval.try(:rejected?)
52
+ end
53
+
54
+ ##
55
+ # Approves the record through {Approval#approve!}
56
+ #
57
+ # @return [Boolean]
58
+ def approve!
59
+ return unless approvable_on?(:create) && approval.present?
60
+ approval.approve!
61
+ end
62
+
63
+ ##
64
+ # Rejects the record through {Approval#reject!}
65
+ #
66
+ # @return [Boolean]
67
+ def reject!
68
+ return unless approvable_on?(:create) && approval.present?
69
+ approval.reject!
70
+ end
71
+
72
+ private
73
+ def approvable_create?
74
+ approvals_enabled? and approvable_on?(:create)
75
+ end
76
+
77
+ def approvable_create
78
+ @approval = approvals.build(:event => 'create', :state => 'pending')
79
+ set_approval_state('pending')
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,89 @@
1
+ module ActsAsApprovable
2
+ module Model
3
+ ##
4
+ # Instance methods that apply to both `:update` and `:create` events.
5
+ module InstanceMethods
6
+ ##
7
+ # Returns true if the approval queue is active at both the local and global
8
+ # level. Note that the global level supercedes the local level.
9
+ def approvals_enabled?
10
+ global_approvals_on? and model_approvals_on? and approvals_on?
11
+ end
12
+
13
+ ##
14
+ # Returns the inverse of `#approvals_enabled?`
15
+ def approvals_disabled?
16
+ not approvals_enabled?
17
+ end
18
+
19
+ def approvals_off
20
+ @approvals_disabled = true
21
+ end
22
+
23
+ def approvals_on
24
+ @approvals_disabled = false
25
+ end
26
+
27
+ def approvals_on?
28
+ not @approvals_disabled
29
+ end
30
+
31
+ def model_approvals_on?
32
+ self.class.approvals_on?
33
+ end
34
+
35
+ def global_approvals_on?
36
+ ActsAsApprovable.enabled?
37
+ end
38
+
39
+ ##
40
+ # Returns true if the model is configured to use the approval queue on the
41
+ # given event (`:create` or `:update`).
42
+ def approvable_on?(event)
43
+ self.class.approvable_on?(event)
44
+ end
45
+
46
+ ##
47
+ # A filter that is run before the record can be approved. Returning false
48
+ # stops the approval process from completing.
49
+ def before_approve(approval); end
50
+
51
+ ##
52
+ # A filter that is run after the record has been approved.
53
+ def after_approve(approval); end
54
+
55
+ ##
56
+ # A filter that is run before the record can be rejected. Returning false
57
+ # stops the rejection process from completing.
58
+ def before_reject(approval); end
59
+
60
+ ##
61
+ # A filter that is run after the record has been rejected.
62
+ def after_reject(approval); end
63
+
64
+ ##
65
+ # Execute a code block while the approval queue is temporarily disabled. The
66
+ # queue state will be returned to it's previous value, either on or off.
67
+ def without_approval(&block)
68
+ enable = approvals_on? # If we use #approvals_enabled? the global state might be incorrectly applied.
69
+ approvals_off
70
+ yield(self)
71
+ ensure
72
+ approvals_on if enable
73
+ end
74
+
75
+ def save_without_approval(*args)
76
+ without_approval { |i| save(*args) }
77
+ end
78
+
79
+ def save_without_approval!(*args)
80
+ without_approval { |i| save!(*args) }
81
+ end
82
+
83
+ private
84
+ def approvable_save
85
+ @approval.save if @approval.present? && @approval.new_record?
86
+ end
87
+ end
88
+ end
89
+ end