approvable 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/models/approvable/change_request.rb +28 -13
- data/db/migrate/20140807145534_create_approvable_change_requests.rb +1 -1
- data/db/migrate/20140809183112_add_notes_to_approvable_change_requests.rb +5 -0
- data/lib/approvable/acts_as_approvable.rb +101 -36
- data/lib/approvable/engine.rb +4 -16
- data/lib/approvable/version.rb +1 -1
- data/spec/dummy/app/models/foobar.rb +6 -0
- data/spec/dummy/app/models/listing.rb +2 -0
- data/spec/dummy/db/migrate/20140816143802_create_foobars.rb +9 -0
- data/spec/dummy/db/schema.rb +33 -1
- data/spec/factories/change_requests.rb +5 -28
- data/spec/factories/listing.rb +4 -1
- data/spec/models/acts_as_approvable_spec.rb +344 -159
- data/spec/models/approvable/change_request_spec.rb +19 -20
- metadata +23 -9
- data/README.rdoc +0 -3
- data/spec/dummy/log/development.log +0 -339
- data/spec/dummy/log/test.log +0 -33704
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44c15bf9a39dd40e95b9d8455bf00c5bdcd44824
|
4
|
+
data.tar.gz: b91c51047334ead882747819116a498858a42c88
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7f2c4b284e223bd22b5139b0d1d98cc2597fa3d5a580c6294d11510f52a618b773632dbf40e040060ee375ddc106c9dafc2efc80a303e5aaf2b83e640197f157
|
7
|
+
data.tar.gz: 8155feb1924e39a3aa114c67d5147481fdf3036e3645c32394e68a942489246b51060f1b5f784c422c2ff8acb3597463f28a1487502143b69f4058a26ee72d85
|
@@ -1,54 +1,69 @@
|
|
1
1
|
module Approvable
|
2
2
|
class ChangeRequest < ActiveRecord::Base
|
3
|
-
belongs_to :approvable, :
|
3
|
+
belongs_to :approvable, :polymorphic => true
|
4
4
|
belongs_to :approver, :polymorphic => true
|
5
5
|
|
6
6
|
validate :no_outstanding_change_requests, on: :create
|
7
|
-
validate :not_submitted_or_approved, if: :requested_changes_changed?
|
7
|
+
# validate :not_submitted_or_approved, if: :requested_changes_changed?
|
8
8
|
|
9
9
|
after_save :update_rejected_to_pending, if: :requested_changes_changed?
|
10
10
|
|
11
11
|
scope :unapproved, -> {where.not(state: 'approved')}
|
12
12
|
|
13
|
-
|
13
|
+
include AASM
|
14
|
+
|
15
|
+
aasm column: :state do
|
16
|
+
|
17
|
+
state :pending, :initial => true
|
18
|
+
state :submitted
|
19
|
+
state :rejected
|
20
|
+
state :approved
|
21
|
+
|
14
22
|
event :submit do
|
15
|
-
|
23
|
+
transitions from: [:rejected, :pending], to: :submitted
|
16
24
|
end
|
17
25
|
|
18
26
|
event :unsubmit do
|
19
|
-
|
27
|
+
transitions from: :submitted, to: :pending
|
20
28
|
end
|
21
29
|
|
22
30
|
event :approve do
|
23
|
-
|
31
|
+
transitions from: :submitted, to: :approved
|
24
32
|
end
|
25
|
-
|
33
|
+
|
26
34
|
event :reject do
|
27
|
-
|
35
|
+
transitions from: :submitted, to: :rejected, :on_transition => Proc.new {|obj, *args| obj.transition_options(*args)}
|
28
36
|
end
|
29
37
|
|
30
38
|
event :unreject do
|
31
|
-
|
39
|
+
transitions from: :rejected, to: :pending
|
32
40
|
end
|
33
41
|
end
|
34
42
|
|
43
|
+
def transition_options(options = {})
|
44
|
+
note = options[:note] if options
|
45
|
+
self.notes_will_change! if note
|
46
|
+
self.notes ||= {}
|
47
|
+
self.notes[Time.now.to_s] = note if note
|
48
|
+
end
|
49
|
+
|
35
50
|
private
|
36
51
|
|
37
52
|
def not_submitted_or_approved
|
38
|
-
if approved
|
53
|
+
if ['approved', 'submitted'].include? state_was
|
39
54
|
errors.add(:base, "cannot change a #{state} request")
|
40
55
|
end
|
41
56
|
end
|
42
|
-
|
57
|
+
|
43
58
|
def no_outstanding_change_requests
|
44
59
|
if self.class.where(approvable: approvable).unapproved.any?
|
45
60
|
errors.add(:base, 'please use the existing change request')
|
46
|
-
end
|
61
|
+
end
|
47
62
|
end
|
48
63
|
|
49
64
|
def update_rejected_to_pending
|
50
65
|
if rejected?
|
51
|
-
unreject
|
66
|
+
unreject!
|
52
67
|
end
|
53
68
|
end
|
54
69
|
|
@@ -3,7 +3,7 @@ class CreateApprovableChangeRequests < ActiveRecord::Migration
|
|
3
3
|
create_table :approvable_change_requests do |t|
|
4
4
|
t.string :approvable_type
|
5
5
|
t.integer :approvable_id
|
6
|
-
t.json :requested_changes
|
6
|
+
t.json :requested_changes, default: {}
|
7
7
|
t.string :state
|
8
8
|
t.string :approver_type
|
9
9
|
t.integer :approver_id
|
@@ -6,78 +6,143 @@ module Approvable
|
|
6
6
|
end
|
7
7
|
|
8
8
|
module ClassMethods
|
9
|
-
def acts_as_approvable
|
9
|
+
def acts_as_approvable **options
|
10
10
|
include Approvable::ActsAsApprovable::LocalInstanceMethods
|
11
11
|
|
12
|
-
has_many :change_requests, as: :approvable, class_name: 'Approvable::ChangeRequest'
|
13
|
-
has_one :current_change_request, -> {where.not(state: 'approved') }, as: :approvable, class_name: 'Approvable::ChangeRequest'
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
accepts_nested_attributes_for :current_change_request, update_only: true
|
12
|
+
has_many :change_requests, as: :approvable, class_name: 'Approvable::ChangeRequest', dependent: :destroy
|
13
|
+
has_one :current_change_request, -> {where.not(state: 'approved') }, as: :approvable, class_name: 'Approvable::ChangeRequest', autosave: true
|
14
|
+
|
15
|
+
before_save :apply_changes, if: :auto_approve?
|
16
|
+
after_save :force_approve!, if: :auto_approve?
|
18
17
|
|
18
|
+
cattr_accessor :filter_attrs, :filter_type
|
19
|
+
if options[:except]
|
20
|
+
self.filter_type = :except
|
21
|
+
self.filter_attrs = options[:except]
|
22
|
+
elsif options[:only]
|
23
|
+
self.filter_type = :only
|
24
|
+
self.filter_attrs = options[:only]
|
25
|
+
else
|
26
|
+
self.filter_type = :except
|
27
|
+
self.filter_attrs = []
|
28
|
+
end
|
29
|
+
|
30
|
+
unless method_defined?(:assign_attributes_without_change_request)
|
31
|
+
alias_method_chain :assign_attributes, :change_request
|
32
|
+
alias_method :attributes=, :assign_attributes_with_change_request
|
33
|
+
|
34
|
+
end
|
19
35
|
end
|
36
|
+
|
20
37
|
end
|
21
38
|
|
22
39
|
module LocalInstanceMethods
|
40
|
+
|
41
|
+
def requested_changes
|
42
|
+
current_change_request ? current_change_request.requested_changes.with_indifferent_access : {}
|
43
|
+
end
|
23
44
|
|
45
|
+
# use current_change_request here so that we dont get pending from a newly built change_request
|
24
46
|
def change_status
|
25
|
-
current_change_request.state
|
47
|
+
current_change_request ? current_change_request.state : 'approved'
|
26
48
|
end
|
27
|
-
|
28
|
-
def
|
29
|
-
|
49
|
+
|
50
|
+
def change_status_notes
|
51
|
+
current_change_request ? current_change_request.notes : {}
|
52
|
+
end
|
53
|
+
|
54
|
+
def apply_changes
|
55
|
+
self.assign_attributes_without_change_request requested_changes
|
30
56
|
self
|
31
57
|
end
|
32
58
|
|
33
59
|
def submit_changes
|
34
|
-
|
60
|
+
transaction do
|
61
|
+
current_change_request.submit! if current_change_request
|
62
|
+
reload
|
63
|
+
end
|
35
64
|
end
|
36
65
|
|
37
66
|
def unsubmit_changes
|
38
|
-
|
67
|
+
transaction do
|
68
|
+
current_change_request.unsubmit! if current_change_request
|
69
|
+
reload
|
70
|
+
end
|
39
71
|
end
|
40
72
|
|
41
73
|
def approve_changes
|
42
|
-
|
43
|
-
current_change_request.approve
|
74
|
+
transaction do
|
75
|
+
current_change_request.approve! if current_change_request
|
76
|
+
apply_changes.save
|
77
|
+
reload
|
44
78
|
end
|
45
79
|
end
|
46
80
|
|
47
|
-
def reject_changes
|
48
|
-
current_change_request.reject
|
81
|
+
def reject_changes options = {}
|
82
|
+
current_change_request.reject! :rejected, options if current_change_request
|
83
|
+
reload
|
49
84
|
end
|
50
85
|
|
51
|
-
|
86
|
+
def assign_attributes_with_change_request new_attributes
|
87
|
+
ignored_params = ignored_attributes(new_attributes)
|
88
|
+
approvable_params = approvable_attributes(new_attributes)
|
89
|
+
|
90
|
+
if approvable_params.any?
|
91
|
+
current_change_request || build_current_change_request(requested_changes: {})
|
92
|
+
current_change_request.requested_changes = requested_changes.merge approvable_params
|
93
|
+
end
|
52
94
|
|
53
|
-
|
54
|
-
|
55
|
-
|
95
|
+
assign_attributes_without_change_request ignored_params
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
56
99
|
|
57
|
-
def
|
58
|
-
self.
|
100
|
+
def ignored_attributes(new_attributes)
|
101
|
+
process_nested_hash(new_attributes, self.class.filter_attrs, filter_type == :except)
|
59
102
|
end
|
60
|
-
|
61
103
|
|
62
|
-
def
|
63
|
-
|
104
|
+
def approvable_attributes(new_attributes)
|
105
|
+
process_nested_hash(new_attributes, self.class.filter_attrs, filter_type == :only )
|
64
106
|
end
|
65
107
|
|
66
|
-
|
67
|
-
|
68
|
-
|
108
|
+
# h = {"first_name"=>"Leif", "last_name"=>"Gensert", "address"=>{"street"=>"Preysinstraße", "city"=>"München"}}
|
109
|
+
def process_nested_hash(attributes, keys, should_match)
|
110
|
+
attributes = attributes.dup.stringify_keys!
|
111
|
+
hash = {}
|
112
|
+
[*keys].each do |key|
|
113
|
+
if key.is_a? Hash
|
114
|
+
key.each do |k,v|
|
115
|
+
hash[k.to_s] = process_nested_hash(attributes[k.to_s], v, should_match) if attributes[k.to_s]
|
116
|
+
end
|
117
|
+
elsif key.is_a? Array
|
118
|
+
key.each do|k|
|
119
|
+
process_nested_hash(attributes[k.to_s], k, should_match) if attributes[k.to_s]
|
120
|
+
end
|
121
|
+
else
|
122
|
+
value = attributes.delete(key.to_s)
|
123
|
+
hash[key.to_s] = value if value
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
if should_match
|
128
|
+
return hash
|
129
|
+
else
|
130
|
+
return attributes
|
131
|
+
end
|
132
|
+
|
69
133
|
end
|
134
|
+
|
135
|
+
# process_nested_hash h, [:first_name, {address: :street}], true
|
136
|
+
# process_nested_hash h, [:first_name, {address: :street}], false
|
70
137
|
|
71
|
-
def
|
72
|
-
|
73
|
-
save_without_change_request
|
138
|
+
def auto_approve?
|
139
|
+
Approvable.auto_approve == true
|
74
140
|
end
|
75
141
|
|
76
|
-
def
|
77
|
-
|
78
|
-
save_without_change_request!
|
142
|
+
def force_approve!
|
143
|
+
current_change_request.update_column :state, 'approved' if current_change_request
|
79
144
|
end
|
80
|
-
|
145
|
+
|
81
146
|
end
|
82
147
|
end
|
83
148
|
end
|
data/lib/approvable/engine.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
require 'approvable/acts_as_approvable'
|
2
|
-
require '
|
3
|
-
require '
|
4
|
-
require 'state_machine/version'
|
2
|
+
require 'aasm'
|
3
|
+
require 'activesupport/json_encoder'
|
5
4
|
|
6
5
|
module Approvable
|
6
|
+
cattr_accessor :disabled, :auto_approve
|
7
|
+
|
7
8
|
class Engine < ::Rails::Engine
|
8
9
|
isolate_namespace Approvable
|
9
10
|
|
@@ -15,19 +16,6 @@ module Approvable
|
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
18
|
-
|
19
|
-
initializer :state_machine do |app|
|
20
|
-
unless StateMachine::VERSION == '1.2.0'
|
21
|
-
# If you see this message, please test removing this file
|
22
|
-
# If it's still required, please bump up the version above
|
23
|
-
Rails.logger.warn "Please remove me, StateMachine version has changed"
|
24
|
-
end
|
25
|
-
|
26
|
-
module StateMachine::Integrations::ActiveModel
|
27
|
-
public :around_validation
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
19
|
|
32
20
|
config.generators do |g|
|
33
21
|
g.test_framework :rspec, :fixture => false
|
data/lib/approvable/version.rb
CHANGED
data/spec/dummy/db/schema.rb
CHANGED
@@ -11,11 +11,43 @@
|
|
11
11
|
#
|
12
12
|
# It's strongly recommended that you check this file into your version control system.
|
13
13
|
|
14
|
-
ActiveRecord::Schema.define(version:
|
14
|
+
ActiveRecord::Schema.define(version: 20140816143802) do
|
15
15
|
|
16
16
|
# These are extensions that must be enabled in order to support this database
|
17
17
|
enable_extension "plpgsql"
|
18
18
|
|
19
|
+
create_table "approvable_change_requests", force: true do |t|
|
20
|
+
t.string "approvable_type"
|
21
|
+
t.integer "approvable_id"
|
22
|
+
t.json "requested_changes", default: {}
|
23
|
+
t.string "state"
|
24
|
+
t.string "approver_type"
|
25
|
+
t.integer "approver_id"
|
26
|
+
t.datetime "created_at"
|
27
|
+
t.datetime "updated_at"
|
28
|
+
t.json "notes", default: {}
|
29
|
+
end
|
30
|
+
|
31
|
+
create_table "bars", force: true do |t|
|
32
|
+
t.string "title"
|
33
|
+
t.integer "listing_id"
|
34
|
+
t.datetime "created_at"
|
35
|
+
t.datetime "updated_at"
|
36
|
+
end
|
37
|
+
|
38
|
+
create_table "foobars", force: true do |t|
|
39
|
+
t.json "json_hash"
|
40
|
+
t.datetime "created_at"
|
41
|
+
t.datetime "updated_at"
|
42
|
+
end
|
43
|
+
|
44
|
+
create_table "foos", force: true do |t|
|
45
|
+
t.string "title"
|
46
|
+
t.integer "listing_id"
|
47
|
+
t.datetime "created_at"
|
48
|
+
t.datetime "updated_at"
|
49
|
+
end
|
50
|
+
|
19
51
|
create_table "listings", force: true do |t|
|
20
52
|
t.string "title"
|
21
53
|
t.text "description"
|
@@ -7,44 +7,21 @@ FactoryGirl.define do
|
|
7
7
|
|
8
8
|
|
9
9
|
trait :approved do
|
10
|
-
|
10
|
+
state :approved
|
11
11
|
end
|
12
12
|
|
13
13
|
trait :pending do
|
14
|
+
state :pending
|
15
|
+
|
14
16
|
end
|
15
17
|
|
16
18
|
trait :submitted do
|
17
|
-
|
19
|
+
state :submitted
|
18
20
|
end
|
19
21
|
|
20
22
|
trait :rejected do
|
21
|
-
|
23
|
+
state :rejected
|
22
24
|
end
|
23
|
-
|
24
|
-
|
25
|
-
# trait :pending do
|
26
|
-
# submitted_at nil
|
27
|
-
# approved_at nil
|
28
|
-
# rejected_at nil
|
29
|
-
# end
|
30
|
-
#
|
31
|
-
# trait :submitted do
|
32
|
-
# submitted_at {Time.now}
|
33
|
-
# approved_at nil
|
34
|
-
# rejected_at nil
|
35
|
-
# end
|
36
|
-
#
|
37
|
-
# trait :approved do
|
38
|
-
# submitted_at {Time.now}
|
39
|
-
# approved_at {Time.now}
|
40
|
-
# rejected_at nil
|
41
|
-
# end
|
42
|
-
#
|
43
|
-
# trait :rejected do
|
44
|
-
# submitted_at {Time.now}
|
45
|
-
# approved_at nil
|
46
|
-
# rejected_at {Time.now}
|
47
|
-
# end
|
48
25
|
|
49
26
|
end
|
50
27
|
end
|