dcidev_approval 0.0.7 → 0.0.11
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.
- checksums.yaml +4 -4
- data/README.md +93 -1
- data/lib/dcidev_approval.rb +75 -63
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 047563f0a296d733db4828b485a0446cd2757bb3f915809808555d44719a5e44
|
4
|
+
data.tar.gz: 2a3fb2c40952698d8ea669a33f67bb503e5a0dfb44d6c900caef2d4ef27e1231
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b9ef11b07ae7f35c5c21c1a1c93da4565077570d5b277786a87b913d690b9b4885703cbed14337e8ecc9c16c5c0e3f822fd6359776cac2db608069dff51619d2
|
7
|
+
data.tar.gz: '099759cd2d503012da3ac66a22a28fee1b67b2d07e67f0c421ae348477fd7db1898a8fed75087d2365f9dff61cc3c189219fc9184105a0fb8e11990d55684856'
|
data/README.md
CHANGED
@@ -1 +1,93 @@
|
|
1
|
-
|
1
|
+
# Setup
|
2
|
+
##### 1. Add new column named `status` to every model requiring this gem
|
3
|
+
`db/migrate/migration_file.rb`
|
4
|
+
```ruby
|
5
|
+
class AddStatusToProduk < ActiveRecord::Migration[6.0]
|
6
|
+
def change
|
7
|
+
add_column :produk, :status, :string
|
8
|
+
add_column :produk, :change_status, :string
|
9
|
+
end
|
10
|
+
end
|
11
|
+
```
|
12
|
+
`app/models/model.rb`
|
13
|
+
```ruby
|
14
|
+
STATUS = %w[waiting approved rejected].freeze
|
15
|
+
enum status: STATUS.zip(STATUS).to_h, _prefix: true
|
16
|
+
|
17
|
+
CHANGE_STATUS = %w(pending_delete pending_update).freeze
|
18
|
+
enum change_status: CHANGE_STATUS.zip(CHANGE_STATUS).to_h, _prefix: true
|
19
|
+
```
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
##### 2. Format your `Agent` and `Role` with one-many relationship.
|
24
|
+
##### 3. Declare instance method `is_admin?` to `Agent` to find out whether an agent is an admin or not
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
def is_admin?
|
28
|
+
# add your custom logic here
|
29
|
+
self.approved? && ["admin", "super_admin", "checker"].include?(self.try(:roles).try(:first).try(:code))
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
##### 4. Include the module to every model requiring approval. Or just put it in `ApplicationRecord`
|
34
|
+
`app/models/application_record.rb`
|
35
|
+
```ruby
|
36
|
+
class ApplicationRecord < ActiveRecord::Base
|
37
|
+
include DcidevApproval
|
38
|
+
self.abstract_class = true
|
39
|
+
# ...
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
# Features
|
44
|
+
* Create: `Model.create_data(declared(params), current_user, bypass)`
|
45
|
+
* Update: `model.edit_data(declared(params), current_user, bypass)`
|
46
|
+
* Delete: `model.delete_data(declared(params), current_user, bypass)`
|
47
|
+
* Approval: `model.approval(declared(params))`
|
48
|
+
* Compare current database value and argument to check if there are any update: `model.changes_present?(params)`
|
49
|
+
* Check approval status: `model.waiting_approval?`, `model.pending_insert?`, `model.pending_update?`, `model.pending_delete?`
|
50
|
+
* Find last lodifier & timestamp: `model.last_modified_by`
|
51
|
+
* Find author: `model.created_by`
|
52
|
+
* Find approval agent & timestamp: `model.last_approved_by`
|
53
|
+
|
54
|
+
Explanation
|
55
|
+
* `declared(params)`: is a hash value from Grape Parameters, plain ruby hash can also be used
|
56
|
+
* `current_user`: the agent responsible for the changes
|
57
|
+
* `bypass`: boolean value to toogle the approval system. If not sent, the default value is `true`
|
58
|
+
|
59
|
+
To track changes peformed to a record, call
|
60
|
+
# Callbacks
|
61
|
+
To execute code before/after the CRUD, include module `DcidevApproval` in `ApplicationRecord` and peform overide and or overload on it's child model.
|
62
|
+
|
63
|
+
`app/models/application_record.rb`
|
64
|
+
```ruby
|
65
|
+
class ApplicationRecord < ActiveRecord::Base
|
66
|
+
include DcidevApproval
|
67
|
+
self.abstract_class = true
|
68
|
+
# ...
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
`app/models/child_model.rb`
|
73
|
+
```ruby
|
74
|
+
class ChildModel < ApplicationRecord
|
75
|
+
# ...
|
76
|
+
def self.create_data(params, agent, request)
|
77
|
+
super(params, agent, false) do |data|
|
78
|
+
# do something after the record is successfully created
|
79
|
+
# in this case, write an activity log
|
80
|
+
# the data variable will return the created record
|
81
|
+
ActivityLog.write("#{agent.is_admin? || params.bypass ? nil : "Request "} Add #{self.class.to_s}", request, agent, menu, data) if params.log
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def edit_data(params, agent, request)
|
86
|
+
super(params, agent, false) do |_|
|
87
|
+
# do something after the record is successfully edited and require approval
|
88
|
+
end
|
89
|
+
end
|
90
|
+
# ...
|
91
|
+
end
|
92
|
+
|
93
|
+
```
|
data/lib/dcidev_approval.rb
CHANGED
@@ -1,135 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module DcidevApproval
|
2
|
-
def self.included
|
4
|
+
def self.included(base)
|
3
5
|
base.send :include, InstanceMethods
|
4
6
|
base.extend ClassMethods
|
5
7
|
end
|
6
8
|
|
7
9
|
module InstanceMethods
|
8
|
-
|
9
10
|
def changes_present?(changes)
|
10
11
|
present = false
|
11
12
|
changes.each do |k, v|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
break
|
16
|
-
end
|
17
|
-
rescue => _
|
13
|
+
if eval("self.#{k}") != v
|
14
|
+
present = true
|
15
|
+
break
|
18
16
|
end
|
17
|
+
rescue StandardError => _e
|
19
18
|
end
|
20
|
-
|
19
|
+
present
|
21
20
|
end
|
22
21
|
|
23
22
|
def waiting_approval?
|
24
|
-
%w[pending_update pending_delete].include?(
|
23
|
+
%w[pending_update pending_delete].include?(change_status) || status == 'waiting'
|
25
24
|
end
|
26
25
|
|
27
26
|
def pending_insert?
|
28
|
-
|
27
|
+
change_status.nil? && %w[waiting rejected].include?(status)
|
29
28
|
end
|
30
29
|
|
31
30
|
def pending_update?
|
32
|
-
|
31
|
+
change_status == 'pending_update'
|
33
32
|
end
|
34
33
|
|
35
34
|
def pending_delete?
|
36
|
-
|
35
|
+
change_status == 'pending_delete'
|
36
|
+
end
|
37
|
+
|
38
|
+
def approved?
|
39
|
+
status == 'approved' || change_status.nil?
|
40
|
+
end
|
41
|
+
|
42
|
+
def rejected?
|
43
|
+
status == 'rejected'
|
44
|
+
end
|
45
|
+
|
46
|
+
def waiting?
|
47
|
+
status == 'waiting'
|
37
48
|
end
|
38
49
|
|
39
50
|
def last_modified_by
|
40
51
|
# p self.audit_trail
|
41
|
-
|
42
|
-
log = self.activity_logs.where("activity LIKE '%delete%'").limit(1).order(created_at: :desc).try(:first)
|
43
|
-
else
|
44
|
-
log = self.activity_logs.where("activity LIKE '%edit%'").limit(1).order(created_at: :desc).try(:first)
|
45
|
-
end
|
52
|
+
log = activity_logs.where(activity_type: %w[update delete]).limit(1).order(created_at: :desc).try(:first)
|
46
53
|
{
|
47
|
-
modified_by: log.present? ? log.try(:agent).try(:name).to_s + " (#{log.try(:agent).try(:username)
|
48
|
-
modified_at: log.present? ? log.try(:created_at) ||
|
54
|
+
modified_by: log.present? ? log.try(:agent).try(:name).to_s + " (#{log.try(:agent).try(:username)} | #{log.try(:agent).try(:roles).try(:first).try(:name)})" : nil,
|
55
|
+
modified_at: log.present? ? log.try(:created_at) || try(:updated_at) || try(:created_at) : nil
|
49
56
|
}
|
50
57
|
end
|
51
58
|
|
52
59
|
def created_by
|
53
|
-
log =
|
60
|
+
log = activity_logs.order(created_at: :desc).limit(1).try(:first)
|
54
61
|
{
|
55
|
-
created_by: log.present? && log.try(:agent).try(:name).present? ? log.try(:agent).try(:name).to_s + " (#{log.try(:
|
56
|
-
created_at:
|
62
|
+
created_by: log.present? && log.try(:agent).try(:name).present? ? log.try(:agent).try(:name).to_s + " (#{log.try(:agent_role).try(:name)})" : 'System',
|
63
|
+
created_at: try(:created_at) || log.try(:created_at)
|
57
64
|
}
|
58
65
|
end
|
59
66
|
|
60
67
|
def last_approved_by
|
61
|
-
last_approve =
|
62
|
-
last_entry =
|
68
|
+
last_approve = activity_logs.where(activity_type: 'approve').limit(1).order(created_at: :desc).try(:first)
|
69
|
+
last_entry = activity_logs.limit(1).order(created_at: :desc).try(:first)
|
63
70
|
{
|
64
|
-
approved_by: last_approve.try(:id) == last_entry.try(:id) ? last_approve.try(:agent).try(:name).to_s + " (#{last_approve.try(:
|
71
|
+
approved_by: last_approve.try(:id) == last_entry.try(:id) ? last_approve.try(:agent).try(:name).to_s + " (#{last_approve.try(:agent_role).try(:name)})" : nil,
|
65
72
|
approved_at: last_approve.try(:id) == last_entry.try(:id) ? last_approve.try(:created_at) : nil
|
66
73
|
}
|
67
74
|
end
|
68
75
|
|
69
76
|
def approve_changes
|
70
|
-
|
71
|
-
|
72
|
-
raise self.errors.full_messages.join(", ") unless self.update(status: :approved, data_changes: nil, change_status: nil)
|
77
|
+
if change_status.nil? && %w[waiting rejected].include?(status) && !update(status: :approved, data_changes: nil, change_status: nil)
|
78
|
+
raise errors.full_messages.join(', ')
|
73
79
|
# ActivityLog.write("Approve insert to #{self.class.to_s}", request, agent, menu, self) if params.log
|
74
80
|
# self.delay(queue: "reorder_#{self.id}", priority: 0).reorder if self.class.column_names.include?("view_order")
|
75
81
|
|
76
82
|
end
|
77
|
-
if self.change_status == "pending_update"
|
78
|
-
raise self.errors.full_messages.join(", ") unless self.update_by_params(self.data_changes, false)
|
79
|
-
raise self.errors.full_messages.join(", ") unless self.update(status: :approved, data_changes: nil, change_status: nil)
|
80
|
-
# ActivityLog.write("Approve update to #{self.class.to_s}", request, agent, menu, self) if params.log
|
81
|
-
# self.delay(queue: "reorder_#{self.id}", priority: 0).reorder if self.class.column_names.include?("view_order")
|
82
83
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
84
|
+
case change_status
|
85
|
+
when 'pending_update'
|
86
|
+
raise errors.full_messages.join(', ') unless update_by_params(data_changes, false)
|
87
|
+
raise errors.full_messages.join(', ') unless update(status: :approved, data_changes: nil, change_status: nil)
|
88
|
+
# ActivityLog.write("Approve update to #{self.class.to_s}", request, agent, menu, self) if params.log
|
89
|
+
# self.delay(queue: "reorder_#{self.id}", priority: 0).reorder if self.class.column_names.include?("view_order")
|
90
|
+
|
91
|
+
when 'pending_delete'
|
92
|
+
destroy
|
89
93
|
end
|
90
94
|
end
|
91
95
|
|
92
96
|
def delete_changes
|
93
97
|
# return unless %w[pending_update pending_delete].include? self.change_status
|
94
|
-
raise
|
98
|
+
raise errors.full_messages.join(', ') unless update(data_changes: nil, change_status: nil, status: status == 'waiting' ? :rejected : :approved)
|
95
99
|
# ActivityLog.write("Reject changes to #{self.class.to_s}", request, agent, menu, self) if params.log
|
96
100
|
end
|
97
101
|
|
98
102
|
def edit_data(params, agent, bypass = true)
|
99
|
-
raise
|
103
|
+
raise 'data still waiting for approval' if waiting_approval?
|
104
|
+
|
100
105
|
if bypass
|
101
|
-
raise
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
106
|
+
raise errors.full_messages.join(', ') unless update_by_params(params, false)
|
107
|
+
# ActivityLog.write("Edit #{self.class.to_s}", request, agent, menu, self) if params.log
|
108
|
+
elsif changes_present?(params)
|
109
|
+
ActiveRecord::Base.transaction do
|
110
|
+
data = if agent.is_admin? || status == 'waiting'
|
111
|
+
params
|
112
|
+
else
|
113
|
+
{
|
114
|
+
change_status: :pending_update, data_changes: agent.is_admin? ? nil : params
|
115
|
+
}
|
116
|
+
end
|
117
|
+
raise errors.full_messages.join(', ') unless update_by_params(data, false)
|
110
118
|
end
|
119
|
+
# ActivityLog.write("#{agent.is_admin? ? nil : "Request "}Edit #{self.class.to_s}", request, agent, menu, self) if params.log
|
111
120
|
end
|
112
121
|
yield self
|
113
122
|
end
|
114
123
|
|
115
124
|
def approval(params)
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
125
|
+
case params.status
|
126
|
+
when 'approved'
|
127
|
+
approve_changes
|
128
|
+
when 'rejected'
|
129
|
+
delete_changes
|
120
130
|
end
|
121
131
|
yield self
|
122
132
|
end
|
123
133
|
|
124
134
|
def delete_data(agent, bypass = true)
|
125
|
-
raise
|
135
|
+
raise 'data still waiting for approval' if waiting_approval?
|
136
|
+
|
126
137
|
if bypass || agent.is_admin?
|
127
138
|
ActiveRecord::Base.transaction do
|
128
139
|
# ActivityLog.write("Delete #{self.class.to_s}", request, agent, menu, self) if params.log
|
129
|
-
raise
|
140
|
+
raise errors.full_messages.join(', ') unless destroy
|
130
141
|
end
|
131
142
|
else
|
132
|
-
raise
|
143
|
+
raise errors.full_messages.join(', ') unless update(change_status: :pending_delete)
|
133
144
|
# ActivityLog.write("Request Delete #{self.class.to_s}", request, agent, menu, self) if params.log
|
134
145
|
end
|
135
146
|
yield true
|
@@ -141,18 +152,19 @@ module DcidevApproval
|
|
141
152
|
if bypass
|
142
153
|
ActiveRecord::Base.transaction do
|
143
154
|
data = params.merge!({ status: :approved })
|
144
|
-
d =
|
145
|
-
raise d.errors.full_messages.join(
|
155
|
+
d = new_from_params(data)
|
156
|
+
raise d.errors.full_messages.join(', ') unless d.save
|
157
|
+
|
146
158
|
yield d
|
147
159
|
# ActivityLog.write("#{agent.is_admin? ? nil : "Request "} Add #{self.to_s}", request, agent, menu, d) if params.log
|
148
160
|
end
|
149
161
|
else
|
150
|
-
d =
|
162
|
+
d = new_from_params(params)
|
151
163
|
d.status = agent.is_admin? ? :approved : :waiting
|
152
|
-
raise d.errors.full_messages.join(
|
164
|
+
raise d.errors.full_messages.join(', ') unless d.save
|
165
|
+
|
153
166
|
yield d
|
154
167
|
end
|
155
168
|
end
|
156
169
|
end
|
157
170
|
end
|
158
|
-
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dcidev_approval
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Punto Damar P
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-06-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dcidev_active_record
|
@@ -33,10 +33,10 @@ extra_rdoc_files: []
|
|
33
33
|
files:
|
34
34
|
- README.md
|
35
35
|
- lib/dcidev_approval.rb
|
36
|
-
homepage:
|
36
|
+
homepage:
|
37
37
|
licenses: []
|
38
38
|
metadata: {}
|
39
|
-
post_install_message:
|
39
|
+
post_install_message:
|
40
40
|
rdoc_options: []
|
41
41
|
require_paths:
|
42
42
|
- lib
|
@@ -51,8 +51,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
51
51
|
- !ruby/object:Gem::Version
|
52
52
|
version: '0'
|
53
53
|
requirements: []
|
54
|
-
rubygems_version: 3.
|
55
|
-
signing_key:
|
54
|
+
rubygems_version: 3.1.2
|
55
|
+
signing_key:
|
56
56
|
specification_version: 4
|
57
57
|
summary: Logic for implementing record changes approval
|
58
58
|
test_files: []
|