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.
- data/.gitignore +2 -5
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Appraisals +14 -10
- data/CHANGELOG +16 -0
- data/Gemfile.lock +60 -20
- data/README.md +1 -2
- data/Rakefile +44 -16
- data/VERSION +1 -1
- data/acts-as-approvable.gemspec +21 -11
- data/features/create_approval.feature +26 -0
- data/features/step_definitions/cucumber_steps.rb +86 -0
- data/features/support/env.rb +14 -0
- data/features/support/large.txt +29943 -0
- data/features/support/second_large.txt +31798 -0
- data/features/update_approval.feature +34 -0
- data/gemfiles/Gemfile.ci +14 -0
- data/gemfiles/Gemfile.ci.lock +98 -0
- data/gemfiles/mysql2.gemfile +7 -0
- data/gemfiles/mysql2.gemfile.lock +92 -0
- data/gemfiles/rails2.gemfile +1 -0
- data/gemfiles/rails2.gemfile.lock +59 -17
- data/gemfiles/rails30.gemfile +1 -0
- data/gemfiles/rails30.gemfile.lock +54 -14
- data/gemfiles/rails31.gemfile +1 -0
- data/gemfiles/rails31.gemfile.lock +54 -14
- data/gemfiles/sqlite.gemfile +7 -0
- data/generators/acts_as_approvable/templates/create_approvals.rb +8 -7
- data/lib/acts-as-approvable.rb +2 -3
- data/lib/acts_as_approvable/approval.rb +3 -2
- data/lib/acts_as_approvable/model.rb +55 -0
- data/lib/acts_as_approvable/model/class_methods.rb +63 -0
- data/lib/acts_as_approvable/model/create_instance_methods.rb +83 -0
- data/lib/acts_as_approvable/model/instance_methods.rb +89 -0
- data/lib/acts_as_approvable/model/update_instance_methods.rb +61 -0
- data/lib/acts_as_approvable/railtie.rb +1 -1
- data/lib/generators/acts_as_approvable/templates/create_approvals.rb +8 -7
- data/spec/acts_as_approvable/approval_spec.rb +475 -0
- data/spec/acts_as_approvable/model/class_methods_spec.rb +219 -0
- data/spec/acts_as_approvable/model/create_instance_methods_spec.rb +149 -0
- data/spec/acts_as_approvable/model/instance_methods_spec.rb +328 -0
- data/spec/acts_as_approvable/model/update_instance_methods_spec.rb +111 -0
- data/spec/acts_as_approvable/model_spec.rb +90 -0
- data/spec/acts_as_approvable/ownership/class_methods_spec.rb +101 -0
- data/spec/acts_as_approvable/ownership/instance_methods_spec.rb +32 -0
- data/spec/acts_as_approvable/ownership_spec.rb +51 -0
- data/spec/acts_as_approvable_spec.rb +29 -0
- data/spec/spec_helper.rb +51 -0
- data/spec/support/database.rb +46 -0
- data/spec/support/database.yml +12 -0
- data/spec/support/matchers.rb +87 -0
- data/spec/support/models.rb +60 -0
- data/{test → spec/support}/schema.rb +15 -9
- metadata +156 -53
- data/gemfiles/rails32.gemfile +0 -8
- data/gemfiles/rails32.gemfile.lock +0 -99
- data/lib/acts_as_approvable/acts_as_approvable.rb +0 -291
- data/test/acts_as_approvable_model_test.rb +0 -459
- data/test/acts_as_approvable_ownership_test.rb +0 -132
- data/test/acts_as_approvable_schema_test.rb +0 -13
- data/test/acts_as_approvable_test.rb +0 -8
- data/test/database.yml +0 -7
- data/test/support.rb +0 -19
- data/test/test_helper.rb +0 -62
data/gemfiles/rails31.gemfile
CHANGED
@@ -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
|
-
|
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
|
-
|
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.
|
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.
|
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
|
-
|
94
|
-
|
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
|
-
|
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
|
@@ -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
|
5
|
-
t.integer
|
6
|
-
t.string
|
7
|
-
t.integer
|
8
|
-
<% if options[:owner] %> t.integer
|
9
|
-
<% end %> t.text
|
10
|
-
t.text
|
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
|
data/lib/acts-as-approvable.rb
CHANGED
@@ -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/
|
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 :
|
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 :
|
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
|
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
|