dcidev_approval 0.0.8 → 0.0.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +93 -1
  3. data/lib/dcidev_approval.rb +145 -165
  4. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 113b68a6c6cc3aa09902b2ede950d44deaa4a3f9cfe8a72c4ab4e1a9051e5ca7
4
- data.tar.gz: 489f1c91e45864e4d88ab12bd32dd34f9eafc1c1e381d9d8be92884703e7f316
3
+ metadata.gz: acf499a815402aa955b457d3fa2db0a97a6018135c1c8814ecad4c4894eeaf62
4
+ data.tar.gz: 12c8dffb9dea52b6d335e0a38194344f780dbf9e7d13259d4c92892d92dbdc60
5
5
  SHA512:
6
- metadata.gz: 6b59254605c479585ecd5289dfd6e1f41c15aa4cde5888a4c1f834633a1f56e13d04fe1570762507adf249411d11234e0aaf102bd13e150e9f2dfbe98c40aa98
7
- data.tar.gz: c1af9bc61fb9ae9fc0ce9da4daef4b1c9dc44975f04a4a68ca07371f4109ea7c3569f508243ed8fd799c0cb0b354aa2b3e106f27083973ff9875b307bb5becd5
6
+ metadata.gz: 1cd2869a8e8709375a4b82fde759ab37a7e43a41f1ceedbdf1f563e68a6ac58976c950b8b1088ca8bd7c50ee81e42a8d744bd207d5a5819d23d967fa6be8f242
7
+ data.tar.gz: 844432fe9092b9918d238ce1ff730dd4028993c087665f26c8361518f49d7c5a189362a7a987c2fc3a92fcb81ecd94cadcd651d3311649fa62e4a79357718e80
data/README.md CHANGED
@@ -1 +1,93 @@
1
- dcidev_approval
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
+ ```
@@ -1,170 +1,150 @@
1
- module DcidevApproval
2
- def self.included base
3
- base.send :include, InstanceMethods
4
- base.extend ClassMethods
5
- end
6
-
7
- module InstanceMethods
8
-
9
- def changes_present?(changes)
10
- present = false
11
- changes.each do |k, v|
12
- begin
13
- if eval("self.#{k}") != v
14
- present = true
15
- break
16
- end
17
- rescue => _
18
- end
19
- end
20
- return present
21
- end
22
-
23
- def waiting_approval?
24
- %w[pending_update pending_delete].include?(self.change_status) || self.status == "waiting"
25
- end
26
-
27
- def pending_insert?
28
- self.change_status.nil? && %w[waiting rejected].include?(self.status)
29
- end
30
-
31
- def pending_update?
32
- self.change_status == "pending_update"
33
- end
34
-
35
- def pending_delete?
36
- self.change_status == "pending_delete"
37
- end
38
-
39
- def approved?
40
- self.status == "approved"
41
- end
42
-
43
- def rejected?
44
- self.status == "rejected"
45
- end
46
-
47
- def waiting?
48
- self.status == "waiting"
49
- end
50
-
51
- def last_modified_by
52
- # p self.audit_trail
53
- if self.try(:change_status).present? && self.try(:change_status) == 'pending_delete'
54
- log = self.activity_logs.where("activity LIKE '%delete%'").limit(1).order(created_at: :desc).try(:first)
55
- else
56
- log = self.activity_logs.where("activity LIKE '%edit%'").limit(1).order(created_at: :desc).try(:first)
57
- end
58
- {
59
- modified_by: log.present? ? log.try(:agent).try(:name).to_s + " (#{log.try(:agent).try(:username).to_s} | #{log.try(:agent).try(:roles).try(:first).try(:name)})" : "System",
60
- modified_at: log.present? ? log.try(:created_at) || self.try(:updated_at) || self.try(:created_at) : nil
61
- }
62
- end
63
-
64
- def created_by
65
- log = self.activity_logs.try(:first)
66
- {
67
- created_by: log.present? && log.try(:agent).try(:name).present? ? log.try(:agent).try(:name).to_s + " (#{log.try(:agent).try(:username).to_s} | #{log.try(:agent).try(:roles).try(:first).try(:name)})" : "System",
68
- created_at: self.try(:created_at) || log.try(:created_at)
69
- }
70
- end
71
-
72
- def last_approved_by
73
- last_approve = self.activity_logs.where("activity LIKE '%approv%'").limit(1).order(created_at: :desc).try(:first)
74
- last_entry = self.activity_logs.last
75
- {
76
- approved_by: last_approve.try(:id) == last_entry.try(:id) ? last_approve.try(:agent).try(:name).to_s + " (#{last_approve.try(:agent).try(:username).to_s} | #{last_approve.try(:agent).try(:roles).try(:first).try(:name)})" : nil,
77
- approved_at: last_approve.try(:id) == last_entry.try(:id) ? last_approve.try(:created_at) : nil
78
- }
79
- end
80
-
81
- def approve_changes
82
-
83
- if self.change_status.nil? && %w[waiting rejected].include?(self.status)
84
- raise self.errors.full_messages.join(", ") unless self.update(status: :approved, data_changes: nil, change_status: nil)
85
- # ActivityLog.write("Approve insert to #{self.class.to_s}", request, agent, menu, self) if params.log
86
- # self.delay(queue: "reorder_#{self.id}", priority: 0).reorder if self.class.column_names.include?("view_order")
87
-
88
- end
89
- if self.change_status == "pending_update"
90
- raise self.errors.full_messages.join(", ") unless self.update_by_params(self.data_changes, false)
91
- raise self.errors.full_messages.join(", ") unless self.update(status: :approved, data_changes: nil, change_status: nil)
92
- # ActivityLog.write("Approve update to #{self.class.to_s}", request, agent, menu, self) if params.log
93
- # self.delay(queue: "reorder_#{self.id}", priority: 0).reorder if self.class.column_names.include?("view_order")
1
+ # frozen_string_literal: true
94
2
 
95
- elsif self.change_status == "pending_delete"
96
- raise self.errors.full_messages.join(", ") unless self.update(change_status: nil, data_changes: nil)
97
- ActiveRecord::Base.transaction do
98
- # ActivityLog.write("Approve delete to #{self.class.to_s}", request, agent, menu, self) if params.log
99
- self.try(:destroy)
3
+ module DcidevApproval
4
+ def self.included(base)
5
+ base.send :include, InstanceMethods
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ module InstanceMethods
10
+ def changes_present?(changes)
11
+ present = false
12
+ changes.each do |k, v|
13
+ if eval("self.#{k}") != v
14
+ present = true
15
+ break
16
+ end
17
+ rescue StandardError => _e
18
+ end
19
+ present
20
+ end
21
+
22
+ def waiting_approval?
23
+ %w[pending_update pending_delete].include?(change_status) || status == 'waiting'
24
+ end
25
+
26
+ def pending_insert?
27
+ change_status.nil? && %w[waiting rejected].include?(status)
28
+ end
29
+
30
+ def pending_update?
31
+ change_status == 'pending_update'
32
+ end
33
+
34
+ def pending_delete?
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'
48
+ end
49
+
50
+ def last_modified_by
51
+ log = audit_trails.where(activity_type: %w[update delete]).limit(1).try(:first)
52
+ {
53
+ modified_by: log.present? ? log.try(:agent).try(:name).to_s + " (#{log.try(:agent).try(:roles).try(:first).try(:name)})" : nil,
54
+ modified_at: log.present? ? log.try(:created_at) || try(:updated_at) || try(:created_at) : nil
55
+ }
56
+ end
57
+
58
+ def created_by
59
+ log = audit_trails(:asc).where(activity_type: :create).limit(1).try(:first)
60
+ {
61
+ created_by: log.present? && log.try(:agent).try(:name).present? ? log.try(:agent).try(:name).to_s + " (#{log.try(:agent_role).try(:name)})" : 'System',
62
+ created_at: self.try(:created_at) || log.try(:created_at)
63
+ }
64
+ end
65
+
66
+ def last_approved_by
67
+ last_approve = audit_trails.where(activity_type: :approve).limit(1).try(:first)
68
+ last_entry = audit_trails.limit(1).order(created_at: :desc).try(:first)
69
+ {
70
+ 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,
71
+ approved_at: last_approve.try(:id) == last_entry.try(:id) ? last_approve.try(:created_at) : nil
72
+ }
73
+ end
74
+
75
+ def approve_changes
76
+ if change_status.nil? && %w[waiting rejected].include?(status) && !update(status: :approved, data_changes: nil, change_status: nil)
77
+ raise errors.full_messages.join(', ')
78
+ end
79
+
80
+ case change_status
81
+ when 'pending_update'
82
+ raise errors.full_messages.join(', ') unless update_by_params(data_changes, false)
83
+ raise errors.full_messages.join(', ') unless update(status: :approved, data_changes: nil, change_status: nil)
84
+ when 'pending_delete'
85
+ destroy
86
+ end
87
+ end
88
+
89
+ def delete_changes
90
+ raise errors.full_messages.join(', ') unless update(data_changes: nil, change_status: nil, status: status == 'waiting' ? :rejected : :approved)
91
+ end
92
+
93
+ def edit_data(params, agent, bypass = true)
94
+ raise 'data still waiting for approval' if waiting_approval?
95
+
96
+ if bypass
97
+ raise errors.full_messages.join(', ') unless update_by_params(params, false)
98
+ elsif changes_present?(params)
99
+ ActiveRecord::Base.transaction do
100
+ data = agent.is_admin? || status == 'waiting' ? params : { change_status: :pending_update, data_changes: agent.is_admin? ? nil : params }
101
+ raise errors.full_messages.join(', ') unless update_by_params(data, false)
102
+ end
103
+ end
104
+ yield self
105
+ end
106
+
107
+ def approval(params)
108
+ case params.status
109
+ when 'approved'
110
+ approve_changes
111
+ when 'rejected'
112
+ delete_changes
113
+ end
114
+ yield self
115
+ end
116
+
117
+ def delete_data(agent, bypass = true)
118
+ raise 'data still waiting for approval' if waiting_approval?
119
+
120
+ if bypass || agent.is_admin?
121
+ ActiveRecord::Base.transaction do
122
+ raise errors.full_messages.join(', ') unless destroy
123
+ end
124
+ else
125
+ raise errors.full_messages.join(', ') unless update(change_status: :pending_delete)
126
+ end
127
+ yield true
100
128
  end
101
- end
102
- end
103
-
104
- def delete_changes
105
- # return unless %w[pending_update pending_delete].include? self.change_status
106
- raise self.errors.full_messages.join(", ") unless self.update(data_changes: nil, change_status: nil, status: self.status == "waiting" ? :rejected : :approved)
107
- # ActivityLog.write("Reject changes to #{self.class.to_s}", request, agent, menu, self) if params.log
108
- end
109
-
110
- def edit_data(params, agent, bypass = true)
111
- raise "data still waiting for approval" if self.waiting_approval?
112
- if bypass
113
- raise self.errors.full_messages.join(", ") unless self.update_by_params(params, false)
114
- # ActivityLog.write("Edit #{self.class.to_s}", request, agent, menu, self) if params.log
115
- else
116
- if self.changes_present?(params)
117
- ActiveRecord::Base.transaction do
118
- data = (agent.is_admin? || self.status == "waiting") ? params : { change_status: :pending_update, data_changes: agent.is_admin? ? nil : params }
119
- raise self.errors.full_messages.join(", ") unless self.update_by_params(data, false)
120
- end
121
- # ActivityLog.write("#{agent.is_admin? ? nil : "Request "}Edit #{self.class.to_s}", request, agent, menu, self) if params.log
122
- end
123
- end
124
- yield true
125
- end
126
-
127
- def approval(params)
128
- if params.status == "approved"
129
- self.approve_changes
130
- elsif params.status == "rejected"
131
- self.delete_changes
132
- end
133
- yield true
134
- end
135
-
136
- def delete_data(agent, bypass = true)
137
- raise "data still waiting for approval" if self.waiting_approval?
138
- if bypass || agent.is_admin?
139
- ActiveRecord::Base.transaction do
140
- # ActivityLog.write("Delete #{self.class.to_s}", request, agent, menu, self) if params.log
141
- raise self.errors.full_messages.join(", ") unless self.destroy
142
- end
143
- else
144
- raise self.errors.full_messages.join(", ") unless self.update(change_status: :pending_delete)
145
- # ActivityLog.write("Request Delete #{self.class.to_s}", request, agent, menu, self) if params.log
146
- end
147
- yield true
148
129
  end
149
- end
150
-
151
- module ClassMethods
152
- def create_data(params, agent, bypass = true)
153
- if bypass
154
- ActiveRecord::Base.transaction do
155
- data = params.merge!({ status: :approved })
156
- d = self.new_from_params(data)
157
- raise d.errors.full_messages.join(", ") unless d.save
158
- # ActivityLog.write("#{agent.is_admin? ? nil : "Request "} Add #{self.to_s}", request, agent, menu, d) if params.log
159
- end
160
- else
161
- d = self.new_from_params(params)
162
- d.status = agent.is_admin? ? :approved : :waiting
163
- raise d.errors.full_messages.join(", ") unless d.save
164
- # ActivityLog.write("Add #{self.to_s}", request, agent, menu, d) if params.log
165
- end
166
- yield d
130
+
131
+ module ClassMethods
132
+ def create_data(params, agent, bypass = true)
133
+ if bypass
134
+ ActiveRecord::Base.transaction do
135
+ data = params.merge!({ status: :approved })
136
+ d = new_from_params(data)
137
+ raise d.errors.full_messages.join(', ') unless d.save
138
+
139
+ yield d
140
+ end
141
+ else
142
+ d = new_from_params(params)
143
+ d.status = agent.is_admin? ? :approved : :waiting
144
+ raise d.errors.full_messages.join(', ') unless d.save
145
+
146
+ yield d
147
+ end
148
+ end
167
149
  end
168
- end
169
150
  end
170
-
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.8
4
+ version: 0.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Punto Damar P
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-04 00:00:00.000000000 Z
11
+ date: 2022-06-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dcidev_active_record