approval 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +38 -16
- data/Rakefile +9 -3
- data/lib/approval/config.rb +19 -0
- data/lib/approval/engine.rb +12 -0
- data/lib/approval/mixins/resource.rb +24 -0
- data/lib/approval/mixins/user.rb +36 -0
- data/lib/approval/mixins.rb +19 -0
- data/lib/approval/models/approval/comment.rb +10 -0
- data/lib/approval/models/approval/item.rb +60 -0
- data/lib/approval/models/approval/request.rb +43 -0
- data/lib/approval/models/approval/request_form/base.rb +37 -0
- data/lib/approval/models/approval/request_form/create.rb +22 -0
- data/lib/approval/models/approval/request_form/destroy.rb +22 -0
- data/lib/approval/models/approval/request_form/update.rb +23 -0
- data/lib/approval/models/approval/request_form.rb +4 -0
- data/lib/approval/models/approval/respond_form/approve.rb +19 -0
- data/lib/approval/models/approval/respond_form/base.rb +42 -0
- data/lib/approval/models/approval/respond_form/cancel.rb +16 -0
- data/lib/approval/models/approval/respond_form/reject.rb +18 -0
- data/lib/approval/models/approval/respond_form.rb +4 -0
- data/lib/approval/version.rb +1 -1
- data/lib/approval.rb +11 -3
- data/lib/generators/approval/USAGE +0 -0
- data/lib/generators/approval/install_generator.rb +45 -0
- data/lib/generators/approval/templates/create_approval_comments.rb.tt +14 -0
- data/lib/generators/approval/templates/create_approval_items.rb.tt +17 -0
- data/lib/generators/approval/templates/create_approval_requests.rb.tt +19 -0
- data/lib/generators/approval/templates/initializer.rb +4 -0
- data/spec/lib/config_spec.rb +10 -0
- data/spec/lib/version_spec.rb +7 -0
- data/spec/models/book_spec.rb +27 -0
- data/spec/models/comment_spec.rb +9 -0
- data/spec/models/item_spec.rb +114 -0
- data/spec/models/request_form/base_spec.rb +54 -0
- data/spec/models/request_form/create_spec.rb +23 -0
- data/spec/models/request_form/destroy_spec.rb +23 -0
- data/spec/models/request_form/update_spec.rb +23 -0
- data/spec/models/request_form_spec.rb +4 -0
- data/spec/models/request_spec.rb +40 -0
- data/spec/models/respond_form/approve_spec.rb +4 -0
- data/spec/models/respond_form/base_spec.rb +84 -0
- data/spec/models/respond_form/cancel_spec.rb +4 -0
- data/spec/models/respond_form/reject_spec.rb +4 -0
- data/spec/models/respond_form_spec.rb +4 -0
- data/spec/models/user_spec.rb +48 -0
- data/spec/rails/rails-4.2.9/README.rdoc +28 -0
- data/spec/rails/rails-4.2.9/Rakefile +6 -0
- data/spec/rails/rails-4.2.9/app/assets/javascripts/application.js +16 -0
- data/spec/rails/rails-4.2.9/app/assets/stylesheets/application.css +15 -0
- data/spec/rails/rails-4.2.9/app/controllers/application_controller.rb +5 -0
- data/spec/rails/rails-4.2.9/app/helpers/application_helper.rb +2 -0
- data/spec/rails/rails-4.2.9/app/models/book.rb +3 -0
- data/spec/rails/rails-4.2.9/app/models/user.rb +3 -0
- data/spec/rails/rails-4.2.9/app/views/layouts/application.html.erb +14 -0
- data/spec/rails/rails-4.2.9/bin/bundle +3 -0
- data/spec/rails/rails-4.2.9/bin/rails +4 -0
- data/spec/rails/rails-4.2.9/bin/rake +4 -0
- data/spec/rails/rails-4.2.9/bin/setup +29 -0
- data/spec/rails/rails-4.2.9/config/application.rb +35 -0
- data/spec/rails/rails-4.2.9/config/boot.rb +3 -0
- data/spec/rails/rails-4.2.9/config/database.yml +25 -0
- data/spec/rails/rails-4.2.9/config/environment.rb +8 -0
- data/spec/rails/rails-4.2.9/config/environments/development.rb +28 -0
- data/spec/rails/rails-4.2.9/config/environments/production.rb +67 -0
- data/spec/rails/rails-4.2.9/config/environments/test.rb +42 -0
- data/spec/rails/rails-4.2.9/config/initializers/approval.rb +4 -0
- data/spec/rails/rails-4.2.9/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails/rails-4.2.9/config/initializers/cookies_serializer.rb +3 -0
- data/spec/rails/rails-4.2.9/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/rails/rails-4.2.9/config/initializers/inflections.rb +16 -0
- data/spec/rails/rails-4.2.9/config/initializers/mime_types.rb +4 -0
- data/spec/rails/rails-4.2.9/config/initializers/session_store.rb +3 -0
- data/spec/rails/rails-4.2.9/config/initializers/to_time_preserves_timezone.rb +10 -0
- data/spec/rails/rails-4.2.9/config/initializers/wrap_parameters.rb +14 -0
- data/spec/rails/rails-4.2.9/config/locales/en.yml +23 -0
- data/spec/rails/rails-4.2.9/config/routes.rb +56 -0
- data/spec/rails/rails-4.2.9/config/secrets.yml +22 -0
- data/spec/rails/rails-4.2.9/config.ru +4 -0
- data/spec/rails/rails-4.2.9/db/migrate/20170731151146_create_approval_requests.rb +19 -0
- data/spec/rails/rails-4.2.9/db/migrate/20170731151147_create_approval_comments.rb +14 -0
- data/spec/rails/rails-4.2.9/db/migrate/20170731151148_create_approval_items.rb +17 -0
- data/spec/rails/rails-4.2.9/db/migrate/20170731151149_create_users.rb +9 -0
- data/spec/rails/rails-4.2.9/db/migrate/20170731151150_create_books.rb +9 -0
- data/spec/rails/rails-4.2.9/db/schema.rb +68 -0
- data/spec/rails/rails-4.2.9/db/seeds.rb +7 -0
- data/spec/rails/rails-4.2.9/db/test.sqlite3 +0 -0
- data/spec/rails/rails-4.2.9/log/test.log +133 -0
- data/spec/rails/rails-4.2.9/public/404.html +67 -0
- data/spec/rails/rails-4.2.9/public/422.html +67 -0
- data/spec/rails/rails-4.2.9/public/500.html +66 -0
- data/spec/rails/rails-4.2.9/public/favicon.ico +0 -0
- data/spec/rails/rails-4.2.9/public/robots.txt +5 -0
- data/spec/rails/rails-5.1.2/README.md +24 -0
- data/spec/rails/rails-5.1.2/Rakefile +6 -0
- data/spec/rails/rails-5.1.2/app/assets/config/manifest.js +3 -0
- data/spec/rails/rails-5.1.2/app/assets/javascripts/application.js +15 -0
- data/spec/rails/rails-5.1.2/app/assets/javascripts/cable.js +13 -0
- data/spec/rails/rails-5.1.2/app/assets/stylesheets/application.css +15 -0
- data/spec/rails/rails-5.1.2/app/channels/application_cable/channel.rb +4 -0
- data/spec/rails/rails-5.1.2/app/channels/application_cable/connection.rb +4 -0
- data/spec/rails/rails-5.1.2/app/controllers/application_controller.rb +3 -0
- data/spec/rails/rails-5.1.2/app/helpers/application_helper.rb +2 -0
- data/spec/rails/rails-5.1.2/app/jobs/application_job.rb +2 -0
- data/spec/rails/rails-5.1.2/app/mailers/application_mailer.rb +4 -0
- data/spec/rails/rails-5.1.2/app/models/application_record.rb +3 -0
- data/spec/rails/rails-5.1.2/app/models/book.rb +3 -0
- data/spec/rails/rails-5.1.2/app/models/user.rb +3 -0
- data/spec/rails/rails-5.1.2/app/views/layouts/application.html.erb +14 -0
- data/spec/rails/rails-5.1.2/app/views/layouts/mailer.html.erb +13 -0
- data/spec/rails/rails-5.1.2/app/views/layouts/mailer.text.erb +1 -0
- data/spec/rails/rails-5.1.2/bin/bundle +3 -0
- data/spec/rails/rails-5.1.2/bin/rails +4 -0
- data/spec/rails/rails-5.1.2/bin/rake +4 -0
- data/spec/rails/rails-5.1.2/bin/setup +38 -0
- data/spec/rails/rails-5.1.2/bin/update +29 -0
- data/spec/rails/rails-5.1.2/bin/yarn +11 -0
- data/spec/rails/rails-5.1.2/config/application.rb +31 -0
- data/spec/rails/rails-5.1.2/config/boot.rb +3 -0
- data/spec/rails/rails-5.1.2/config/cable.yml +10 -0
- data/spec/rails/rails-5.1.2/config/database.yml +25 -0
- data/spec/rails/rails-5.1.2/config/environment.rb +8 -0
- data/spec/rails/rails-5.1.2/config/environments/development.rb +47 -0
- data/spec/rails/rails-5.1.2/config/environments/production.rb +83 -0
- data/spec/rails/rails-5.1.2/config/environments/test.rb +42 -0
- data/spec/rails/rails-5.1.2/config/initializers/application_controller_renderer.rb +6 -0
- data/spec/rails/rails-5.1.2/config/initializers/approval.rb +4 -0
- data/spec/rails/rails-5.1.2/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails/rails-5.1.2/config/initializers/cookies_serializer.rb +5 -0
- data/spec/rails/rails-5.1.2/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/rails/rails-5.1.2/config/initializers/inflections.rb +16 -0
- data/spec/rails/rails-5.1.2/config/initializers/mime_types.rb +4 -0
- data/spec/rails/rails-5.1.2/config/initializers/wrap_parameters.rb +14 -0
- data/spec/rails/rails-5.1.2/config/locales/en.yml +33 -0
- data/spec/rails/rails-5.1.2/config/puma.rb +56 -0
- data/spec/rails/rails-5.1.2/config/routes.rb +3 -0
- data/spec/rails/rails-5.1.2/config/secrets.yml +32 -0
- data/spec/rails/rails-5.1.2/config.ru +5 -0
- data/spec/rails/rails-5.1.2/db/migrate/20170731101409_create_approval_requests.rb +19 -0
- data/spec/rails/rails-5.1.2/db/migrate/20170731101410_create_approval_comments.rb +14 -0
- data/spec/rails/rails-5.1.2/db/migrate/20170731101411_create_approval_items.rb +17 -0
- data/spec/rails/rails-5.1.2/db/migrate/20170731101412_create_users.rb +9 -0
- data/spec/rails/rails-5.1.2/db/migrate/20170731101413_create_books.rb +9 -0
- data/spec/rails/rails-5.1.2/db/schema.rb +64 -0
- data/spec/rails/rails-5.1.2/db/seeds.rb +7 -0
- data/spec/rails/rails-5.1.2/db/test.sqlite3 +0 -0
- data/spec/rails/rails-5.1.2/log/test.log +3485 -0
- data/spec/rails/rails-5.1.2/package.json +5 -0
- data/spec/rails/rails-5.1.2/public/404.html +67 -0
- data/spec/rails/rails-5.1.2/public/422.html +67 -0
- data/spec/rails/rails-5.1.2/public/500.html +66 -0
- data/spec/rails/rails-5.1.2/public/apple-touch-icon-precomposed.png +0 -0
- data/spec/rails/rails-5.1.2/public/apple-touch-icon.png +0 -0
- data/spec/rails/rails-5.1.2/public/favicon.ico +0 -0
- data/spec/rails/rails-5.1.2/public/robots.txt +1 -0
- data/spec/spec_helper.rb +117 -0
- data/spec/support/rails_template.rb +24 -0
- metadata +287 -43
- data/.gitignore +0 -12
- data/.rspec +0 -2
- data/.travis.yml +0 -5
- data/Gemfile +0 -4
- data/LICENSE.txt +0 -21
- data/approval.gemspec +0 -26
- data/bin/console +0 -14
- data/bin/setup +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ec9655c12fc1e790936193dfe16b35b3e2352dd7
|
4
|
+
data.tar.gz: 4ab7e5d9dd38039a91391cfdc6c0f1665938c44f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea6dea2cfe991bf86765c405926af2ae6d13d5422fb2f115c11907a29166edd729cb061d58a2bf40cf73c712927526032fa9369a2afaf8bc9a93b0a4a1a90328
|
7
|
+
data.tar.gz: c6b4fcdf006136b9288cceb8c2b67291873b2e9f41e378cc60ed46c33e0752fa5e5107d27801e706fff9f7d67ee0718feb312d942f45deb37946d62cc0a136bd
|
data/README.md
CHANGED
@@ -1,38 +1,60 @@
|
|
1
1
|
# Approval
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
3
|
+
:ok_woman::no_good:Approval workflow for Rails
|
6
4
|
|
7
5
|
## Installation
|
8
6
|
|
9
|
-
Add this line to your application's Gemfile:
|
10
|
-
|
11
7
|
```ruby
|
12
8
|
gem 'approval'
|
13
9
|
```
|
14
10
|
|
15
|
-
|
11
|
+
Optionally, you can run the generator, which will set up approval models with some useful defaults for you:
|
12
|
+
|
13
|
+
```bash
|
14
|
+
$ bin/rails g approval:install
|
15
|
+
```
|
16
16
|
|
17
|
-
|
17
|
+
## Usage
|
18
18
|
|
19
|
-
|
19
|
+
### Prepare
|
20
20
|
|
21
|
-
|
21
|
+
Call `acts_as_approval_user` in your resource of user model (`User`, `AdminUser`, `Member` etc):
|
22
22
|
|
23
|
-
|
23
|
+
```ruby
|
24
|
+
class User
|
25
|
+
acts_as_approval_user
|
26
|
+
end
|
27
|
+
```
|
24
28
|
|
25
|
-
|
29
|
+
Call `acts_as_approval_resource` in your resource:
|
26
30
|
|
27
|
-
|
31
|
+
```ruby
|
32
|
+
class Book < ApplicationRecord
|
33
|
+
acts_as_approval_resource
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
### Request
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
staff = User.find_or_create_by(email: "staff@example.com")
|
41
|
+
book = 10.times.map { |n| Book.new(name: "my_book_#{n}") }
|
42
|
+
request = staff.request_for_create(records, reason: "something")
|
43
|
+
request.save!
|
44
|
+
```
|
28
45
|
|
29
|
-
|
46
|
+
You send request, but resources aren't created.
|
30
47
|
|
31
|
-
|
48
|
+
### Respond
|
32
49
|
|
33
|
-
|
50
|
+
```ruby
|
51
|
+
admin = User.find_or_create_by(email: "admin@example.com")
|
52
|
+
request = Approval::Request.first
|
53
|
+
respond = admin.approve_request(request, reason: "something")
|
54
|
+
respond.save!
|
55
|
+
```
|
34
56
|
|
35
|
-
|
57
|
+
Then resources are created, if respond user have approved the request.
|
36
58
|
|
37
59
|
## License
|
38
60
|
|
data/Rakefile
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
|
-
require "rspec/core/rake_task"
|
3
2
|
|
4
|
-
|
3
|
+
desc "Creates a test rails app for the specs to run against"
|
4
|
+
task :setup do
|
5
|
+
require "rails/version"
|
6
|
+
system("mkdir spec/rails") unless File.exist?("spec/rails")
|
7
|
+
system "bundle exec rails new spec/rails/rails-#{Rails::VERSION::STRING} -m spec/support/rails_template.rb -T -B --skip-spring --skip-listen --skip-sprockets"
|
8
|
+
end
|
5
9
|
|
6
|
-
|
10
|
+
require "rspec/core/rake_task"
|
11
|
+
RSpec::Core::RakeTask.new(:spec)
|
12
|
+
task default: :spec
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Approval
|
2
|
+
class Config
|
3
|
+
attr_accessor(
|
4
|
+
:comment_maximum,
|
5
|
+
:user_class_name,
|
6
|
+
:permit_to_respond_to_own_request,
|
7
|
+
)
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@comment_maximum = 2000
|
11
|
+
@user_class_name = "User"
|
12
|
+
@permit_to_respond_to_own_request = false
|
13
|
+
end
|
14
|
+
|
15
|
+
def permit_to_respond_to_own_request?
|
16
|
+
!!permit_to_respond_to_own_request
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Approval
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
paths["app/models"] << "lib/approval/models"
|
4
|
+
|
5
|
+
initializer "approval" do
|
6
|
+
ActiveSupport.on_load :active_record do
|
7
|
+
require "approval/mixins"
|
8
|
+
ActiveRecord::Base.send :include, ::Approval::Mixins
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Approval
|
2
|
+
module Mixins
|
3
|
+
module Resource
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
DEFAULT_IGNORE_FIELDS = %w[id created_at updated_at].freeze
|
7
|
+
|
8
|
+
included do
|
9
|
+
class_attribute :approval_ignore_fields
|
10
|
+
has_many :approval_items, class_name: :"Approval::Item", as: :resource
|
11
|
+
end
|
12
|
+
|
13
|
+
class_methods do
|
14
|
+
def append_ignore_fields(ignore_fields = [])
|
15
|
+
self.approval_ignore_fields = DEFAULT_IGNORE_FIELDS.dup.concat(ignore_fields).map(&:to_s).uniq
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def params_for_approval
|
20
|
+
attributes.except(*approval_ignore_fields).compact
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Approval
|
2
|
+
module Mixins
|
3
|
+
module User
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
has_many :approval_requests, class_name: :"Approval::Request", foreign_key: :request_user_id
|
8
|
+
has_many :approval_comments, class_name: :"Approval::Comment", foreign_key: :user_id
|
9
|
+
end
|
10
|
+
|
11
|
+
def request_for_create(records, reason:)
|
12
|
+
Approval::RequestForm::Create.new(user: self, reason: reason, records: records)
|
13
|
+
end
|
14
|
+
|
15
|
+
def request_for_update(records, reason:)
|
16
|
+
Approval::RequestForm::Update.new(user: self, reason: reason, records: records)
|
17
|
+
end
|
18
|
+
|
19
|
+
def request_for_destroy(records, reason:)
|
20
|
+
Approval::RequestForm::Destroy.new(user: self, reason: reason, records: records)
|
21
|
+
end
|
22
|
+
|
23
|
+
def cancel_request(request, reason:)
|
24
|
+
Approval::RespondForm::Cancel.new(user: self, reason: reason, request: request)
|
25
|
+
end
|
26
|
+
|
27
|
+
def approve_request(request, reason:)
|
28
|
+
Approval::RespondForm::Approve.new(user: self, reason: reason, request: request)
|
29
|
+
end
|
30
|
+
|
31
|
+
def reject_request(request, reason:)
|
32
|
+
Approval::RespondForm::Reject.new(user: self, reason: reason, request: request)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Approval
|
2
|
+
module Mixins
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
class_methods do
|
6
|
+
def acts_as_approval_resource(ignore_fields: [])
|
7
|
+
include ::Approval::Mixins::Resource
|
8
|
+
append_ignore_fields(ignore_fields)
|
9
|
+
end
|
10
|
+
|
11
|
+
def acts_as_approval_user
|
12
|
+
include ::Approval::Mixins::User
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
require "approval/mixins/resource"
|
19
|
+
require "approval/mixins/user"
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Approval
|
2
|
+
class Comment < ::ActiveRecord::Base
|
3
|
+
self.table_name_prefix = "approval_".freeze
|
4
|
+
|
5
|
+
belongs_to :request, class_name: :"Approval::Request", inverse_of: :comments
|
6
|
+
belongs_to :user, class_name: Approval.config.user_class_name
|
7
|
+
|
8
|
+
validates :content, presence: true, length: { maximum: Approval.config.comment_maximum }
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Approval
|
2
|
+
class Item < ::ActiveRecord::Base
|
3
|
+
class UnexistResource < StandardError; end
|
4
|
+
|
5
|
+
EVENTS = %w[create update destroy].freeze
|
6
|
+
|
7
|
+
self.table_name_prefix = "approval_".freeze
|
8
|
+
|
9
|
+
belongs_to :request, class_name: :"Approval::Request", inverse_of: :items
|
10
|
+
belongs_to :resource, polymorphic: true, optional: true
|
11
|
+
|
12
|
+
serialize :params, Hash
|
13
|
+
|
14
|
+
with_options presence: true do
|
15
|
+
validates :resource_id, unless: :create_event?
|
16
|
+
validates :resource_type
|
17
|
+
validates :event, inclusion: { in: EVENTS }
|
18
|
+
end
|
19
|
+
|
20
|
+
validate :ensure_resource_be_valid
|
21
|
+
|
22
|
+
EVENTS.each do |event_name|
|
23
|
+
define_method "#{event_name}_event?" do
|
24
|
+
event_name.to_s == event.to_s
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def apply
|
29
|
+
case event
|
30
|
+
when "create"
|
31
|
+
resource_model.create!(params).tap do |created_resource|
|
32
|
+
update!(resource_id: created_resource.id)
|
33
|
+
end
|
34
|
+
when "update"
|
35
|
+
raise UnexistResource unless resource
|
36
|
+
resource.update!(params)
|
37
|
+
when "destroy"
|
38
|
+
raise UnexistResource unless resource
|
39
|
+
resource.destroy
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def resource_model
|
46
|
+
@_resource_model ||= resource_type.to_s.safe_constantize
|
47
|
+
end
|
48
|
+
|
49
|
+
def ensure_resource_be_valid
|
50
|
+
return if resource_model.nil? || destroy_event?
|
51
|
+
record = resource_model.new(params || {})
|
52
|
+
|
53
|
+
unless record.valid?
|
54
|
+
record.errors.full_messages.each do |_message|
|
55
|
+
errors.add(:base, "message")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Approval
|
2
|
+
class Request < ::ActiveRecord::Base
|
3
|
+
self.table_name_prefix = "approval_".freeze
|
4
|
+
|
5
|
+
with_options class_name: Approval.config.user_class_name do
|
6
|
+
belongs_to :request_user
|
7
|
+
belongs_to :respond_user, optional: true
|
8
|
+
end
|
9
|
+
|
10
|
+
with_options dependent: :destroy, inverse_of: :request do
|
11
|
+
has_many :comments, class_name: "::Approval::Comment"
|
12
|
+
has_many :items, class_name: "::Approval::Item"
|
13
|
+
end
|
14
|
+
|
15
|
+
enum state: { pending: 0, cancelled: 1, approved: 2, rejected: 3 }
|
16
|
+
|
17
|
+
scope :recently, -> { order(id: :desc) }
|
18
|
+
|
19
|
+
with_options presence: true do
|
20
|
+
validates :state
|
21
|
+
validates :request_user
|
22
|
+
validates :respond_user, unless: :pending?
|
23
|
+
validates :comments
|
24
|
+
validates :items
|
25
|
+
end
|
26
|
+
|
27
|
+
validates_associated :comments
|
28
|
+
validates_associated :items
|
29
|
+
|
30
|
+
validate :ensure_state_was_pending
|
31
|
+
|
32
|
+
before_create do
|
33
|
+
self.requested_at = Time.current
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def ensure_state_was_pending
|
39
|
+
return unless persisted?
|
40
|
+
errors.add(:base, :already_performed) if state_was != "pending"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,37 @@
|
|
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
|
+
prepare(&:save)
|
23
|
+
end
|
24
|
+
|
25
|
+
def save!
|
26
|
+
raise ::ActiveRecord::RecordInvalid unless valid?
|
27
|
+
prepare(&:save!)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def prepare
|
33
|
+
raise NotImplementedError, "you must implement #{self.class}##{__method__}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Approval
|
2
|
+
module RequestForm
|
3
|
+
class Create < Base
|
4
|
+
private
|
5
|
+
|
6
|
+
def prepare
|
7
|
+
::ActiveRecord::Base.transaction do
|
8
|
+
request = user.approval_requests.new
|
9
|
+
request.comments.new(user: user, content: reason)
|
10
|
+
Array(records).each do |record|
|
11
|
+
request.items.new(
|
12
|
+
event: "create",
|
13
|
+
resource_type: record.class.to_s,
|
14
|
+
params: record.params_for_approval,
|
15
|
+
)
|
16
|
+
end
|
17
|
+
yield(request)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Approval
|
2
|
+
module RequestForm
|
3
|
+
class Destroy < Base
|
4
|
+
private
|
5
|
+
|
6
|
+
def prepare
|
7
|
+
::ActiveRecord::Base.transaction do
|
8
|
+
request = user.approval_requests.new
|
9
|
+
request.comments.new(user: user, content: reason)
|
10
|
+
Array(records).each do |record|
|
11
|
+
request.items.new(
|
12
|
+
event: "destroy",
|
13
|
+
resource_type: record.class.to_s,
|
14
|
+
resource_id: record.id,
|
15
|
+
)
|
16
|
+
end
|
17
|
+
yield(request)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Approval
|
2
|
+
module RequestForm
|
3
|
+
class Update < Base
|
4
|
+
private
|
5
|
+
|
6
|
+
def prepare
|
7
|
+
::ActiveRecord::Base.transaction do
|
8
|
+
request = user.approval_requests.new
|
9
|
+
request.comments.new(user: user, content: reason)
|
10
|
+
Array(records).each do |record|
|
11
|
+
request.items.new(
|
12
|
+
event: "update",
|
13
|
+
resource_type: record.class.to_s,
|
14
|
+
resource_id: record.id,
|
15
|
+
params: record.params_for_approval,
|
16
|
+
)
|
17
|
+
end
|
18
|
+
yield(request)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
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
|
+
::ActiveRecord::Base.transaction do
|
10
|
+
request.lock!
|
11
|
+
request.assign_attributes(state: :approved, approved_at: Time.current, respond_user: user)
|
12
|
+
request.comments.new(user: user, content: reason)
|
13
|
+
request.items.each(&:apply)
|
14
|
+
yield(request)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,42 @@
|
|
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
|
+
prepare(&:save)
|
23
|
+
end
|
24
|
+
|
25
|
+
def save!
|
26
|
+
raise ::ActiveRecord::RecordInvalid unless valid?
|
27
|
+
prepare(&:save!)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def prepare
|
33
|
+
raise NotImplementedError, "you must implement #{self.class}##{__method__}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def ensure_user_cannot_respond_to_my_request
|
37
|
+
return if Approval.config.permit_to_respond_to_own_request?
|
38
|
+
errors.add(:user, :invalid) if user == request.request_user
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Approval
|
2
|
+
module RespondForm
|
3
|
+
class Cancel < Base
|
4
|
+
private
|
5
|
+
|
6
|
+
def prepare
|
7
|
+
::ActiveRecord::Base.transaction do
|
8
|
+
request.lock!
|
9
|
+
request.assign_attributes(state: :cancelled, cancelled_at: Time.current, respond_user: user)
|
10
|
+
request.comments.new(user: user, 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
|
+
::ActiveRecord::Base.transaction do
|
10
|
+
request.lock!
|
11
|
+
request.assign_attributes(state: :rejected, rejected_at: Time.current, respond_user: user)
|
12
|
+
request.comments.new(user: user, content: reason)
|
13
|
+
yield(request)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/approval/version.rb
CHANGED
data/lib/approval.rb
CHANGED
@@ -1,5 +1,13 @@
|
|
1
|
-
require "approval/version"
|
2
|
-
|
3
1
|
module Approval
|
4
|
-
|
2
|
+
def self.config
|
3
|
+
@_config ||= Config.new
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.configure
|
7
|
+
yield config
|
8
|
+
end
|
5
9
|
end
|
10
|
+
|
11
|
+
require "approval/config"
|
12
|
+
require "approval/engine" if defined?(::Rails)
|
13
|
+
require "approval/version"
|
File without changes
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require "rails/generators"
|
2
|
+
require "rails/generators/active_record"
|
3
|
+
|
4
|
+
module Approval
|
5
|
+
class InstallGenerator < ::Rails::Generators::Base
|
6
|
+
include ::Rails::Generators::Migration
|
7
|
+
source_root File.expand_path("templates", __dir__)
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def next_migration_number(dirname)
|
11
|
+
::ActiveRecord::Generators::Base.next_migration_number(dirname)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_migration_file
|
16
|
+
add_migration_file("create_approval_requests")
|
17
|
+
add_migration_file("create_approval_comments")
|
18
|
+
add_migration_file("create_approval_items")
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_initializer
|
22
|
+
copy_file("initializer.rb", "config/initializers/approval.rb")
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def add_migration_file(template)
|
28
|
+
migration_dir = File.expand_path("db/migrate")
|
29
|
+
|
30
|
+
if self.class.migration_exists?(migration_dir, template)
|
31
|
+
::Kernel.warn "Migration already exists: #{template}"
|
32
|
+
else
|
33
|
+
migration_template("#{template}.rb.tt", "db/migrate/#{template}.rb", migration_version: migration_version)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def migration_version
|
38
|
+
major = ActiveRecord::VERSION::MAJOR
|
39
|
+
|
40
|
+
if major >= 5
|
41
|
+
"[#{major}.#{ActiveRecord::VERSION::MINOR}]"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class CreateApprovalComments < ActiveRecord::Migration<%= migration_version %>
|
2
|
+
def change
|
3
|
+
create_table :approval_comments do |t|
|
4
|
+
t.integer :request_id, null: false
|
5
|
+
t.integer :user_id, null: false
|
6
|
+
t.text :content, null: false
|
7
|
+
|
8
|
+
t.timestamps
|
9
|
+
|
10
|
+
t.index :request_id
|
11
|
+
t.index :user_id
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class CreateApprovalItems < ActiveRecord::Migration<%= migration_version %>
|
2
|
+
def change
|
3
|
+
create_table :approval_items do |t|
|
4
|
+
t.integer :request_id, null: false
|
5
|
+
t.integer :resource_id
|
6
|
+
t.string :resource_type, null: false
|
7
|
+
t.string :event, null: false
|
8
|
+
t.text :params
|
9
|
+
|
10
|
+
t.timestamps
|
11
|
+
|
12
|
+
t.index :request_id
|
13
|
+
end
|
14
|
+
|
15
|
+
add_index :approval_items, [:resource_id, :resource_type]
|
16
|
+
end
|
17
|
+
end
|