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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +93 -1
  3. data/lib/dcidev_approval.rb +75 -63
  4. metadata +7 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b0f4b607b87d0a4885cb7eed7fa8a669e82e9e6b2fb79f9af7fdf79410b1fcff
4
- data.tar.gz: 24ce7542734d119282afc7241a14125c64e1f90000474986b79ea5321f308f66
3
+ metadata.gz: 047563f0a296d733db4828b485a0446cd2757bb3f915809808555d44719a5e44
4
+ data.tar.gz: 2a3fb2c40952698d8ea669a33f67bb503e5a0dfb44d6c900caef2d4ef27e1231
5
5
  SHA512:
6
- metadata.gz: 3ea5cf3de84456bb54c7452e61f871829aa6717325815dd6d153e030ee31a5e2c63db09cc93a9f5783b0630057b86af04bd6cf8dfad4e0165fd0a9f875e15bec
7
- data.tar.gz: e5bee4b7d43226dad75d04ac0ba0b4fd6380d6b8d3bc1fd3f27ff8c98c4e951af61fab0f5725fabda1200eea61edf88c260b3811535b3f21f8b5a49f69700f19
6
+ metadata.gz: b9ef11b07ae7f35c5c21c1a1c93da4565077570d5b277786a87b913d690b9b4885703cbed14337e8ecc9c16c5c0e3f822fd6359776cac2db608069dff51619d2
7
+ data.tar.gz: '099759cd2d503012da3ac66a22a28fee1b67b2d07e67f0c421ae348477fd7db1898a8fed75087d2365f9dff61cc3c189219fc9184105a0fb8e11990d55684856'
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,135 +1,146 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DcidevApproval
2
- def self.included base
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
- begin
13
- if eval("self.#{k}") != v
14
- present = true
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
- return present
19
+ present
21
20
  end
22
21
 
23
22
  def waiting_approval?
24
- %w[pending_update pending_delete].include?(self.change_status) || self.status == "waiting"
23
+ %w[pending_update pending_delete].include?(change_status) || status == 'waiting'
25
24
  end
26
25
 
27
26
  def pending_insert?
28
- self.change_status.nil? && %w[waiting rejected].include?(self.status)
27
+ change_status.nil? && %w[waiting rejected].include?(status)
29
28
  end
30
29
 
31
30
  def pending_update?
32
- self.change_status == "pending_update"
31
+ change_status == 'pending_update'
33
32
  end
34
33
 
35
34
  def pending_delete?
36
- self.change_status == "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'
37
48
  end
38
49
 
39
50
  def last_modified_by
40
51
  # p self.audit_trail
41
- if self.try(:change_status).present? && self.try(:change_status) == 'pending_delete'
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).to_s} | #{log.try(:agent).try(:roles).try(:first).try(:name)})" : "System",
48
- modified_at: log.present? ? log.try(:created_at) || self.try(:updated_at) || self.try(:created_at) : nil
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 = self.activity_logs.try(:first)
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(:agent).try(:username).to_s} | #{log.try(:agent).try(:roles).try(:first).try(:name)})" : "System",
56
- created_at: self.try(:created_at) || log.try(: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 = self.activity_logs.where("activity LIKE '%approv%'").limit(1).order(created_at: :desc).try(:first)
62
- last_entry = self.activity_logs.last
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(:agent).try(:username).to_s} | #{last_approve.try(:agent).try(:roles).try(:first).try(:name)})" : nil,
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
- if self.change_status.nil? && %w[waiting rejected].include?(self.status)
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
- elsif self.change_status == "pending_delete"
84
- raise self.errors.full_messages.join(", ") unless self.update(change_status: nil, data_changes: nil)
85
- ActiveRecord::Base.transaction do
86
- # ActivityLog.write("Approve delete to #{self.class.to_s}", request, agent, menu, self) if params.log
87
- self.try(:destroy)
88
- end
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 self.errors.full_messages.join(", ") unless self.update(data_changes: nil, change_status: nil, status: self.status == "waiting" ? :rejected : :approved)
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 "data still waiting for approval" if self.waiting_approval?
103
+ raise 'data still waiting for approval' if waiting_approval?
104
+
100
105
  if bypass
101
- raise self.errors.full_messages.join(", ") unless self.update_by_params(params, false)
102
- # ActivityLog.write("Edit #{self.class.to_s}", request, agent, menu, self) if params.log
103
- else
104
- if self.changes_present?(params)
105
- ActiveRecord::Base.transaction do
106
- data = (agent.is_admin? || self.status == "waiting") ? params : { change_status: :pending_update, data_changes: agent.is_admin? ? nil : params }
107
- raise self.errors.full_messages.join(", ") unless self.update_by_params(data, false)
108
- end
109
- # ActivityLog.write("#{agent.is_admin? ? nil : "Request "}Edit #{self.class.to_s}", request, agent, menu, self) if params.log
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
- if params.status == "approved"
117
- self.approve_changes
118
- elsif params.status == "rejected"
119
- self.delete_changes
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 "data still waiting for approval" if self.waiting_approval?
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 self.errors.full_messages.join(", ") unless self.destroy
140
+ raise errors.full_messages.join(', ') unless destroy
130
141
  end
131
142
  else
132
- raise self.errors.full_messages.join(", ") unless self.update(change_status: :pending_delete)
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 = self.new_from_params(data)
145
- raise d.errors.full_messages.join(", ") unless d.save
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 = self.new_from_params(params)
162
+ d = new_from_params(params)
151
163
  d.status = agent.is_admin? ? :approved : :waiting
152
- raise d.errors.full_messages.join(", ") unless d.save
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.7
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-02-03 00:00:00.000000000 Z
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.0.6
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: []