acts-as-approvable 0.6.7 → 0.6.8.1

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