dcidev_approval 0.0.7 → 0.0.11

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 +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: []