approval 0.3.6 → 0.3.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +21 -0
- data/app/models/approval/application_record.rb +5 -0
- data/app/models/approval/comment.rb +12 -0
- data/app/models/approval/item.rb +72 -0
- data/app/models/approval/request.rb +39 -0
- data/app/models/approval/request_form/base.rb +47 -0
- data/app/models/approval/request_form/create.rb +21 -0
- data/app/models/approval/request_form/destroy.rb +21 -0
- data/app/models/approval/request_form/update.rb +22 -0
- data/app/models/approval/respond_form/approve.rb +19 -0
- data/app/models/approval/respond_form/base.rb +45 -0
- data/app/models/approval/respond_form/cancel.rb +16 -0
- data/app/models/approval/respond_form/reject.rb +18 -0
- data/app/models/concerns/approval/acts_as_resource.rb +26 -0
- data/app/models/concerns/approval/acts_as_user.rb +34 -0
- data/app/models/concerns/approval/mixins.rb +12 -0
- data/db/migrate/20180409000000_create_approval_tables.rb +43 -0
- data/lib/approval/version.rb +1 -1
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 036447a5b614f818fa6a3ace7ee80d56c736d307bf1b8020cca81c2e6458ac07
|
4
|
+
data.tar.gz: 95feed0024428c045f96adaa675618751481ac9111f3aeafb79e02d6d9e17856
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: afdc581773b236d893187061146bbb58e4c3e5760eda31b74a73105c52ee22b3647ad2400f5b31421cf3224b9c5920fcc0f4ae572101721f741e746fd567f19b
|
7
|
+
data.tar.gz: 05a14a3c41748d87ecbdc9365b3198a7f750be241385966c5a5b55cc53c7c7874fdcb4092c933195a1f43b01dff5c81c91fbdd4ceb5c5e81c4df50fd67f58f8b
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Yoshiyuki Hirano
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Approval
|
2
|
+
class Comment < ApplicationRecord
|
3
|
+
self.table_name = :approval_comments
|
4
|
+
|
5
|
+
def self.define_user_association
|
6
|
+
belongs_to :user, class_name: Approval.config.user_class_name
|
7
|
+
end
|
8
|
+
|
9
|
+
belongs_to :request, class_name: :"Approval::Request", inverse_of: :comments
|
10
|
+
validates :content, presence: true, length: { maximum: Approval.config.comment_maximum }
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Approval
|
2
|
+
class Item < ApplicationRecord
|
3
|
+
class UnexistResource < StandardError; end
|
4
|
+
|
5
|
+
self.table_name = :approval_items
|
6
|
+
EVENTS = %w[create update destroy].freeze
|
7
|
+
|
8
|
+
belongs_to :request, class_name: :"Approval::Request", inverse_of: :items
|
9
|
+
belongs_to :resource, polymorphic: true, optional: true
|
10
|
+
|
11
|
+
serialize :params, Hash
|
12
|
+
|
13
|
+
validates :resource_type, presence: true
|
14
|
+
validates :event, presence: true, inclusion: { in: EVENTS }
|
15
|
+
|
16
|
+
with_options unless: :create_event? do
|
17
|
+
validates :resource_id, presence: true
|
18
|
+
end
|
19
|
+
|
20
|
+
with_options if: :update_event? do
|
21
|
+
validates :params, presence: true
|
22
|
+
end
|
23
|
+
|
24
|
+
validate :ensure_resource_be_valid
|
25
|
+
|
26
|
+
EVENTS.each do |event_name|
|
27
|
+
define_method "#{event_name}_event?" do
|
28
|
+
event_name.to_s == event.to_s
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def apply
|
33
|
+
case event
|
34
|
+
when "create"
|
35
|
+
resource_model.create!(params).tap do |created_resource|
|
36
|
+
update!(resource_id: created_resource.id)
|
37
|
+
end
|
38
|
+
when "update"
|
39
|
+
raise UnexistResource unless resource
|
40
|
+
|
41
|
+
resource.update!(params)
|
42
|
+
when "destroy"
|
43
|
+
raise UnexistResource unless resource
|
44
|
+
|
45
|
+
resource.destroy
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def resource_model
|
52
|
+
@resource_model ||= resource_type.to_s.safe_constantize
|
53
|
+
end
|
54
|
+
|
55
|
+
def ensure_resource_be_valid
|
56
|
+
return if resource_model.nil? || destroy_event?
|
57
|
+
|
58
|
+
record = if resource_id.present?
|
59
|
+
resource_model.find(resource_id).tap {|m| m.assign_attributes(params) }
|
60
|
+
else
|
61
|
+
resource_model.new(params || {})
|
62
|
+
end
|
63
|
+
|
64
|
+
unless record.valid?
|
65
|
+
errors.add(:base, :invalid)
|
66
|
+
record.errors.full_messages.each do |message|
|
67
|
+
request.errors.add(:base, message)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Approval
|
2
|
+
class Request < ApplicationRecord
|
3
|
+
self.table_name = :approval_requests
|
4
|
+
|
5
|
+
def self.define_user_association
|
6
|
+
belongs_to :request_user, class_name: Approval.config.user_class_name
|
7
|
+
belongs_to :respond_user, class_name: Approval.config.user_class_name, optional: true
|
8
|
+
end
|
9
|
+
|
10
|
+
has_many :comments, class_name: :"Approval::Comment", inverse_of: :request, dependent: :destroy
|
11
|
+
has_many :items, class_name: :"Approval::Item", inverse_of: :request, dependent: :destroy
|
12
|
+
|
13
|
+
enum state: { pending: 0, cancelled: 1, approved: 2, rejected: 3 }
|
14
|
+
|
15
|
+
scope :recently, -> { order(id: :desc) }
|
16
|
+
|
17
|
+
validates :state, presence: true
|
18
|
+
validates :respond_user, presence: true, unless: :pending?
|
19
|
+
validates :comments, presence: true
|
20
|
+
validates :items, presence: true
|
21
|
+
|
22
|
+
validates_associated :comments
|
23
|
+
validates_associated :items
|
24
|
+
|
25
|
+
validate :ensure_state_was_pending
|
26
|
+
|
27
|
+
before_create do
|
28
|
+
self.requested_at = Time.current
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def ensure_state_was_pending
|
34
|
+
return unless persisted?
|
35
|
+
|
36
|
+
errors.add(:base, :already_performed) if state_was != "pending"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Approval
|
2
|
+
module RequestForm
|
3
|
+
class Base
|
4
|
+
include ::ActiveModel::Model
|
5
|
+
|
6
|
+
attr_accessor :user, :reason, :records
|
7
|
+
|
8
|
+
def initialize(user:, reason:, records:)
|
9
|
+
@user = user
|
10
|
+
@reason = reason
|
11
|
+
@records = records
|
12
|
+
end
|
13
|
+
|
14
|
+
with_options presence: true do
|
15
|
+
validates :user
|
16
|
+
validates :reason, length: { maximum: Approval.config.comment_maximum }
|
17
|
+
validates :records
|
18
|
+
end
|
19
|
+
|
20
|
+
def save
|
21
|
+
return false unless valid?
|
22
|
+
|
23
|
+
prepare(&:save)
|
24
|
+
end
|
25
|
+
|
26
|
+
def save!
|
27
|
+
raise ::ActiveRecord::RecordInvalid unless valid?
|
28
|
+
|
29
|
+
prepare(&:save!)
|
30
|
+
end
|
31
|
+
|
32
|
+
def request
|
33
|
+
@request ||= user.approval_requests.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def error_full_messages
|
37
|
+
[errors, request.errors].flat_map(&:full_messages)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def prepare
|
43
|
+
raise NotImplementedError, "you must implement #{self.class}##{__method__}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Approval
|
2
|
+
module RequestForm
|
3
|
+
class Create < Base
|
4
|
+
private
|
5
|
+
|
6
|
+
def prepare
|
7
|
+
::Approval::Request.transaction do
|
8
|
+
request.comments.new(user_id: user.id, content: reason)
|
9
|
+
Array(records).each do |record|
|
10
|
+
request.items.new(
|
11
|
+
event: "create",
|
12
|
+
resource_type: record.class.to_s,
|
13
|
+
params: record.create_params_for_approval,
|
14
|
+
)
|
15
|
+
end
|
16
|
+
yield(request)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Approval
|
2
|
+
module RequestForm
|
3
|
+
class Destroy < Base
|
4
|
+
private
|
5
|
+
|
6
|
+
def prepare
|
7
|
+
::Approval::Request.transaction do
|
8
|
+
request.comments.new(user_id: user.id, content: reason)
|
9
|
+
Array(records).each do |record|
|
10
|
+
request.items.new(
|
11
|
+
event: "destroy",
|
12
|
+
resource_type: record.class.to_s,
|
13
|
+
resource_id: record.id,
|
14
|
+
)
|
15
|
+
end
|
16
|
+
yield(request)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Approval
|
2
|
+
module RequestForm
|
3
|
+
class Update < Base
|
4
|
+
private
|
5
|
+
|
6
|
+
def prepare
|
7
|
+
::Approval::Request.transaction do
|
8
|
+
request.comments.new(user_id: user.id, content: reason)
|
9
|
+
Array(records).each do |record|
|
10
|
+
request.items.new(
|
11
|
+
event: "update",
|
12
|
+
resource_type: record.class.to_s,
|
13
|
+
resource_id: record.id,
|
14
|
+
params: record.update_params_for_approval,
|
15
|
+
)
|
16
|
+
end
|
17
|
+
yield(request)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Approval
|
2
|
+
module RespondForm
|
3
|
+
class Approve < Base
|
4
|
+
validate :ensure_user_cannot_respond_to_my_request
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
def prepare
|
9
|
+
::Approval::Request.transaction do
|
10
|
+
request.lock!
|
11
|
+
request.assign_attributes(state: :approved, approved_at: Time.current, respond_user_id: user.id)
|
12
|
+
request.comments.new(user_id: user.id, content: reason)
|
13
|
+
request.items.each(&:apply)
|
14
|
+
yield(request)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Approval
|
2
|
+
module RespondForm
|
3
|
+
class Base
|
4
|
+
include ::ActiveModel::Model
|
5
|
+
|
6
|
+
attr_accessor :user, :reason, :request
|
7
|
+
|
8
|
+
def initialize(user:, reason:, request:)
|
9
|
+
@user = user
|
10
|
+
@reason = reason
|
11
|
+
@request = request
|
12
|
+
end
|
13
|
+
|
14
|
+
with_options presence: true do
|
15
|
+
validates :user
|
16
|
+
validates :reason, length: { maximum: Approval.config.comment_maximum }
|
17
|
+
validates :request
|
18
|
+
end
|
19
|
+
|
20
|
+
def save
|
21
|
+
return false unless valid?
|
22
|
+
|
23
|
+
prepare(&:save)
|
24
|
+
end
|
25
|
+
|
26
|
+
def save!
|
27
|
+
raise ::ActiveRecord::RecordInvalid unless valid?
|
28
|
+
|
29
|
+
prepare(&:save!)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def prepare
|
35
|
+
raise NotImplementedError, "you must implement #{self.class}##{__method__}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def ensure_user_cannot_respond_to_my_request
|
39
|
+
return if Approval.config.permit_to_respond_to_own_request?
|
40
|
+
|
41
|
+
errors.add(:user, :cannot_respond_to_own_request) if user.try(:id) == request.request_user_id
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Approval
|
2
|
+
module RespondForm
|
3
|
+
class Cancel < Base
|
4
|
+
private
|
5
|
+
|
6
|
+
def prepare
|
7
|
+
::Approval::Request.transaction do
|
8
|
+
request.lock!
|
9
|
+
request.assign_attributes(state: :cancelled, cancelled_at: Time.current, respond_user_id: user.id)
|
10
|
+
request.comments.new(user_id: user.id, content: reason)
|
11
|
+
yield(request)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Approval
|
2
|
+
module RespondForm
|
3
|
+
class Reject < Base
|
4
|
+
validate :ensure_user_cannot_respond_to_my_request
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
def prepare
|
9
|
+
::Approval::Request.transaction do
|
10
|
+
request.lock!
|
11
|
+
request.assign_attributes(state: :rejected, rejected_at: Time.current, respond_user_id: user.id)
|
12
|
+
request.comments.new(user_id: user.id, content: reason)
|
13
|
+
yield(request)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Approval
|
2
|
+
module ActsAsResource
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_attribute :approval_ignore_fields
|
7
|
+
self.approval_ignore_fields = %w[id created_at updated_at]
|
8
|
+
|
9
|
+
has_many :approval_items, class_name: :"Approval::Item", as: :resource
|
10
|
+
end
|
11
|
+
|
12
|
+
class_methods do
|
13
|
+
def assign_ignore_fields(ignore_fields = [])
|
14
|
+
self.approval_ignore_fields = approval_ignore_fields.concat(ignore_fields).map(&:to_s).uniq
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_params_for_approval
|
19
|
+
attributes.except(*approval_ignore_fields).compact
|
20
|
+
end
|
21
|
+
|
22
|
+
def update_params_for_approval
|
23
|
+
changes.except(*approval_ignore_fields).each_with_object({}) {|(k, v), h| h[k] = v.last }.compact
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Approval
|
2
|
+
module ActsAsUser
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
has_many :approval_requests, class_name: :"Approval::Request", foreign_key: :request_user_id
|
7
|
+
has_many :approval_comments, class_name: :"Approval::Comment", foreign_key: :user_id
|
8
|
+
end
|
9
|
+
|
10
|
+
def request_for_create(records, reason:)
|
11
|
+
Approval::RequestForm::Create.new(user: self, reason: reason, records: records)
|
12
|
+
end
|
13
|
+
|
14
|
+
def request_for_update(records, reason:)
|
15
|
+
Approval::RequestForm::Update.new(user: self, reason: reason, records: records)
|
16
|
+
end
|
17
|
+
|
18
|
+
def request_for_destroy(records, reason:)
|
19
|
+
Approval::RequestForm::Destroy.new(user: self, reason: reason, records: records)
|
20
|
+
end
|
21
|
+
|
22
|
+
def cancel_request(request, reason:)
|
23
|
+
Approval::RespondForm::Cancel.new(user: self, reason: reason, request: request)
|
24
|
+
end
|
25
|
+
|
26
|
+
def approve_request(request, reason:)
|
27
|
+
Approval::RespondForm::Approve.new(user: self, reason: reason, request: request)
|
28
|
+
end
|
29
|
+
|
30
|
+
def reject_request(request, reason:)
|
31
|
+
Approval::RespondForm::Reject.new(user: self, reason: reason, request: request)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class CreateApprovalTables < ActiveRecord::Migration[5.0]
|
2
|
+
def change
|
3
|
+
create_table :approval_requests do |t|
|
4
|
+
t.integer :request_user_id, null: false
|
5
|
+
t.integer :respond_user_id
|
6
|
+
t.integer :state, null: false, limit: 1, default: 0
|
7
|
+
t.datetime :requested_at, null: false
|
8
|
+
t.datetime :cancelled_at
|
9
|
+
t.datetime :approved_at
|
10
|
+
t.datetime :rejected_at
|
11
|
+
|
12
|
+
t.timestamps
|
13
|
+
|
14
|
+
t.index :request_user_id
|
15
|
+
t.index :respond_user_id
|
16
|
+
t.index :state
|
17
|
+
end
|
18
|
+
|
19
|
+
create_table :approval_items do |t|
|
20
|
+
t.integer :request_id, null: false
|
21
|
+
t.integer :resource_id
|
22
|
+
t.string :resource_type, null: false
|
23
|
+
t.string :event, null: false
|
24
|
+
t.text :params
|
25
|
+
|
26
|
+
t.timestamps
|
27
|
+
|
28
|
+
t.index :request_id
|
29
|
+
t.index [:resource_id, :resource_type]
|
30
|
+
end
|
31
|
+
|
32
|
+
create_table :approval_comments do |t|
|
33
|
+
t.integer :request_id, null: false
|
34
|
+
t.integer :user_id, null: false
|
35
|
+
t.text :content, null: false
|
36
|
+
|
37
|
+
t.timestamps
|
38
|
+
|
39
|
+
t.index :request_id
|
40
|
+
t.index :user_id
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/approval/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: approval
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yoshiyuki Hirano
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-04-
|
11
|
+
date: 2018-04-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -31,9 +31,26 @@ executables: []
|
|
31
31
|
extensions: []
|
32
32
|
extra_rdoc_files: []
|
33
33
|
files:
|
34
|
+
- LICENSE.txt
|
34
35
|
- README.md
|
36
|
+
- app/models/approval/application_record.rb
|
37
|
+
- app/models/approval/comment.rb
|
38
|
+
- app/models/approval/item.rb
|
39
|
+
- app/models/approval/request.rb
|
40
|
+
- app/models/approval/request_form/base.rb
|
41
|
+
- app/models/approval/request_form/create.rb
|
42
|
+
- app/models/approval/request_form/destroy.rb
|
43
|
+
- app/models/approval/request_form/update.rb
|
44
|
+
- app/models/approval/respond_form/approve.rb
|
45
|
+
- app/models/approval/respond_form/base.rb
|
46
|
+
- app/models/approval/respond_form/cancel.rb
|
47
|
+
- app/models/approval/respond_form/reject.rb
|
48
|
+
- app/models/concerns/approval/acts_as_resource.rb
|
49
|
+
- app/models/concerns/approval/acts_as_user.rb
|
50
|
+
- app/models/concerns/approval/mixins.rb
|
35
51
|
- config/locales/en.yml
|
36
52
|
- config/locales/ja.yml
|
53
|
+
- db/migrate/20180409000000_create_approval_tables.rb
|
37
54
|
- lib/approval.rb
|
38
55
|
- lib/approval/config.rb
|
39
56
|
- lib/approval/engine.rb
|