protected_record 0.0.0 → 0.0.1
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 +7 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/README.md +18 -2
- data/Rakefile +9 -0
- data/lib/protected_record/change_log.rb +2 -1
- data/lib/protected_record/change_request.rb +2 -1
- data/lib/protected_record/use_case/change_filter/create.rb +34 -0
- data/lib/protected_record/use_case/change_log/create.rb +41 -0
- data/lib/protected_record/use_case/change_request/create.rb +60 -0
- data/lib/protected_record/use_case/update.rb +89 -0
- data/lib/protected_record/use_case.rb +5 -0
- data/lib/protected_record/version.rb +1 -1
- data/lib/protected_record.rb +0 -2
- data/protected_record.gemspec +2 -1
- data/test/protected_record/change_filter/create_test.rb +134 -0
- data/test/protected_record/change_log/create_test.rb +2 -0
- data/test/protected_record/change_request/create_test.rb +2 -0
- data/test/protected_record/update_test.rb +1 -0
- metadata +75 -62
- data/lib/protected_record/use_case/base.rb +0 -21
- data/lib/protected_record/use_case/change/change_log/create.rb +0 -43
- data/lib/protected_record/use_case/change/change_request/create.rb +0 -51
- data/lib/protected_record/use_case/change/update.rb +0 -80
- data/lib/protected_record/use_case/result.rb +0 -40
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5936b33d95c7b170f1c1f0a90dbb94ba3d5cddc5
|
4
|
+
data.tar.gz: 4d6bbe694afa07f22a4b04e93fc6dc13fc9819a6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fd08a85e169ce3ebc9e47f0217fd031d90ef01b1330640113e6b39c1d57a071cb0de107863b5269e2b3904716976131e19b4c974a64773eede60efa33b6d30e4
|
7
|
+
data.tar.gz: f5829512aba3077dc008b4fa47145ecda4c3f54713b50f679ef4f2d7f42da09c3043df93f1db0e1c3864e5a1b7fa6ba44444b05a40d2a771dd7c15147dde9f64
|
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
protected_record
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.0.0-p0
|
data/README.md
CHANGED
@@ -1,7 +1,23 @@
|
|
1
|
-
|
1
|
+
# protected_record
|
2
|
+
## Description
|
3
|
+
protected_record will prevent changes to attributes you specify as protected. Any attempted change will be logged as a ProtectedRecord::ChangeRequest::Record.
|
4
|
+
If any changes are allowed through the filter, protected_record will create a ProtectedRecord::ChangeLog::Record to log who changed what, and for which record.
|
5
|
+
ProtectedRecord is opt-in only. In order to update with protection, use the following:
|
6
|
+
|
7
|
+
result = ProtectedRecord::UseCase::Update.new({
|
8
|
+
params: visit_params,
|
9
|
+
protected_record: @patient_visit,
|
10
|
+
user: current_user,
|
11
|
+
protected_keys: %w{ visit_date do_not_resuscitate }
|
12
|
+
}).execute!
|
13
|
+
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
To get started, just include in your tracked models:
|
2
18
|
|
3
19
|
include ProtectedRecord::ChangeRequest::Changeling
|
4
|
-
include ProtectedRecord::ChangeLog::Changeling
|
20
|
+
include ProtectedRecord::ChangeLog::Changeling
|
5
21
|
|
6
22
|
and include in your user model:
|
7
23
|
|
data/Rakefile
ADDED
@@ -1,7 +1,8 @@
|
|
1
|
+
require 'active_record'
|
1
2
|
module ProtectedRecord
|
2
3
|
module ChangeLog
|
3
4
|
class Record < ActiveRecord::Base
|
4
|
-
self.table_name = "
|
5
|
+
self.table_name = "protected_record_change_log_records"
|
5
6
|
attr_accessible :recordable, :user
|
6
7
|
belongs_to :recordable, polymorphic: true
|
7
8
|
belongs_to :user, class_name: "User", foreign_key: :user_id
|
@@ -1,7 +1,8 @@
|
|
1
|
+
require 'active_record'
|
1
2
|
module ProtectedRecord
|
2
3
|
module ChangeRequest
|
3
4
|
class Record < ActiveRecord::Base
|
4
|
-
self.table_name =
|
5
|
+
self.table_name = "protected_record_change_request_records"
|
5
6
|
attr_accessible :recordable, :user
|
6
7
|
|
7
8
|
belongs_to :recordable, polymorphic: true
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module ProtectedRecord
|
2
|
+
module UseCase
|
3
|
+
module ChangeFilter
|
4
|
+
class Create < PayDirt::Base
|
5
|
+
def initialize(options)
|
6
|
+
load_options(:protected_keys, :protected_record, options) and validate_state
|
7
|
+
end
|
8
|
+
|
9
|
+
def execute!
|
10
|
+
revert_protected_attrs
|
11
|
+
|
12
|
+
return PayDirt::Result.new(data: { change_request_record: @protected_record })
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
def revert_protected_attrs
|
17
|
+
@protected_keys.each do |key|
|
18
|
+
if @protected_record.send("#{key.to_s}_changed?")
|
19
|
+
@protected_record.send("#{key.to_s}=", @protected_record.send("#{key.to_s}_was"))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
def validate_state
|
26
|
+
# If there are no changes, there's no need to do any filtering
|
27
|
+
if !@protected_record.changes.present?
|
28
|
+
raise ActiveRecord::ActiveRecordError.new(':protected_record not dirty')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module ProtectedRecord
|
2
|
+
module UseCase
|
3
|
+
module ChangeLog
|
4
|
+
class Create < PayDirt::Base
|
5
|
+
def initialize(options)
|
6
|
+
options = {
|
7
|
+
record_class: ::ProtectedRecord::ChangeLog::Record
|
8
|
+
}.merge!(options) if !options.has_key?(:record_class)
|
9
|
+
|
10
|
+
load_options(:record_class, :user, :changed_object, options) and validate_state
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute!
|
14
|
+
initialize_change_log_record
|
15
|
+
|
16
|
+
if @record.save
|
17
|
+
return PayDirt::Result.new(data: { change_log_record: @record })
|
18
|
+
else
|
19
|
+
return PayDirt::Result.new(data: { change_log_record: @record }, success: false)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def initialize_change_log_record
|
25
|
+
@record = @record_class.new
|
26
|
+
@record.user = @user
|
27
|
+
@record.recordable = @changed_object
|
28
|
+
@record.observed_changes = ActiveSupport::JSON.encode(@changed_object.previous_changes)
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
def validate_state
|
33
|
+
# What are we doing here, if not logging a change that has already happened?
|
34
|
+
if !@changed_object.previous_changes.present?
|
35
|
+
raise ActiveRecord::ActiveRecordError.new(':changed_object has no previous_changes')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module ProtectedRecord
|
2
|
+
module UseCase
|
3
|
+
module ChangeRequest
|
4
|
+
class Create < PayDirt::Base
|
5
|
+
def initialize(options)
|
6
|
+
options = {
|
7
|
+
record_class: ::ProtectedRecord::ChangeRequest::Record
|
8
|
+
}.merge!(options) if !options.has_key?(:record_class)
|
9
|
+
|
10
|
+
load_options(:protected_keys, :record_class, :user, :protected_record, options) and validate_state
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute!
|
14
|
+
return PayDirt::Result.new(data: {}, success: true) if !requested_changes.present?
|
15
|
+
|
16
|
+
initialize_change_request_record
|
17
|
+
|
18
|
+
if @record.save
|
19
|
+
return PayDirt::Result.new(data: { change_request_record: @record })
|
20
|
+
else
|
21
|
+
return PayDirt::Result.new(data: { change_request_record: @record }, success: false)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def initialize_change_request_record
|
27
|
+
@record = @record_class.new
|
28
|
+
@record.user = @user
|
29
|
+
@record.recordable = @protected_record
|
30
|
+
@record.requested_changes = ActiveSupport::JSON.encode(requested_changes)
|
31
|
+
end
|
32
|
+
|
33
|
+
def requested_changes
|
34
|
+
@dirty_object.changes.select do |key|
|
35
|
+
@protected_keys.map(&:to_s).include? key.to_s
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def revert_protected_attrs
|
40
|
+
@protected_keys.each do |key|
|
41
|
+
if @protected_record.send("#{key.to_s}_changed?")
|
42
|
+
@protected_record.send("#{key.to_s}=", @protected_record.send("#{key.to_s}_was"))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
raise if @protected_keys.any? { |key| @protected_record.send("#{key}_changed?") }
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
def validate_state
|
51
|
+
# If there are no changes, there's no need to make a request
|
52
|
+
if !@dirty_object.changes.present?
|
53
|
+
raise ActiveRecord::ActiveRecordError.new(':dirty_object not dirty')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module ProtectedRecord
|
2
|
+
module UseCase
|
3
|
+
class Update < PayDirt::Base
|
4
|
+
def initialize(options)
|
5
|
+
# Defaults
|
6
|
+
options = {
|
7
|
+
change_request: UseCase::ChangeRequest::Create,
|
8
|
+
change_filter: UseCase::ChangeFilter::Create,
|
9
|
+
change_log: UseCase::ChangeLog::Create
|
10
|
+
}.merge!(options)
|
11
|
+
|
12
|
+
load_options(:params, :protected_record, :change_request, :change_log, :change_filter, :protected_keys, :user, options)
|
13
|
+
validate_state
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute!
|
17
|
+
form_change_request
|
18
|
+
|
19
|
+
# We are successful if all changes have been applied
|
20
|
+
if !@protected_record.changes.present?
|
21
|
+
return UseCase::Result.new(data: { updated: @protected_record })
|
22
|
+
else
|
23
|
+
return UseCase::Result.new({
|
24
|
+
data: { remaining_changes: @protected_record.changes },
|
25
|
+
success: false
|
26
|
+
})
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def form_change_request
|
32
|
+
@protected_record.attributes = @params
|
33
|
+
|
34
|
+
# We allow any changes on creation
|
35
|
+
unless @protected_record.id_was.nil?
|
36
|
+
request_result = @change_request.new({
|
37
|
+
protected_keys: @protected_keys,
|
38
|
+
protected_record: @protected_record,
|
39
|
+
user: @user
|
40
|
+
}).execute!
|
41
|
+
|
42
|
+
if request_result.successful?
|
43
|
+
revert_protected_attributes ||
|
44
|
+
save_protected_record
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def save_protected_record
|
50
|
+
@protected_record.save
|
51
|
+
log_changes
|
52
|
+
end
|
53
|
+
|
54
|
+
def revert_protected_attributes
|
55
|
+
revert_result = @change_filter.new({
|
56
|
+
protected_keys: @protected_keys,
|
57
|
+
protected_record: @protected_record
|
58
|
+
}).execute!
|
59
|
+
|
60
|
+
revert_result.successful? ? return : raise
|
61
|
+
end
|
62
|
+
|
63
|
+
def log_changes
|
64
|
+
log_result = @change_log.new(user: @user, changed_object: @protected_record).execute!
|
65
|
+
|
66
|
+
result.successful? ? return : raise
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
def validate_state
|
71
|
+
# We expect some keys
|
72
|
+
if !@protected_keys.kind_of?(Array)
|
73
|
+
raise TypeError.new(':protected_keys not kind of Array')
|
74
|
+
end
|
75
|
+
|
76
|
+
# The keys should respond to to_s
|
77
|
+
if @protected_keys.any? {|el| !el.respond_to?("to_s") }
|
78
|
+
raise TypeError.new('All :protected_keys should respond to #to_s')
|
79
|
+
end
|
80
|
+
|
81
|
+
# The dirty_object should respond to all keys (as methods)
|
82
|
+
@protected_keys.each do |key|
|
83
|
+
error = ActiveRecord::ActiveRecordError.new(":dirty_object must respond to #{key.to_s}")
|
84
|
+
raise error unless @protected_record.respond_to?(key.to_s)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/protected_record.rb
CHANGED
data/protected_record.gemspec
CHANGED
@@ -19,8 +19,9 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
20
|
s.require_paths = ["lib"]
|
21
21
|
|
22
|
+
s.add_runtime_dependency "pay_dirt"
|
23
|
+
s.add_runtime_dependency "activerecord"
|
22
24
|
s.add_development_dependency "minitest"
|
23
|
-
|
24
25
|
s.add_development_dependency "rake"
|
25
26
|
s.add_development_dependency "pry"
|
26
27
|
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require "minitest_helper"
|
2
|
+
require "active_record"
|
3
|
+
require "pry"
|
4
|
+
|
5
|
+
describe ProtectedRecord::UseCase::ChangeFilter::Create do
|
6
|
+
describe "new" do
|
7
|
+
before do
|
8
|
+
class TestCase
|
9
|
+
include ActiveModel::Dirty
|
10
|
+
|
11
|
+
define_attribute_methods [:knowledge, :power]
|
12
|
+
|
13
|
+
def knowledge
|
14
|
+
@knowledge
|
15
|
+
end
|
16
|
+
|
17
|
+
def power
|
18
|
+
@power
|
19
|
+
end
|
20
|
+
|
21
|
+
def knowledge=(val)
|
22
|
+
knowledge_will_change! unless val == @knowledge
|
23
|
+
@knowledge = val
|
24
|
+
end
|
25
|
+
|
26
|
+
def power=(val)
|
27
|
+
power_will_change! unless val == @power
|
28
|
+
@power = val
|
29
|
+
end
|
30
|
+
|
31
|
+
def save
|
32
|
+
@previously_changed = changes
|
33
|
+
@changed_attributes.clear
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(attributes = {})
|
37
|
+
attributes.each do |name, value|
|
38
|
+
send("#{name}=", value)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
#####
|
42
|
+
end
|
43
|
+
|
44
|
+
@subject = ProtectedRecord::UseCase::ChangeFilter::Create
|
45
|
+
|
46
|
+
@protected_record = TestCase.new(knowledge: "power", power: "money")
|
47
|
+
@protected_record.save
|
48
|
+
end
|
49
|
+
|
50
|
+
it "initializes with required options" do
|
51
|
+
@protected_record.knowledge="growth"
|
52
|
+
@protected_record.power="knowledge"
|
53
|
+
|
54
|
+
@protected_keys = %w{ power }
|
55
|
+
@subject.new(protected_record: @protected_record, protected_keys: @protected_keys)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "fails to initialize without :protected_record" do
|
59
|
+
begin
|
60
|
+
@subject.new(protected_keys: @protected_keys)
|
61
|
+
rescue => e
|
62
|
+
e.must_be_kind_of RuntimeError
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
it "fails to initialize without :protected_keys" do
|
67
|
+
begin
|
68
|
+
@subject.new(protected_record: @protected_record)
|
69
|
+
rescue => e
|
70
|
+
e.must_be_kind_of RuntimeError
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it "returns a result" do
|
75
|
+
@protected_record.knowledge="growth"
|
76
|
+
@protected_record.power="knowledge"
|
77
|
+
|
78
|
+
@protected_keys = %w{ power }
|
79
|
+
@subject.new({
|
80
|
+
protected_record: @protected_record,
|
81
|
+
protected_keys: @protected_keys
|
82
|
+
}).execute!.must_be_kind_of(PayDirt::Result)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "allows unprotected changes" do
|
86
|
+
@protected_keys = %w{}
|
87
|
+
|
88
|
+
@protected_record.knowledge="growth"
|
89
|
+
@protected_record.power="knowledge"
|
90
|
+
|
91
|
+
result = @subject.new({
|
92
|
+
protected_record: @protected_record,
|
93
|
+
protected_keys: @protected_keys
|
94
|
+
}).execute!
|
95
|
+
|
96
|
+
ret_obj = result.data[:change_request_record]
|
97
|
+
|
98
|
+
ret_obj.knowledge.must_equal "growth"
|
99
|
+
ret_obj.power.must_equal "knowledge"
|
100
|
+
end
|
101
|
+
|
102
|
+
it "filters all protected_keys" do
|
103
|
+
@protected_record.knowledge="growth"
|
104
|
+
@protected_record.power="knowledge"
|
105
|
+
|
106
|
+
@protected_keys = %w{ power knowledge }
|
107
|
+
result = @subject.new({
|
108
|
+
protected_record: @protected_record,
|
109
|
+
protected_keys: @protected_keys
|
110
|
+
}).execute!
|
111
|
+
|
112
|
+
ret_obj = result.data[:change_request_record]
|
113
|
+
|
114
|
+
ret_obj.knowledge.must_equal "power"
|
115
|
+
ret_obj.power.must_equal "money"
|
116
|
+
end
|
117
|
+
|
118
|
+
it "will allow unprotected changes while reverting protected changes" do
|
119
|
+
@protected_record.knowledge="growth"
|
120
|
+
@protected_record.power="knowledge"
|
121
|
+
|
122
|
+
@protected_keys = %w{ power }
|
123
|
+
result = @subject.new({
|
124
|
+
protected_record: @protected_record,
|
125
|
+
protected_keys: @protected_keys
|
126
|
+
}).execute!
|
127
|
+
|
128
|
+
ret_obj = result.data[:change_request_record]
|
129
|
+
|
130
|
+
ret_obj.knowledge.must_equal "growth"
|
131
|
+
ret_obj.power.must_equal "money"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "minitest_helper"
|
metadata
CHANGED
@@ -1,73 +1,88 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: protected_record
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
version: 0.0.0
|
4
|
+
version: 0.0.1
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Tad Hosford
|
9
|
-
autorequire:
|
8
|
+
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2013-04-
|
11
|
+
date: 2013-04-23 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
14
|
+
name: pay_dirt
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
16
22
|
version_requirements: !ruby/object:Gem::Requirement
|
17
23
|
requirements:
|
18
|
-
- -
|
24
|
+
- - '>='
|
19
25
|
- !ruby/object:Gem::Version
|
20
|
-
version:
|
21
|
-
|
22
|
-
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activerecord
|
23
29
|
requirement: !ruby/object:Gem::Requirement
|
24
30
|
requirements:
|
25
|
-
- -
|
31
|
+
- - '>='
|
26
32
|
- !ruby/object:Gem::Version
|
27
|
-
version:
|
28
|
-
|
29
|
-
none: false
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
30
35
|
prerelease: false
|
31
|
-
type: :development
|
32
|
-
- !ruby/object:Gem::Dependency
|
33
|
-
name: rake
|
34
36
|
version_requirements: !ruby/object:Gem::Requirement
|
35
37
|
requirements:
|
36
|
-
- -
|
38
|
+
- - '>='
|
37
39
|
- !ruby/object:Gem::Version
|
38
|
-
version:
|
39
|
-
|
40
|
-
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
41
43
|
requirement: !ruby/object:Gem::Requirement
|
42
44
|
requirements:
|
43
|
-
- -
|
45
|
+
- - '>='
|
44
46
|
- !ruby/object:Gem::Version
|
45
|
-
version:
|
46
|
-
MA==
|
47
|
-
none: false
|
48
|
-
prerelease: false
|
47
|
+
version: '0'
|
49
48
|
type: :development
|
50
|
-
|
51
|
-
name: pry
|
49
|
+
prerelease: false
|
52
50
|
version_requirements: !ruby/object:Gem::Requirement
|
53
51
|
requirements:
|
54
|
-
- -
|
52
|
+
- - '>='
|
55
53
|
- !ruby/object:Gem::Version
|
56
|
-
version:
|
57
|
-
|
58
|
-
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
59
57
|
requirement: !ruby/object:Gem::Requirement
|
60
58
|
requirements:
|
61
|
-
- -
|
59
|
+
- - '>='
|
62
60
|
- !ruby/object:Gem::Version
|
63
|
-
version:
|
64
|
-
|
65
|
-
none: false
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
66
63
|
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
67
76
|
type: :development
|
68
|
-
|
69
|
-
|
70
|
-
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: "\n Filters changes & logs changes to protected
|
84
|
+
records.\n Creates change requests when changes are attempted
|
85
|
+
on protected attrs\n "
|
71
86
|
email:
|
72
87
|
- tad.hosford@gmail.com
|
73
88
|
executables: []
|
@@ -75,49 +90,47 @@ extensions: []
|
|
75
90
|
extra_rdoc_files: []
|
76
91
|
files:
|
77
92
|
- .gitignore
|
93
|
+
- .ruby-gemset
|
94
|
+
- .ruby-version
|
78
95
|
- Gemfile
|
79
96
|
- README.md
|
97
|
+
- Rakefile
|
80
98
|
- lib/protected_record.rb
|
81
99
|
- lib/protected_record/change_log.rb
|
82
100
|
- lib/protected_record/change_request.rb
|
83
|
-
- lib/protected_record/use_case
|
84
|
-
- lib/protected_record/use_case/
|
85
|
-
- lib/protected_record/use_case/
|
86
|
-
- lib/protected_record/use_case/
|
87
|
-
- lib/protected_record/use_case/
|
101
|
+
- lib/protected_record/use_case.rb
|
102
|
+
- lib/protected_record/use_case/change_filter/create.rb
|
103
|
+
- lib/protected_record/use_case/change_log/create.rb
|
104
|
+
- lib/protected_record/use_case/change_request/create.rb
|
105
|
+
- lib/protected_record/use_case/update.rb
|
88
106
|
- lib/protected_record/version.rb
|
89
107
|
- protected_record.gemspec
|
90
108
|
- test/minitest_helper.rb
|
109
|
+
- test/protected_record/change_filter/create_test.rb
|
110
|
+
- test/protected_record/change_log/create_test.rb
|
111
|
+
- test/protected_record/change_request/create_test.rb
|
112
|
+
- test/protected_record/update_test.rb
|
91
113
|
homepage: http://github.com/rthbound/protected_record
|
92
114
|
licenses: []
|
93
|
-
|
115
|
+
metadata: {}
|
116
|
+
post_install_message:
|
94
117
|
rdoc_options: []
|
95
118
|
require_paths:
|
96
119
|
- lib
|
97
120
|
required_ruby_version: !ruby/object:Gem::Requirement
|
98
121
|
requirements:
|
99
|
-
- -
|
122
|
+
- - '>='
|
100
123
|
- !ruby/object:Gem::Version
|
101
|
-
|
102
|
-
- 0
|
103
|
-
hash: 2
|
104
|
-
version: !binary |-
|
105
|
-
MA==
|
106
|
-
none: false
|
124
|
+
version: '0'
|
107
125
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
126
|
requirements:
|
109
|
-
- -
|
127
|
+
- - '>='
|
110
128
|
- !ruby/object:Gem::Version
|
111
|
-
|
112
|
-
- 0
|
113
|
-
hash: 2
|
114
|
-
version: !binary |-
|
115
|
-
MA==
|
116
|
-
none: false
|
129
|
+
version: '0'
|
117
130
|
requirements: []
|
118
|
-
rubyforge_project:
|
119
|
-
rubygems_version:
|
120
|
-
signing_key:
|
121
|
-
specification_version:
|
131
|
+
rubyforge_project:
|
132
|
+
rubygems_version: 2.0.0.rc.2
|
133
|
+
signing_key:
|
134
|
+
specification_version: 4
|
122
135
|
summary: ''
|
123
136
|
test_files: []
|
@@ -1,21 +0,0 @@
|
|
1
|
-
module ProtectedRecord
|
2
|
-
module UseCase
|
3
|
-
class Base
|
4
|
-
def load_option(option, options)
|
5
|
-
instance_variable_set("@#{option}", options.fetch(option.to_sym) { raise "Missing required option: #{option}" } )
|
6
|
-
end
|
7
|
-
|
8
|
-
def load_options(*option_names, options)
|
9
|
-
option_names.each{|o| load_option(o, options) }
|
10
|
-
@errors ||= []
|
11
|
-
end
|
12
|
-
|
13
|
-
def errors
|
14
|
-
@errors
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
|
21
|
-
|
@@ -1,43 +0,0 @@
|
|
1
|
-
module ProtectedRecord
|
2
|
-
module UseCase
|
3
|
-
module Change
|
4
|
-
module ChangeLog
|
5
|
-
class Create < ::ProtectedRecord::UseCase::Base
|
6
|
-
def initialize(options)
|
7
|
-
options = {
|
8
|
-
record_class: ::ProtectedRecord::ChangeLog::Record
|
9
|
-
}.merge!(options) if !options.has_key?(:record_class)
|
10
|
-
|
11
|
-
load_options(:record_class, :user, :changed_object, options) and validate_state
|
12
|
-
end
|
13
|
-
|
14
|
-
def execute!
|
15
|
-
create_change_log_record if @errors.empty?
|
16
|
-
|
17
|
-
if @errors.empty? && @record.save
|
18
|
-
return UseCase::Result.new(data: { change_log_record: @record })
|
19
|
-
else
|
20
|
-
return UseCase::Result.new(data: { change_log_record: @record }, errors: @errors)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
private
|
25
|
-
def create_change_log_record
|
26
|
-
@record = @record_class.new
|
27
|
-
@record.user = @user
|
28
|
-
@record.recordable = @changed_object
|
29
|
-
@record.observed_changes = ActiveSupport::JSON.encode(@changed_object.previous_changes)
|
30
|
-
end
|
31
|
-
|
32
|
-
protected
|
33
|
-
def validate_state
|
34
|
-
# What are we doing here, if not logging a change that has already happened?
|
35
|
-
if !@changed_object.previous_changes.present?
|
36
|
-
@errors << ActiveRecord::ActiveRecordError.new(':changed_object has no previous_changes')
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
@@ -1,51 +0,0 @@
|
|
1
|
-
module ProtectedRecord
|
2
|
-
module UseCase
|
3
|
-
module Change
|
4
|
-
module ChangeRequest
|
5
|
-
class Create < ::ProtectedRecord::UseCase::Base
|
6
|
-
def initialize(options)
|
7
|
-
options = {
|
8
|
-
record_class: ::ProtectedRecord::ChangeRequest::Record
|
9
|
-
}.merge!(options) if !options.has_key?(:record_class)
|
10
|
-
|
11
|
-
load_options(:protected_keys, :record_class, :user, :dirty_object, options) and validate_state
|
12
|
-
end
|
13
|
-
|
14
|
-
def execute!
|
15
|
-
return UseCase::Result.new(data: nil, success: true) if !requested_changes.present?
|
16
|
-
|
17
|
-
create_change_request_record if @errors.empty?
|
18
|
-
|
19
|
-
if @errors.empty? && @record.save
|
20
|
-
return UseCase::Result.new(data: { change_request_record: @record })
|
21
|
-
else
|
22
|
-
return UseCase::Result.new(data: { change_request_record: @record }, errors: @errors)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
def create_change_request_record
|
28
|
-
@record = @record_class.new
|
29
|
-
@record.user = @user
|
30
|
-
@record.recordable = @dirty_object
|
31
|
-
@record.requested_changes = ActiveSupport::JSON.encode(requested_changes)
|
32
|
-
end
|
33
|
-
|
34
|
-
def requested_changes
|
35
|
-
@dirty_object.changes.select do |key|
|
36
|
-
@protected_keys.map(&:to_s).include? key.to_s
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
protected
|
41
|
-
def validate_state
|
42
|
-
# If there are no changes, there's no need to make a request
|
43
|
-
if !@dirty_object.changes.present?
|
44
|
-
@errors << ActiveRecord::ActiveRecordError.new(':dirty_object not dirty')
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
@@ -1,80 +0,0 @@
|
|
1
|
-
module ProtectedRecord
|
2
|
-
module UseCase
|
3
|
-
module Change
|
4
|
-
class Update < ::ProtectedRecord::UseCase::Base
|
5
|
-
def initialize(options)
|
6
|
-
# Defaults
|
7
|
-
options = {
|
8
|
-
change_request_use_case: UseCase::Change::ChangeRequest::Create,
|
9
|
-
change_log_use_case: UseCase::Change::ChangeLog::Create
|
10
|
-
}.merge!(options) if !options.has_key?(:record_class)
|
11
|
-
|
12
|
-
load_options(:params, :protected_record, :change_request_use_case, :change_log_use_case, :protected_keys, :user, options)
|
13
|
-
validate_state
|
14
|
-
end
|
15
|
-
|
16
|
-
def execute!
|
17
|
-
form_change_request
|
18
|
-
|
19
|
-
if @errors.empty? && !@protected_record.changes.present?
|
20
|
-
return UseCase::Result.new(data: { params: @protected_record })
|
21
|
-
else
|
22
|
-
return UseCase::Result.new(data: { params: @protected_record }, errors: @errors)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
def form_change_request
|
28
|
-
@protected_record.attributes = @params
|
29
|
-
|
30
|
-
unless @protected_record.id_was.nil?
|
31
|
-
request_result = @change_request_use_case.new(protected_keys: @protected_keys, dirty_object: @protected_record, user: @user).execute!
|
32
|
-
if request_result.successful?
|
33
|
-
revert_protected_attrs
|
34
|
-
save_protected_record
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def revert_protected_attrs
|
40
|
-
@protected_keys.each do |key|
|
41
|
-
if @protected_record.send("#{key.to_s}_changed?")
|
42
|
-
@protected_record.send("#{key.to_s}=", @protected_record.send("#{key.to_s}_was"))
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
raise if @protected_keys.any? { |key| @protected_record.send("#{key}_changed?") }
|
47
|
-
end
|
48
|
-
|
49
|
-
def save_protected_record
|
50
|
-
@protected_record.save
|
51
|
-
log_changes
|
52
|
-
end
|
53
|
-
|
54
|
-
def log_changes
|
55
|
-
log_result = @change_log_use_case.new(user: @user, changed_object: @protected_record).execute!
|
56
|
-
return true if log_result.successful?
|
57
|
-
end
|
58
|
-
|
59
|
-
protected
|
60
|
-
def validate_state
|
61
|
-
# We expect some keys
|
62
|
-
if !@protected_keys.kind_of?(Array)
|
63
|
-
@errors << TypeError.new(':protected_keys not kind of Array')
|
64
|
-
end
|
65
|
-
|
66
|
-
# The keys should respond to to_s
|
67
|
-
if @protected_keys.any? {|el| !el.respond_to?("to_s") }
|
68
|
-
@errors << TypeError.new('All :protected_keys should respond to #to_s')
|
69
|
-
end
|
70
|
-
|
71
|
-
# The dirty_object should respond to all keys (as methods)
|
72
|
-
@protected_keys.each do |key|
|
73
|
-
error = ActiveRecord::ActiveRecordError.new(":dirty_object must respond to #{key.to_s}")
|
74
|
-
@errors << error unless @protected_record.respond_to?(key.to_s)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
@@ -1,40 +0,0 @@
|
|
1
|
-
module ProtectedRecord
|
2
|
-
module UseCase
|
3
|
-
class Result
|
4
|
-
# The response from a use case execution
|
5
|
-
#
|
6
|
-
# Every use case should return a Result after it runs.
|
7
|
-
#
|
8
|
-
# @param [options] options_hash
|
9
|
-
# A hash specifying the appropriate options
|
10
|
-
#
|
11
|
-
# @return [UseCase::Result]
|
12
|
-
# the Result instance
|
13
|
-
#
|
14
|
-
# @example
|
15
|
-
# UseCase::Result.new(success: true, data: {})
|
16
|
-
# # => <UseCase::Result>
|
17
|
-
#
|
18
|
-
# @api public
|
19
|
-
def initialize(options)
|
20
|
-
@success = options.fetch(:success) { !options[:errors].present? }
|
21
|
-
@errors = options[:errors]
|
22
|
-
@data = options[:data]
|
23
|
-
end
|
24
|
-
|
25
|
-
# @api public
|
26
|
-
def successful?
|
27
|
-
!!@success
|
28
|
-
end
|
29
|
-
|
30
|
-
def errors
|
31
|
-
@errors
|
32
|
-
end
|
33
|
-
|
34
|
-
# @api public
|
35
|
-
def data
|
36
|
-
@data
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|