approvable 0.0.1 → 0.0.2

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,7 +1,7 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
- metadata.gz: b8c9ad5fa11f3a6ca079cc935124013edace0ce5
4
- data.tar.gz: 727445aa21a877ea3340f9f07aeb83a87188b1ab
3
+ metadata.gz: 44c15bf9a39dd40e95b9d8455bf00c5bdcd44824
4
+ data.tar.gz: b91c51047334ead882747819116a498858a42c88
5
5
  SHA512:
6
- metadata.gz: 4151b5d043c9766856283cee5cbb6e837cb5600e68cbf4ef82e4664b4c76911038af4cefd84c3d71c5abd12647d8b18f4805bca3859e15b488ca46f9adff51ad
7
- data.tar.gz: af85d4ccc38564186c27f9b7978733fd3dd75ee0719454d93602d00c3e361b496a1804c7f088231bf0e5d2ddbf308f9b096eedcf862265825c157a9582f5053e
6
+ metadata.gz: 7f2c4b284e223bd22b5139b0d1d98cc2597fa3d5a580c6294d11510f52a618b773632dbf40e040060ee375ddc106c9dafc2efc80a303e5aaf2b83e640197f157
7
+ data.tar.gz: 8155feb1924e39a3aa114c67d5147481fdf3036e3645c32394e68a942489246b51060f1b5f784c422c2ff8acb3597463f28a1487502143b69f4058a26ee72d85
@@ -1,54 +1,69 @@
1
1
  module Approvable
2
2
  class ChangeRequest < ActiveRecord::Base
3
- belongs_to :approvable, :dependent => :destroy, :polymorphic => true
3
+ belongs_to :approvable, :polymorphic => true
4
4
  belongs_to :approver, :polymorphic => true
5
5
 
6
6
  validate :no_outstanding_change_requests, on: :create
7
- validate :not_submitted_or_approved, if: :requested_changes_changed?
7
+ # validate :not_submitted_or_approved, if: :requested_changes_changed?
8
8
 
9
9
  after_save :update_rejected_to_pending, if: :requested_changes_changed?
10
10
 
11
11
  scope :unapproved, -> {where.not(state: 'approved')}
12
12
 
13
- state_machine :initial => :pending do
13
+ include AASM
14
+
15
+ aasm column: :state do
16
+
17
+ state :pending, :initial => true
18
+ state :submitted
19
+ state :rejected
20
+ state :approved
21
+
14
22
  event :submit do
15
- transition [:rejected, :pending] => :submitted
23
+ transitions from: [:rejected, :pending], to: :submitted
16
24
  end
17
25
 
18
26
  event :unsubmit do
19
- transition :submitted => :pending
27
+ transitions from: :submitted, to: :pending
20
28
  end
21
29
 
22
30
  event :approve do
23
- transition :submitted => :approved
31
+ transitions from: :submitted, to: :approved
24
32
  end
25
-
33
+
26
34
  event :reject do
27
- transition :submitted => :rejected
35
+ transitions from: :submitted, to: :rejected, :on_transition => Proc.new {|obj, *args| obj.transition_options(*args)}
28
36
  end
29
37
 
30
38
  event :unreject do
31
- transition :rejected => :pending
39
+ transitions from: :rejected, to: :pending
32
40
  end
33
41
  end
34
42
 
43
+ def transition_options(options = {})
44
+ note = options[:note] if options
45
+ self.notes_will_change! if note
46
+ self.notes ||= {}
47
+ self.notes[Time.now.to_s] = note if note
48
+ end
49
+
35
50
  private
36
51
 
37
52
  def not_submitted_or_approved
38
- if approved? || submitted?
53
+ if ['approved', 'submitted'].include? state_was
39
54
  errors.add(:base, "cannot change a #{state} request")
40
55
  end
41
56
  end
42
-
57
+
43
58
  def no_outstanding_change_requests
44
59
  if self.class.where(approvable: approvable).unapproved.any?
45
60
  errors.add(:base, 'please use the existing change request')
46
- end
61
+ end
47
62
  end
48
63
 
49
64
  def update_rejected_to_pending
50
65
  if rejected?
51
- unreject
66
+ unreject!
52
67
  end
53
68
  end
54
69
 
@@ -3,7 +3,7 @@ class CreateApprovableChangeRequests < ActiveRecord::Migration
3
3
  create_table :approvable_change_requests do |t|
4
4
  t.string :approvable_type
5
5
  t.integer :approvable_id
6
- t.json :requested_changes
6
+ t.json :requested_changes, default: {}
7
7
  t.string :state
8
8
  t.string :approver_type
9
9
  t.integer :approver_id
@@ -0,0 +1,5 @@
1
+ class AddNotesToApprovableChangeRequests < ActiveRecord::Migration
2
+ def change
3
+ add_column :approvable_change_requests, :notes, :json, default: {}
4
+ end
5
+ end
@@ -6,78 +6,143 @@ module Approvable
6
6
  end
7
7
 
8
8
  module ClassMethods
9
- def acts_as_approvable(options = {})
9
+ def acts_as_approvable **options
10
10
  include Approvable::ActsAsApprovable::LocalInstanceMethods
11
11
 
12
- has_many :change_requests, as: :approvable, class_name: 'Approvable::ChangeRequest'
13
- has_one :current_change_request, -> {where.not(state: 'approved') }, as: :approvable, class_name: 'Approvable::ChangeRequest'
14
-
15
- alias_method_chain :save, :change_request
16
- alias_method_chain :save!, :change_request
17
- accepts_nested_attributes_for :current_change_request, update_only: true
12
+ has_many :change_requests, as: :approvable, class_name: 'Approvable::ChangeRequest', dependent: :destroy
13
+ has_one :current_change_request, -> {where.not(state: 'approved') }, as: :approvable, class_name: 'Approvable::ChangeRequest', autosave: true
14
+
15
+ before_save :apply_changes, if: :auto_approve?
16
+ after_save :force_approve!, if: :auto_approve?
18
17
 
18
+ cattr_accessor :filter_attrs, :filter_type
19
+ if options[:except]
20
+ self.filter_type = :except
21
+ self.filter_attrs = options[:except]
22
+ elsif options[:only]
23
+ self.filter_type = :only
24
+ self.filter_attrs = options[:only]
25
+ else
26
+ self.filter_type = :except
27
+ self.filter_attrs = []
28
+ end
29
+
30
+ unless method_defined?(:assign_attributes_without_change_request)
31
+ alias_method_chain :assign_attributes, :change_request
32
+ alias_method :attributes=, :assign_attributes_with_change_request
33
+
34
+ end
19
35
  end
36
+
20
37
  end
21
38
 
22
39
  module LocalInstanceMethods
40
+
41
+ def requested_changes
42
+ current_change_request ? current_change_request.requested_changes.with_indifferent_access : {}
43
+ end
23
44
 
45
+ # use current_change_request here so that we dont get pending from a newly built change_request
24
46
  def change_status
25
- current_change_request.state if current_change_request
47
+ current_change_request ? current_change_request.state : 'approved'
26
48
  end
27
-
28
- def with_changes
29
- self.attributes = current_change_request.requested_changes || {}
49
+
50
+ def change_status_notes
51
+ current_change_request ? current_change_request.notes : {}
52
+ end
53
+
54
+ def apply_changes
55
+ self.assign_attributes_without_change_request requested_changes
30
56
  self
31
57
  end
32
58
 
33
59
  def submit_changes
34
- current_change_request.submit
60
+ transaction do
61
+ current_change_request.submit! if current_change_request
62
+ reload
63
+ end
35
64
  end
36
65
 
37
66
  def unsubmit_changes
38
- current_change_request.unsubmit
67
+ transaction do
68
+ current_change_request.unsubmit! if current_change_request
69
+ reload
70
+ end
39
71
  end
40
72
 
41
73
  def approve_changes
42
- if with_changes.save_without_change_request
43
- current_change_request.approve
74
+ transaction do
75
+ current_change_request.approve! if current_change_request
76
+ apply_changes.save
77
+ reload
44
78
  end
45
79
  end
46
80
 
47
- def reject_changes
48
- current_change_request.reject
81
+ def reject_changes options = {}
82
+ current_change_request.reject! :rejected, options if current_change_request
83
+ reload
49
84
  end
50
85
 
51
- private
86
+ def assign_attributes_with_change_request new_attributes
87
+ ignored_params = ignored_attributes(new_attributes)
88
+ approvable_params = approvable_attributes(new_attributes)
89
+
90
+ if approvable_params.any?
91
+ current_change_request || build_current_change_request(requested_changes: {})
92
+ current_change_request.requested_changes = requested_changes.merge approvable_params
93
+ end
52
94
 
53
- def existing_changes
54
- current_change_request.try(:requested_changes) || {}
55
- end
95
+ assign_attributes_without_change_request ignored_params
96
+ end
97
+
98
+ private
56
99
 
57
- def new_changes
58
- self.attributes.select {|k,v| changed_attributes.keys.include? k}
100
+ def ignored_attributes(new_attributes)
101
+ process_nested_hash(new_attributes, self.class.filter_attrs, filter_type == :except)
59
102
  end
60
-
61
103
 
62
- def all_changes
63
- existing_changes.merge new_changes
104
+ def approvable_attributes(new_attributes)
105
+ process_nested_hash(new_attributes, self.class.filter_attrs, filter_type == :only )
64
106
  end
65
107
 
66
- def move_changes_to_change_request
67
- self.current_change_request_attributes = {requested_changes: all_changes}
68
- self.attributes = self.changed_attributes
108
+ # h = {"first_name"=>"Leif", "last_name"=>"Gensert", "address"=>{"street"=>"Preysinstraße", "city"=>"München"}}
109
+ def process_nested_hash(attributes, keys, should_match)
110
+ attributes = attributes.dup.stringify_keys!
111
+ hash = {}
112
+ [*keys].each do |key|
113
+ if key.is_a? Hash
114
+ key.each do |k,v|
115
+ hash[k.to_s] = process_nested_hash(attributes[k.to_s], v, should_match) if attributes[k.to_s]
116
+ end
117
+ elsif key.is_a? Array
118
+ key.each do|k|
119
+ process_nested_hash(attributes[k.to_s], k, should_match) if attributes[k.to_s]
120
+ end
121
+ else
122
+ value = attributes.delete(key.to_s)
123
+ hash[key.to_s] = value if value
124
+ end
125
+ end
126
+
127
+ if should_match
128
+ return hash
129
+ else
130
+ return attributes
131
+ end
132
+
69
133
  end
134
+
135
+ # process_nested_hash h, [:first_name, {address: :street}], true
136
+ # process_nested_hash h, [:first_name, {address: :street}], false
70
137
 
71
- def save_with_change_request
72
- move_changes_to_change_request
73
- save_without_change_request
138
+ def auto_approve?
139
+ Approvable.auto_approve == true
74
140
  end
75
141
 
76
- def save_with_change_request!
77
- move_changes_to_change_request
78
- save_without_change_request!
142
+ def force_approve!
143
+ current_change_request.update_column :state, 'approved' if current_change_request
79
144
  end
80
-
145
+
81
146
  end
82
147
  end
83
148
  end
@@ -1,9 +1,10 @@
1
1
  require 'approvable/acts_as_approvable'
2
- require 'state_machine'
3
- require 'graphviz' # Optional: only required for graphing
4
- require 'state_machine/version'
2
+ require 'aasm'
3
+ require 'activesupport/json_encoder'
5
4
 
6
5
  module Approvable
6
+ cattr_accessor :disabled, :auto_approve
7
+
7
8
  class Engine < ::Rails::Engine
8
9
  isolate_namespace Approvable
9
10
 
@@ -15,19 +16,6 @@ module Approvable
15
16
  end
16
17
  end
17
18
 
18
-
19
- initializer :state_machine do |app|
20
- unless StateMachine::VERSION == '1.2.0'
21
- # If you see this message, please test removing this file
22
- # If it's still required, please bump up the version above
23
- Rails.logger.warn "Please remove me, StateMachine version has changed"
24
- end
25
-
26
- module StateMachine::Integrations::ActiveModel
27
- public :around_validation
28
- end
29
- end
30
-
31
19
 
32
20
  config.generators do |g|
33
21
  g.test_framework :rspec, :fixture => false
@@ -1,3 +1,3 @@
1
1
  module Approvable
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -0,0 +1,6 @@
1
+ class Foobar < ActiveRecord::Base
2
+ acts_as_approvable
3
+
4
+ store_accessor :json_hash, :foo, :bar
5
+
6
+ end
@@ -1,3 +1,5 @@
1
1
  class Listing < ActiveRecord::Base
2
2
  acts_as_approvable
3
+
4
+ validates :title, presence: true
3
5
  end
@@ -0,0 +1,9 @@
1
+ class CreateFoobars < ActiveRecord::Migration
2
+ def change
3
+ create_table :foobars do |t|
4
+ t.json :json_hash
5
+
6
+ t.timestamps
7
+ end
8
+ end
9
+ end
@@ -11,11 +11,43 @@
11
11
  #
12
12
  # It's strongly recommended that you check this file into your version control system.
13
13
 
14
- ActiveRecord::Schema.define(version: 20140807144654) do
14
+ ActiveRecord::Schema.define(version: 20140816143802) do
15
15
 
16
16
  # These are extensions that must be enabled in order to support this database
17
17
  enable_extension "plpgsql"
18
18
 
19
+ create_table "approvable_change_requests", force: true do |t|
20
+ t.string "approvable_type"
21
+ t.integer "approvable_id"
22
+ t.json "requested_changes", default: {}
23
+ t.string "state"
24
+ t.string "approver_type"
25
+ t.integer "approver_id"
26
+ t.datetime "created_at"
27
+ t.datetime "updated_at"
28
+ t.json "notes", default: {}
29
+ end
30
+
31
+ create_table "bars", force: true do |t|
32
+ t.string "title"
33
+ t.integer "listing_id"
34
+ t.datetime "created_at"
35
+ t.datetime "updated_at"
36
+ end
37
+
38
+ create_table "foobars", force: true do |t|
39
+ t.json "json_hash"
40
+ t.datetime "created_at"
41
+ t.datetime "updated_at"
42
+ end
43
+
44
+ create_table "foos", force: true do |t|
45
+ t.string "title"
46
+ t.integer "listing_id"
47
+ t.datetime "created_at"
48
+ t.datetime "updated_at"
49
+ end
50
+
19
51
  create_table "listings", force: true do |t|
20
52
  t.string "title"
21
53
  t.text "description"
@@ -7,44 +7,21 @@ FactoryGirl.define do
7
7
 
8
8
 
9
9
  trait :approved do
10
- after(:create) {|c| c.submit; c.approve}
10
+ state :approved
11
11
  end
12
12
 
13
13
  trait :pending do
14
+ state :pending
15
+
14
16
  end
15
17
 
16
18
  trait :submitted do
17
- after(:create) {|c| c.submit}
19
+ state :submitted
18
20
  end
19
21
 
20
22
  trait :rejected do
21
- after(:create) {|c| c.submit; c.reject}
23
+ state :rejected
22
24
  end
23
-
24
-
25
- # trait :pending do
26
- # submitted_at nil
27
- # approved_at nil
28
- # rejected_at nil
29
- # end
30
- #
31
- # trait :submitted do
32
- # submitted_at {Time.now}
33
- # approved_at nil
34
- # rejected_at nil
35
- # end
36
- #
37
- # trait :approved do
38
- # submitted_at {Time.now}
39
- # approved_at {Time.now}
40
- # rejected_at nil
41
- # end
42
- #
43
- # trait :rejected do
44
- # submitted_at {Time.now}
45
- # approved_at nil
46
- # rejected_at {Time.now}
47
- # end
48
25
 
49
26
  end
50
27
  end