protected_record 0.0.0

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.
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ # See http://help.github.com/ignore-files/ for more about ignoring files.
2
+ #
3
+ # If you find yourself ignoring temporary files generated by your text editor
4
+ # or operating system, you probably want to add a global ignore instead:
5
+ # git config --global core.excludesfile ~/.gitignore_global
6
+
7
+ # Ignore bundler config
8
+ /.bundle
9
+
10
+ *.sw?
11
+ .sw?
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ Just include in your tracked models:
2
+
3
+ include ProtectedRecord::ChangeRequest::Changeling
4
+ include ProtectedRecord::ChangeLog::Changeling
5
+
6
+ and include in your user model:
7
+
8
+ include ProtectedRecord::ChangeRequest::Changer
9
+ include ProtectedRecord::ChangeLog::Changer
10
+
11
+ then in your controller
12
+
13
+ # UseCase module will filter changes to protected_keys,
14
+ # creating a "change request" rather than applying changes
15
+
16
+ # UseCase module will allow other changes to be applied,
17
+ # creating a "change log" entry for the observed changes
18
+
19
+ update_result = ProtectedRecord::UseCase::Update.new({
20
+ params: visit_params,
21
+ protected_record: @patient_visit,
22
+ user: current_user,
23
+ protected_keys: %w{ visit_date do_not_resuscitate }
24
+ }).execute!
25
+
26
+ update_result.successful?
27
+
28
+ and call methods like
29
+
30
+ @user.change_log_records
31
+ @user.change_request_records
@@ -0,0 +1,5 @@
1
+ require "protected_record/use_case/base"
2
+
3
+ Dir.glob(File.join(File.dirname(__FILE__), '/**/*.rb')) do |c|
4
+ require(c)
5
+ end
@@ -0,0 +1,30 @@
1
+ module ProtectedRecord
2
+ module ChangeLog
3
+ class Record < ActiveRecord::Base
4
+ self.table_name = "change_log_records"
5
+ attr_accessible :recordable, :user
6
+ belongs_to :recordable, polymorphic: true
7
+ belongs_to :user, class_name: "User", foreign_key: :user_id
8
+ end
9
+
10
+ # Include this module in models inheriting from AR::Base
11
+ module Changer
12
+ def self.included(base)
13
+ # Include this in AR models only
14
+ return unless base.ancestors.include?(ActiveRecord::Base)
15
+
16
+ base.has_many :change_log_records, class_name: "ProtectedRecord::ChangeLog::Record"
17
+ end
18
+ end
19
+
20
+ # Include this module in models inheriting from AR::Base
21
+ module Changeling
22
+ def self.included(base)
23
+ # Include this in AR models only
24
+ return unless base.ancestors.include?(ActiveRecord::Base)
25
+
26
+ base.has_many :change_log_records, as: :recordable, class_name: "ProtectedRecord::ChangeLog::Record"
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,31 @@
1
+ module ProtectedRecord
2
+ module ChangeRequest
3
+ class Record < ActiveRecord::Base
4
+ self.table_name = 'change_request_records'
5
+ attr_accessible :recordable, :user
6
+
7
+ belongs_to :recordable, polymorphic: true
8
+ belongs_to :user, class_name: "User",
9
+ foreign_key: :user_id
10
+ end
11
+
12
+ module Changer
13
+ def self.included(base)
14
+ # Include this in models only
15
+ return unless base.ancestors.include?(ActiveRecord::Base)
16
+
17
+ base.has_many :change_request_records, class_name: "ProtectedRecord::ChangeRequest::Record"
18
+ end
19
+ end
20
+
21
+ module Changeling
22
+ def self.included(base)
23
+ # Include this in models only
24
+ return unless base.ancestors.include?(ActiveRecord::Base)
25
+
26
+ base.has_many :change_request_records, as: :recordable,
27
+ class_name: "ProtectedRecord::ChangeRequest::Record"
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,21 @@
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
+
@@ -0,0 +1,43 @@
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
@@ -0,0 +1,51 @@
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
@@ -0,0 +1,80 @@
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
@@ -0,0 +1,40 @@
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
@@ -0,0 +1,3 @@
1
+ module ProtectedRecord
2
+ VERSION = "0.0.0"
3
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "protected_record/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "protected_record"
7
+ s.version = ProtectedRecord::VERSION
8
+ s.authors = ["Tad Hosford"]
9
+ s.email = ["tad.hosford@gmail.com"]
10
+ s.homepage = "http://github.com/rthbound/protected_record"
11
+ s.description = %q{
12
+ Filters changes & logs changes to protected records.
13
+ Creates change requests when changes are attempted on protected attrs
14
+ }
15
+ s.summary = %q{ }
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_development_dependency "minitest"
23
+
24
+ s.add_development_dependency "rake"
25
+ s.add_development_dependency "pry"
26
+ end
@@ -0,0 +1,12 @@
1
+ # Testing frameworks
2
+ require "minitest/spec"
3
+ require "minitest/autorun"
4
+
5
+ # Debugger
6
+ require "pry"
7
+
8
+ # The gem
9
+ $: << File.dirname(__FILE__) + "/../lib"
10
+ $: << File.dirname(__FILE__)
11
+
12
+ require "protected_record"
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: protected_record
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.0
6
+ platform: ruby
7
+ authors:
8
+ - Tad Hosford
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-04-19 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: minitest
16
+ version_requirements: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ! '>='
19
+ - !ruby/object:Gem::Version
20
+ version: !binary |-
21
+ MA==
22
+ none: false
23
+ requirement: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ! '>='
26
+ - !ruby/object:Gem::Version
27
+ version: !binary |-
28
+ MA==
29
+ none: false
30
+ prerelease: false
31
+ type: :development
32
+ - !ruby/object:Gem::Dependency
33
+ name: rake
34
+ version_requirements: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: !binary |-
39
+ MA==
40
+ none: false
41
+ requirement: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: !binary |-
46
+ MA==
47
+ none: false
48
+ prerelease: false
49
+ type: :development
50
+ - !ruby/object:Gem::Dependency
51
+ name: pry
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ! '>='
55
+ - !ruby/object:Gem::Version
56
+ version: !binary |-
57
+ MA==
58
+ none: false
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ! '>='
62
+ - !ruby/object:Gem::Version
63
+ version: !binary |-
64
+ MA==
65
+ none: false
66
+ prerelease: false
67
+ type: :development
68
+ description: ! "\n Filters changes & logs changes to protected\
69
+ \ records.\n Creates change requests when changes are attempted\
70
+ \ on protected attrs\n "
71
+ email:
72
+ - tad.hosford@gmail.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - .gitignore
78
+ - Gemfile
79
+ - README.md
80
+ - lib/protected_record.rb
81
+ - lib/protected_record/change_log.rb
82
+ - lib/protected_record/change_request.rb
83
+ - lib/protected_record/use_case/base.rb
84
+ - lib/protected_record/use_case/change/change_log/create.rb
85
+ - lib/protected_record/use_case/change/change_request/create.rb
86
+ - lib/protected_record/use_case/change/update.rb
87
+ - lib/protected_record/use_case/result.rb
88
+ - lib/protected_record/version.rb
89
+ - protected_record.gemspec
90
+ - test/minitest_helper.rb
91
+ homepage: http://github.com/rthbound/protected_record
92
+ licenses: []
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ segments:
102
+ - 0
103
+ hash: 2
104
+ version: !binary |-
105
+ MA==
106
+ none: false
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ! '>='
110
+ - !ruby/object:Gem::Version
111
+ segments:
112
+ - 0
113
+ hash: 2
114
+ version: !binary |-
115
+ MA==
116
+ none: false
117
+ requirements: []
118
+ rubyforge_project:
119
+ rubygems_version: 1.8.24
120
+ signing_key:
121
+ specification_version: 3
122
+ summary: ''
123
+ test_files: []