approval2 0.1.2 → 0.1.3

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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- Njc2M2YwOTYwNmM0MzM3ZmE0MTE0NzFmNjJhNzBhZGFlMGY3ZWJjYw==
4
+ NDljMWE3ODNiZjFjMzJhODEyN2MwZDRiYTQ1ZWUxM2IxNzZkZmY4MA==
5
5
  data.tar.gz: !binary |-
6
- Mjc4YzNlOWFmZTIxZGM5ZDYwYmZkN2FhYTZjZmY2OGE4MjY2NjNjYg==
6
+ YzYxODYyNjBlNmJkZWM5MDc4MmRmMDc3YmZiZjhiY2FjZjEyMWMzOA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ODE5YWUxYWM0NjFhNWQ3ZmRiYjliMWE2ZjIxYmFkODUyNjYxYjcwMWFhZWEz
10
- NTAyNjIyMmE1NmQxNzc5N2ExYTZhMDlkODNlYzQxOGZkYzQxM2NlMzZiZGIz
11
- MWJhNzJlZTE2MjhmYTFjYTYxODdlYjM3ZDBkYmQ2NzZhNDZmZDg=
9
+ ODE3M2FlNDMzMTM0YzU2NGQ0NGQwZTUyYTVhZTMzODdhZTkzODM0N2MzY2E3
10
+ OWMxYmFmZGY2NzcwNTJhNDY3ZWMwYjNjZjY5MGE4OTI5MTdjY2Y3ZmRiYjJj
11
+ MTI4MTUxZTkxYjE5ODcyOWU5ZGFmZjQzNTA2ZTI4OGQyMjg1MDg=
12
12
  data.tar.gz: !binary |-
13
- MjI0NTk4NzFjMTE2OTM1M2JkMWQ5ODAzNDVkMmMzYjk4OTY3NGFkZjY4OWVm
14
- Yzc2ZjRlOWEzZGVhYjExNWQzNDg5NGJkYjE4ODE5ZWFiYmQxMzg4ZGExZGYz
15
- ZTNkMTM1OTZkZjllNzM1YTdmYjc1MmY5NWRmOWQ3OWVmY2NkNjc=
13
+ OGFlOGQyOTlhYjQ5NzBmMDQ4OGIwMmU4Mzg3ZWY0YjZlZjkwYzQ0MDI2NzQ5
14
+ Nzk0ZDg5MGFlZmI1OTA1ZTVkYzNiZjRkZTcyMzc2NTE4YTQ4ZWFjNmQwYmI2
15
+ Yzg2MmQ0MTYwYmIzZTNkNzg3OTdhOWJlY2ZkY2E3ZDUzMDUwM2I=
data/README.md CHANGED
@@ -1,9 +1,8 @@
1
1
  # Approval2
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/approval2`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
3
+ A trivial implementation of the 4 eyes principle. All record actions require an approval to take effect.
6
4
 
5
+
7
6
  ## Installation
8
7
 
9
8
  Add this line to your application's Gemfile:
@@ -22,13 +21,14 @@ Or install it yourself as:
22
21
 
23
22
  ## Usage
24
23
 
25
- TODO: Write usage instructions here
26
-
27
- ## Development
24
+ Models that need an approval, should have the standard approval columns (can be added by included t.approval_columns) and should include include Approval2::ModelAdditions in the class.
28
25
 
29
- After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
26
+ Doing so adds the following columns to the model
30
27
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
28
+ 1. approval_status
29
+ 2. approved_version
30
+ 3. approved_id
31
+ 4. last_action
32
32
 
33
33
  ## Contributing
34
34
 
data/approval2.gemspec CHANGED
@@ -22,4 +22,6 @@ Gem::Specification.new do |spec|
22
22
 
23
23
  spec.add_development_dependency "bundler", "~> 1.13"
24
24
  spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_dependency "audited"
26
+ spec.add_dependency "will_paginate"
25
27
  end
@@ -0,0 +1,10 @@
1
+ module ActiveRecord::ConnectionAdapters
2
+ class TableDefinition
3
+ def approval_columns(*args)
4
+ column(:approval_status, :string, limit: 1, default: 'U', null: false, comment: "the approval status of the record, A (approved), U (unapproved)")
5
+ column(:last_action, :string, limit: 1, default: 'C', comment: "the last action on the record, C (create), U (update), D (delete)")
6
+ column(:approved_id, :integer, comment: "the id of the approved record that was edited, and resulted in this unapproved record ")
7
+ column(:approved_version, :integer, comment: "the lock_version of the approved record at the time it was edited, and resulted in this unapproved record")
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,61 @@
1
+ module Approval2
2
+ module ControllerAdditions
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ before_filter :before_edit, only: :edit
7
+ before_filter :before_approve, only: :approve
8
+ before_filter :before_index, only: :index
9
+ end
10
+
11
+ def approve
12
+ end
13
+
14
+ private
15
+
16
+ def before_index
17
+ if (params[:approval_status].present? and params[:approval_status] == 'U')
18
+ modelName = self.class.name.sub("Controller", "").underscore.split('/').last.singularize
19
+ modelKlass = modelName.classify.constantize
20
+
21
+ x = modelKlass.unscoped.where("approval_status =?",'U').order("id desc")
22
+ instance_variable_set("@#{modelName}s", x.paginate(:per_page => 10, :page => params[:page]))
23
+ end
24
+ end
25
+
26
+ def before_edit
27
+ modelName = self.class.name.sub("Controller", "").underscore.split('/').last.singularize
28
+ modelKlass = modelName.classify.constantize
29
+
30
+ x = modelKlass.unscoped.find_by_id(params[:id])
31
+ if x.approval_status == 'A' && x.unapproved_record.nil?
32
+ params = (x.attributes).merge({:approved_id => x.id,:approved_version => x.lock_version})
33
+ x = modelKlass.new(params)
34
+ end
35
+
36
+ instance_variable_set("@#{modelName}", x)
37
+ end
38
+
39
+ def before_approve
40
+ modelName = self.class.name.sub("Controller", "").underscore.split('/').last.singularize
41
+ modelKlass = modelName.classify.constantize
42
+
43
+ x = modelKlass.unscoped.find(params[:id]) rescue nil
44
+ modelKlass.transaction do
45
+ approval = x.approve
46
+ if approval.empty?
47
+ flash[:alert] = "#{modelName.humanize.titleize} record was approved successfully"
48
+ else
49
+ msg = x.errors.full_messages << approval
50
+ flash[:alert] = msg
51
+ raise ActiveRecord::Rollback
52
+ end
53
+ end
54
+ redirect_to x
55
+ end
56
+ end
57
+ end
58
+
59
+ ActionController::Base.class_eval do
60
+ include Approval2::ControllerAdditions
61
+ end
@@ -0,0 +1,90 @@
1
+ module Approval2
2
+ module ModelAdditions
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ audited except: [:approval_status, :last_action]
7
+
8
+ # refers to the unapproved record in the common unapproved_record model, this is true only for U records
9
+ has_one :unapproved_record_entry, :as => :approvable, :class_name => '::UnapprovedRecord'
10
+
11
+ # refers to the approved/unapproved record in the model
12
+ belongs_to :unapproved_record, :primary_key => 'approved_id', :foreign_key => 'id', :class_name => self.name, :unscoped => true
13
+ belongs_to :approved_record, :foreign_key => 'approved_id', :primary_key => 'id', :class_name => self.name, :unscoped => true
14
+
15
+ validates_uniqueness_of :approved_id, :allow_blank => true
16
+ validate :validate_unapproved_record
17
+
18
+ def self.default_scope
19
+ where approval_status: 'A'
20
+ end
21
+
22
+
23
+ after_create :on_create_create_unapproved_record_entry
24
+ after_destroy :on_destory_remove_unapproved_record_entries
25
+ after_update :on_update_remove_unapproved_record_entries
26
+
27
+ end
28
+
29
+ def validate_unapproved_record
30
+ errors.add(:base,"Unapproved Record Already Exists for this record") if !unapproved_record.nil? and (approval_status == 'A' and approval_status_was == 'A')
31
+ end
32
+
33
+ def approve
34
+ return "The record version is different from that of the approved version" if !self.approved_record.nil? and self.approved_version != self.approved_record.lock_version
35
+
36
+ # make the U the A record, also assign the id of the A record, this looses history
37
+ # self.approval_status = 'A'
38
+ # self.approved_record.delete unless self.approved_record.nil?
39
+ # self.update_column(:id, self.approved_id) unless self.approved_id.nil?
40
+ # self.approved_id = nil
41
+
42
+
43
+ if self.approved_record.nil?
44
+ # create action, all we need to do is set the status to approved
45
+ self.approval_status = 'A'
46
+ else
47
+ # copy all attributes of the U record to the A record, and delete the U record
48
+ attributes = self.attributes.select do |attr, value|
49
+ self.class.column_names.include?(attr.to_s) and
50
+ ['id', 'approved_id', 'approval_status', 'lock_version', 'approved_version', 'created_at', 'updated_at', 'updated_by', 'created_by'].exclude?(attr)
51
+ end
52
+
53
+ self.class.unscoped do
54
+ approved_record = self.approved_record
55
+ approved_record.assign_attributes(attributes)
56
+ approved_record.last_action = 'U'
57
+ approved_record.updated_by = self.created_by
58
+ self.destroy
59
+ # not enought time to test cases where the approval is being done after changes in validations of the model, such that the saving of the approved
60
+ # record fails, this can be fixed to return the errors so that they can be shown to the user
61
+ approved_record.save!
62
+ end
63
+ end
64
+
65
+ return ""
66
+ end
67
+
68
+ def enable_approve_button?
69
+ self.approval_status == 'U' ? true : false
70
+ end
71
+
72
+ def on_create_create_unapproved_record_entry
73
+ if approval_status == 'U'
74
+ UnapprovedRecord.create!(:approvable => self)
75
+ end
76
+ end
77
+
78
+ def on_destory_remove_unapproved_record_entries
79
+ if approval_status == 'U'
80
+ unapproved_record_entry.delete
81
+ end
82
+ end
83
+
84
+ def on_update_remove_unapproved_record_entries
85
+ if approval_status == 'A' and approval_status_was == 'U'
86
+ unapproved_record_entry.delete
87
+ end
88
+ end
89
+ end
90
+ end
@@ -1,3 +1,3 @@
1
1
  module Approval2
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.3"
3
3
  end
data/lib/approval2.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require "approval2/version"
2
-
3
- module Approval2
4
- # Your code goes here...
5
- end
2
+ require "approval2/controller_additions"
3
+ require "approval2/model_additions"
4
+ require 'approval2/active_record_adapter.rb'
@@ -0,0 +1,37 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ module Approval2
5
+ module Generators
6
+ class InstallGenerator < ::Rails::Generators::Base
7
+ include Rails::Generators::Migration
8
+ source_root File.expand_path('../templates', __FILE__)
9
+ desc "Add the migrations for unapproved_records"
10
+
11
+ def self.next_migration_number(path)
12
+ next_migration_number = current_migration_number(path) + 1
13
+ ActiveRecord::Migration.next_migration_number(next_migration_number)
14
+ end
15
+
16
+ def copy_migrations
17
+ migration_template "create_unapproved_records.rb", "db/migrate/create_unapproved_records.rb"
18
+ end
19
+
20
+ def copy_models
21
+ copy_file "unapproved_record.rb", "#{Rails.root}/app/models/unapproved_record.rb"
22
+ end
23
+
24
+ def copy_views
25
+ ["index.html.haml", "_approve.html.haml"].each do |v|
26
+ copy_file v, "#{Rails.root}/app/views/unapproved_records/#{v}"
27
+ copy_file v, "#{Rails.root}/app/views/unapproved_records/#{v}"
28
+ end
29
+ end
30
+
31
+ def copy_controllers
32
+ copy_file "unapproved_records_controller.rb", "#{Rails.root}/app/controllers/unapproved_records_controller.rb"
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,15 @@
1
+ - can = can? :approve, @record
2
+ - approve_flag = @record.enable_approve_button?
3
+ %a.btn{"data-toggle" => "modal", :href => "#{!(can && approve_flag) ? '#' : '#myModalApprove'}", :role => "button", :class => "btn btn-primary #{(can && approve_flag) ? '' : 'disabled'}"} Approve
4
+ .modal.hide.fade{"id" => "myModalApprove", "aria-hidden" => "true", "aria-labelledby" => " myModalLabel", :role => "dialog", :tabindex => "-1"}
5
+ .modal-header
6
+ %button.close{"aria-hidden" => "true", "data-dismiss" => "modal", :type => "button"} ×
7
+ %h3#myModalLabel Acknowledge
8
+ #error_message{:style => 'color:red'}
9
+ .modal-body
10
+ = simple_form_for @record, :url => {:action => 'approve'}, :method => :put, :html=>{:id=>"transition"} do |ef|
11
+ = ef.input :updated_by, :as => :hidden, :input_html => {:value => current_user.id}
12
+ = submit_tag "Confirm", :class=>"btn btn-primary transition_button", :id => "transition_button"
13
+ %p{:style => 'color:green;'}
14
+ = created_or_edited_by(@record)
15
+ %br
@@ -0,0 +1,11 @@
1
+ class CreateUnApprovedRecords < ActiveRecord::Migration
2
+ def change
3
+ create_table :unapproved_records, {:sequence_start_value => '1 cache 20 order increment by 1'} do |t|
4
+ t.integer :approvable_id
5
+ t.string :approvable_type
6
+ t.timestamps null: false
7
+
8
+ t.index([:approvable_id, :approvable_type], unique: true, name: :uk_unapproved_records)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,23 @@
1
+ %br
2
+ %h1 Unapproved Records
3
+ %br
4
+ - unless @records.nil?
5
+ %br
6
+ = will_paginate @records
7
+ %br
8
+ %table.table.table-bordered.table-striped.table-hover
9
+ .thead
10
+ %th{:style=>'text-align:left; background-color: lightblue;'}
11
+ Record Type
12
+ %th{:style=>'text-align:left; background-color: lightblue;'}
13
+ Record Count
14
+ .tbody
15
+ - @records.each do |record|
16
+ %tr
17
+ %td{:style=>'text-align:left;'}
18
+ = record[:record_type]
19
+ %td{:style=>'text-align:left;'}
20
+ - if record[:record_type] == 'IncomingFile'
21
+ = link_to record[:record_count], {:controller => record[:record_type].tableize, :action => :index, :approval_status => 'U', :sc_service => 'AML'}
22
+ - else
23
+ = link_to record[:record_count], {:controller => record[:record_type].tableize, :action => :index, :approval_status => 'U'}
@@ -0,0 +1,3 @@
1
+ class UnapprovedRecord < ActiveRecord::Base
2
+ belongs_to :approvable, :polymorphic => true, :unscoped => true
3
+ end
@@ -0,0 +1,15 @@
1
+ require 'will_paginate/array'
2
+
3
+ class UnapprovedRecordsController < ApplicationController
4
+ def index
5
+ result = []
6
+ UnapprovedRecord.distinct.select(:approvable_type).each do |m|
7
+ if m.approvable_type.constantize.column_names.include?('approval_status')
8
+ count = UnapprovedRecord.where("approvable_type =?", m.approvable_type).count
9
+ result << {:record_type => m.approvable_type, :record_count => count}
10
+ end
11
+ end
12
+
13
+ @records = result.paginate(:per_page => 10, :page => params[:page]) rescue []
14
+ end
15
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: approval2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - akil
@@ -38,6 +38,34 @@ dependencies:
38
38
  - - ~>
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: audited
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: will_paginate
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
41
69
  description:
42
70
  email:
43
71
  - akhilesh.kataria@quantiguous.com
@@ -55,7 +83,16 @@ files:
55
83
  - bin/console
56
84
  - bin/setup
57
85
  - lib/approval2.rb
86
+ - lib/approval2/active_record_adapter.rb
87
+ - lib/approval2/controller_additions.rb
88
+ - lib/approval2/model_additions.rb
58
89
  - lib/approval2/version.rb
90
+ - lib/generators/approval2/install/install_generator.rb
91
+ - lib/generators/approval2/install/templates/_approve.html.haml
92
+ - lib/generators/approval2/install/templates/create_unapproved_records.rb
93
+ - lib/generators/approval2/install/templates/index.html.haml
94
+ - lib/generators/approval2/install/templates/unapproved_record.rb
95
+ - lib/generators/approval2/install/templates/unapproved_records_controller.rb
59
96
  homepage: http://quantiguous.com
60
97
  licenses:
61
98
  - MIT