acts_as_approvable 0.1.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Appraisals +22 -0
- data/CHANGELOG +76 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +84 -0
- data/MIT-LICENSE +2 -2
- data/README.md +146 -0
- data/Rakefile +90 -7
- data/TODO.md +30 -0
- data/VERSION +1 -0
- data/acts_as_approvable.gemspec +40 -0
- data/features/create_approval.feature +36 -0
- data/features/destroy_approval.feature +19 -0
- data/features/reset_approval.feature +13 -0
- data/features/step_definitions/cucumber_steps.rb +132 -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 +48 -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 +86 -0
- data/gemfiles/rails2.gemfile +8 -0
- data/gemfiles/rails2.gemfile.lock +86 -0
- data/gemfiles/rails30.gemfile +9 -0
- data/gemfiles/rails30.gemfile.lock +124 -0
- data/gemfiles/rails31.gemfile +9 -0
- data/gemfiles/rails31.gemfile.lock +135 -0
- data/gemfiles/sqlite.gemfile +7 -0
- data/generators/acts_as_approvable/USAGE +3 -0
- data/generators/acts_as_approvable/acts_as_approvable_generator.rb +81 -0
- data/generators/acts_as_approvable/templates/approvals.js +71 -0
- data/generators/acts_as_approvable/templates/approvals_controller.rb +91 -0
- data/generators/acts_as_approvable/templates/create_approvals.rb +27 -0
- data/generators/acts_as_approvable/templates/initializer.rb +3 -0
- data/generators/acts_as_approvable/templates/jquery.form.js +101 -0
- data/generators/acts_as_approvable/templates/views/erb/_owner_select.html.erb +4 -0
- data/generators/acts_as_approvable/templates/views/erb/_table.html.erb +26 -0
- data/generators/acts_as_approvable/templates/views/erb/index.html.erb +17 -0
- data/generators/acts_as_approvable/templates/views/haml/_owner_select.html.haml +3 -0
- data/generators/acts_as_approvable/templates/views/haml/_table.html.haml +19 -0
- data/generators/acts_as_approvable/templates/views/haml/index.html.haml +15 -0
- data/init.rb +1 -0
- data/lib/acts_as_approvable.rb +96 -2
- data/lib/acts_as_approvable/approval.rb +205 -11
- data/lib/acts_as_approvable/error.rb +34 -0
- data/lib/acts_as_approvable/model.rb +60 -0
- data/lib/acts_as_approvable/model/class_methods.rb +63 -0
- data/lib/acts_as_approvable/model/create_instance_methods.rb +88 -0
- data/lib/acts_as_approvable/model/destroy_instance_methods.rb +38 -0
- data/lib/acts_as_approvable/model/instance_methods.rb +107 -0
- data/lib/acts_as_approvable/model/update_instance_methods.rb +61 -0
- data/lib/acts_as_approvable/ownership.rb +141 -0
- data/lib/acts_as_approvable/railtie.rb +7 -0
- data/lib/acts_as_approvable/version.rb +1 -8
- data/lib/generators/acts_as_approvable/USAGE +1 -0
- data/lib/generators/acts_as_approvable/acts_as_approvable_generator.rb +68 -0
- data/lib/generators/acts_as_approvable/base.rb +30 -0
- data/lib/generators/acts_as_approvable/templates/approvals.js +71 -0
- data/lib/generators/acts_as_approvable/templates/approvals_controller.rb +91 -0
- data/lib/generators/acts_as_approvable/templates/create_approvals.rb +27 -0
- data/lib/generators/acts_as_approvable/templates/jquery.form.js +101 -0
- data/lib/generators/erb/acts_as_approvable_generator.rb +33 -0
- data/lib/generators/erb/templates/_owner_select.html.erb +4 -0
- data/lib/generators/erb/templates/_table.html.erb +26 -0
- data/lib/generators/erb/templates/index.html.erb +17 -0
- data/lib/generators/haml/acts_as_approvable_generator.rb +33 -0
- data/lib/generators/haml/templates/_owner_select.html.haml +3 -0
- data/lib/generators/haml/templates/_table.html.haml +19 -0
- data/lib/generators/haml/templates/index.html.haml +15 -0
- data/lib/tasks/acts_as_approvable.rake +4 -0
- data/rails/init.rb +1 -0
- data/spec/acts_as_approvable/approval_spec.rb +614 -0
- data/spec/acts_as_approvable/model/class_methods_spec.rb +219 -0
- data/spec/acts_as_approvable/model/create_instance_methods_spec.rb +169 -0
- data/spec/acts_as_approvable/model/destroy_instance_methods_spec.rb +71 -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 +113 -0
- data/spec/acts_as_approvable/ownership/class_methods_spec.rb +134 -0
- data/spec/acts_as_approvable/ownership/instance_methods_spec.rb +32 -0
- data/spec/acts_as_approvable/ownership_spec.rb +52 -0
- data/spec/acts_as_approvable_spec.rb +31 -0
- data/spec/spec_helper.rb +51 -0
- data/spec/support/database.rb +49 -0
- data/spec/support/database.yml +12 -0
- data/spec/support/matchers.rb +87 -0
- data/spec/support/models.rb +67 -0
- data/spec/support/schema.rb +54 -0
- metadata +375 -58
- data/README.rdoc +0 -38
- data/lib/acts_as_approvable/approver.rb +0 -76
- data/lib/generators/acts_as_approvable/install_generator.rb +0 -28
- data/lib/generators/acts_as_approvable/templates/install.rb +0 -16
@@ -0,0 +1,60 @@
|
|
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
|
+
require 'acts_as_approvable/model/destroy_instance_methods'
|
6
|
+
|
7
|
+
module ActsAsApprovable
|
8
|
+
##
|
9
|
+
# The meat of {ActsAsApprovable}. This applies methods for the configured approval events
|
10
|
+
# and configures the required relationships.
|
11
|
+
module Model
|
12
|
+
# Declare this in your model to require approval on new records or changes to fields.
|
13
|
+
#
|
14
|
+
# @param [Hash] options the options for this models approval workflow.
|
15
|
+
# @option options [Symbol,Array] :on The events to require approval on (`:create`, `:update` or `:destroy`).
|
16
|
+
# @option options [String] :state_field The local field to store `:create` approval state.
|
17
|
+
# @option options [Array] :ignore A list of fields to ignore. By default we ignore `:created_at`, `:updated_at` and
|
18
|
+
# the field specified in `:state_field`.
|
19
|
+
# @option options [Array] :only A list of fields to explicitly require approval on. This list supercedes `:ignore`.
|
20
|
+
def acts_as_approvable(options = {})
|
21
|
+
extend ClassMethods
|
22
|
+
include InstanceMethods
|
23
|
+
|
24
|
+
cattr_accessor :approvable_on
|
25
|
+
self.approvable_on = Array.wrap(options.delete(:on) { [:create, :update, :destroy] })
|
26
|
+
|
27
|
+
cattr_accessor :approvable_field
|
28
|
+
self.approvable_field = options.delete(:state_field)
|
29
|
+
|
30
|
+
cattr_accessor :approvable_ignore
|
31
|
+
ignores = Array.wrap(options.delete(:ignore) { [] })
|
32
|
+
ignores.push('created_at', 'updated_at', primary_key, self.approvable_field)
|
33
|
+
self.approvable_ignore = ignores.compact.uniq.map(&:to_s)
|
34
|
+
|
35
|
+
cattr_accessor :approvable_only
|
36
|
+
self.approvable_only = Array.wrap(options.delete(:only) { [] }).uniq.map(&:to_s)
|
37
|
+
|
38
|
+
cattr_accessor :approvals_disabled
|
39
|
+
self.approvals_disabled = false
|
40
|
+
|
41
|
+
has_many :approvals, :as => :item, :dependent => :destroy
|
42
|
+
|
43
|
+
if approvable_on?(:update)
|
44
|
+
include UpdateInstanceMethods
|
45
|
+
before_update :approvable_update, :if => :approvable_update?
|
46
|
+
end
|
47
|
+
|
48
|
+
if approvable_on?(:create)
|
49
|
+
include CreateInstanceMethods
|
50
|
+
before_create :approvable_create, :if => :approvable_create?
|
51
|
+
end
|
52
|
+
|
53
|
+
if approvable_on?(:destroy)
|
54
|
+
include DestroyInstanceMethods
|
55
|
+
end
|
56
|
+
|
57
|
+
after_save :approvable_save, :if => :approvals_enabled?
|
58
|
+
end
|
59
|
+
end
|
60
|
+
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,88 @@
|
|
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
|
+
def reset!
|
73
|
+
return unless approvable_on?(:create) && approval.present?
|
74
|
+
approval.reset!
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
def approvable_create?
|
79
|
+
approvals_enabled? and approvable_on?(:create)
|
80
|
+
end
|
81
|
+
|
82
|
+
def approvable_create
|
83
|
+
@approval = approvals.build(:event => 'create', :state => 'pending')
|
84
|
+
set_approval_state('pending')
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module ActsAsApprovable
|
2
|
+
module Model
|
3
|
+
##
|
4
|
+
# Instance methods that apply to the `:destroy` event specifically.
|
5
|
+
module DestroyInstanceMethods
|
6
|
+
##
|
7
|
+
# Retrieve approval records for the destruction event.
|
8
|
+
#
|
9
|
+
# @param [Boolean] all toggle for returning all or pending approvals
|
10
|
+
# @return [Approval]
|
11
|
+
def destroy_approvals(all = true)
|
12
|
+
all ? approvals.find_all_by_event('destroy') : approvals.find_all_by_event_and_state('destroy', 0)
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# Returns true if there are any pending `:destroy` event approvals
|
17
|
+
def pending_destruction?
|
18
|
+
not destroy_approvals(false).empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Add a `:destrory` approval event to the queue if approvals are enabled, otherwise
|
23
|
+
# destroy the record.
|
24
|
+
def destroy
|
25
|
+
approvable_destroy? ? request_destruction : super
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def approvable_destroy?
|
30
|
+
approvals_enabled? and approvable_on?(:destroy)
|
31
|
+
end
|
32
|
+
|
33
|
+
def request_destruction
|
34
|
+
approvals.create!(:event => 'destroy', :state => 'pending', :original => attributes)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,107 @@
|
|
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
|
+
##
|
20
|
+
# Turn off approvals for this instance.
|
21
|
+
def approvals_off
|
22
|
+
@approvals_disabled = true
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Turn on approvals for this instance.
|
27
|
+
def approvals_on
|
28
|
+
@approvals_disabled = false
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Returns true if the approval queue is on for this instance.
|
33
|
+
def approvals_on?
|
34
|
+
not @approvals_disabled
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Returns true if the approval queue is on for this model.
|
39
|
+
def model_approvals_on?
|
40
|
+
self.class.approvals_on?
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Returns true if the approval queue is on globally.
|
45
|
+
def global_approvals_on?
|
46
|
+
ActsAsApprovable.enabled?
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Returns true if the model is configured to use the approval queue on the
|
51
|
+
# given event (`:create` or `:update`).
|
52
|
+
def approvable_on?(event)
|
53
|
+
self.class.approvable_on?(event)
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# A filter that is run before the record can be approved. Returning false
|
58
|
+
# stops the approval process from completing.
|
59
|
+
def before_approve(approval); end
|
60
|
+
|
61
|
+
##
|
62
|
+
# A filter that is run after the record has been approved.
|
63
|
+
def after_approve(approval); end
|
64
|
+
|
65
|
+
##
|
66
|
+
# A filter that is run before the record can be rejected. Returning false
|
67
|
+
# stops the rejection process from completing.
|
68
|
+
def before_reject(approval); end
|
69
|
+
|
70
|
+
##
|
71
|
+
# A filter that is run after the record has been rejected.
|
72
|
+
def after_reject(approval); end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Execute a code block while the approval queue is temporarily disabled. The
|
76
|
+
# queue state will be returned to it's previous value, either on or off.
|
77
|
+
def without_approval(&block)
|
78
|
+
enable = approvals_on? # If we use #approvals_enabled? the global state might be incorrectly applied.
|
79
|
+
approvals_off
|
80
|
+
yield(self)
|
81
|
+
ensure
|
82
|
+
approvals_on if enable
|
83
|
+
end
|
84
|
+
|
85
|
+
def save_without_approval(*args) #:nodoc:
|
86
|
+
without_approval { |i| save(*args) }
|
87
|
+
end
|
88
|
+
|
89
|
+
def save_without_approval!(*args) #:nodoc:
|
90
|
+
without_approval { |i| save!(*args) }
|
91
|
+
end
|
92
|
+
|
93
|
+
def destroy_without_approval #:nodoc:
|
94
|
+
without_approval { |i| destroy }
|
95
|
+
end
|
96
|
+
|
97
|
+
def destroy_without_approval! #:nodoc:
|
98
|
+
without_approval { |i| destroy! }
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
def approvable_save
|
103
|
+
@approval.save if @approval.present? && @approval.new_record?
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -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
|
@@ -0,0 +1,141 @@
|
|
1
|
+
module ActsAsApprovable
|
2
|
+
##
|
3
|
+
# This module provides the {Approval} class with the ability to assign records
|
4
|
+
# as an "owner" of the approval. This is especially useful for tracking purposes
|
5
|
+
# when you require it, and can be beneficial when you have an approval queue with
|
6
|
+
# a high rate of insertions.
|
7
|
+
#
|
8
|
+
# By default the ownership functionality will reference a model named `User` and
|
9
|
+
# will allow any user to take ownership of an approval.
|
10
|
+
module Ownership
|
11
|
+
##
|
12
|
+
# Configure approvals to allow ownership by a User model.
|
13
|
+
#
|
14
|
+
# If a block is given it will be applied to Approval at the class level,
|
15
|
+
# allowing you to override functionality on the fly.
|
16
|
+
#
|
17
|
+
# @param [Hash] options a hash of options for configuration
|
18
|
+
# @option options [Object] :model the model being used for Approval records (defaults to `Approval`).
|
19
|
+
# @option options [Object] :owner the model being used for owner records (defaults to `User`).
|
20
|
+
# @option options [Object] :source class used to override retrieval of owner records.
|
21
|
+
def self.configure(options = {}, &block)
|
22
|
+
approval = options.delete(:model) { Approval }
|
23
|
+
owner = options.delete(:owner) { User }
|
24
|
+
|
25
|
+
approval.send(:include, self)
|
26
|
+
|
27
|
+
ActsAsApprovable.owner_class = owner
|
28
|
+
ActsAsApprovable.owner_source = options.delete(:source)
|
29
|
+
|
30
|
+
approval.send(:belongs_to, :owner, :class_name => owner.to_s, :foreign_key => :owner_id)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.included(base)
|
34
|
+
base.send(:include, InstanceMethods)
|
35
|
+
base.extend(ClassMethods)
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Instance methods for approval ownership.
|
40
|
+
module InstanceMethods
|
41
|
+
##
|
42
|
+
# Set the owner and save the record.
|
43
|
+
#
|
44
|
+
# @return [Boolean]
|
45
|
+
def assign(owner)
|
46
|
+
raise ActsAsApprovable::Error::InvalidOwner unless self.class.available_owners.include?(owner)
|
47
|
+
self.owner = owner
|
48
|
+
save
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Removed any assigned owner and save the record.
|
53
|
+
#
|
54
|
+
# @return [Boolean]
|
55
|
+
def unassign
|
56
|
+
self.owner = nil
|
57
|
+
save
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Class methods for approval ownership.
|
63
|
+
module ClassMethods
|
64
|
+
##
|
65
|
+
# Get the model that represents an owner.
|
66
|
+
#
|
67
|
+
# @see ActsAsApprovable::Ownership.configure
|
68
|
+
def owner_class
|
69
|
+
ActsAsApprovable.owner_class
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Source class used to override Owner retrieval methods
|
74
|
+
#
|
75
|
+
# @see ActsAsApprovable::Ownership.configure
|
76
|
+
def owner_source
|
77
|
+
ActsAsApprovable.owner_source
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# Attempt to run a method on the configured #owner_source class. If it does
|
82
|
+
# not exist yield to the given block.
|
83
|
+
def with_owner_source(method, *args)
|
84
|
+
if owner_source && owner_source.singleton_class.method_defined?(method)
|
85
|
+
owner_source.send(method, *args)
|
86
|
+
else
|
87
|
+
yield
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
##
|
92
|
+
# A list of records that can be assigned to an approval.
|
93
|
+
#
|
94
|
+
# This method can be overriden by the configured #owner_source.
|
95
|
+
def available_owners
|
96
|
+
with_owner_source(:available_owners) { owner_class.all }
|
97
|
+
end
|
98
|
+
|
99
|
+
##
|
100
|
+
# Build an array from {#available_owners} usable by Rails' `#options_for_select`.
|
101
|
+
# Each element in the array is built with {#option_for_owner}.
|
102
|
+
#
|
103
|
+
# @return [Array]
|
104
|
+
def options_for_available_owners(with_prompt = false)
|
105
|
+
owners = available_owners.map { |owner| option_for_owner(owner) }
|
106
|
+
owners.unshift(['(none)', nil]) if with_prompt
|
107
|
+
owners
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# A list of owners that have assigned approvals.
|
112
|
+
#
|
113
|
+
# This method can be overriden by the configured #owner_source.
|
114
|
+
def assigned_owners
|
115
|
+
with_owner_source(:assigned_owners) { all(:select => 'DISTINCT(owner_id)', :conditions => 'owner_id IS NOT NULL', :include => :owner).map(&:owner) }
|
116
|
+
end
|
117
|
+
|
118
|
+
##
|
119
|
+
# Build an array from {#assigned_owners} usable by Rails' `#options_for_select`.
|
120
|
+
# Each element in the array is built with {#option_for_owner}.
|
121
|
+
#
|
122
|
+
# @return [Array]
|
123
|
+
def options_for_assigned_owners(with_prompt = false)
|
124
|
+
owners = assigned_owners.map { |owner| option_for_owner(owner) }
|
125
|
+
owners.unshift(['All Users', nil]) if with_prompt
|
126
|
+
owners
|
127
|
+
end
|
128
|
+
|
129
|
+
##
|
130
|
+
# Helper method that takes an owner record and returns an array for Rails'
|
131
|
+
# `#options_for_select`.
|
132
|
+
#
|
133
|
+
# This method can be overriden by the configured #owner_source.
|
134
|
+
#
|
135
|
+
# @return [Array] a 2-index array with a display string and value.
|
136
|
+
def option_for_owner(owner)
|
137
|
+
with_owner_source(:option_for_owner, owner) { [owner.to_str, owner.id] }
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|