snfoil 0.5.5

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.
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+ require_relative './setup_context'
5
+ require_relative './change_context'
6
+
7
+ module SnFoil
8
+ module Contexts
9
+ module DestroyContext # rubocop:disable Metrics/ModuleLength
10
+ extend ActiveSupport::Concern
11
+
12
+ included do
13
+ include SetupContext
14
+ include ChangeContext
15
+ end
16
+
17
+ class_methods do
18
+ attr_reader :i_setup_destroy_hooks, :i_before_destroy_hooks, :i_after_destroy_hooks,
19
+ :i_after_destroy_success_hooks, :i_after_destroy_failure_hooks
20
+ def destroy(id:, entity: nil, **options)
21
+ new(entity).destroy(**options, id: id)
22
+ end
23
+
24
+ def setup_destroy(method = nil, **options, &block)
25
+ raise ArgumentError, '#setup_destroy requires either a method name or a block' if method.nil? && block.nil?
26
+
27
+ (@i_setup_destroy_hooks ||= []) << { method: method, block: block, if: options[:if], unless: options[:unless] }
28
+ end
29
+
30
+ def before_destroy(method = nil, **options, &block)
31
+ raise ArgumentError, '#before_destroy requires either a method name or a block' if method.nil? && block.nil?
32
+
33
+ (@i_before_destroy_hooks ||= []) << { method: method, block: block, if: options[:if], unless: options[:unless] }
34
+ end
35
+
36
+ def after_destroy(method = nil, **options, &block)
37
+ raise ArgumentError, '#after_destroy requires either a method name or a block' if method.nil? && block.nil?
38
+
39
+ (@i_after_destroy_hooks ||= []) << { method: method, block: block, if: options[:if], unless: options[:unless] }
40
+ end
41
+
42
+ def after_destroy_success(method = nil, **options, &block)
43
+ raise ArgumentError, '#after_destroy_success requires either a method name or a block' if method.nil? && block.nil?
44
+
45
+ (@i_after_destroy_success_hooks ||= []) << { method: method, block: block, if: options[:if], unless: options[:unless] }
46
+ end
47
+
48
+ def after_destroy_failure(method = nil, **options, &block)
49
+ raise ArgumentError, '#after_destroy_failure requires either a method name or a block' if method.nil? && block.nil?
50
+
51
+ (@i_after_destroy_failure_hooks ||= []) << { method: method, block: block, if: options[:if], unless: options[:unless] }
52
+ end
53
+ end
54
+
55
+ def setup_destroy_object(id: nil, object: nil, **options)
56
+ raise ArgumentError, 'one of the following keywords is required: id, object' unless id || object
57
+
58
+ options.merge! object: wrap_object(object || scope.resolve.find(id))
59
+ end
60
+
61
+ def destroy(**options)
62
+ options[:action] = :destroy
63
+ options = before_setup_destroy_object(**options)
64
+ options = setup_destroy_object(**options)
65
+ authorize(options[:object], :destroy?, **options)
66
+ options = destroy_hooks(**options)
67
+ unwrap_object(options[:object])
68
+ end
69
+
70
+ def setup_destroy(**options)
71
+ options
72
+ end
73
+
74
+ def before_destroy(**options)
75
+ options
76
+ end
77
+
78
+ def after_destroy(**options)
79
+ options
80
+ end
81
+
82
+ def after_destroy_success(**options)
83
+ options
84
+ end
85
+
86
+ def after_destroy_failure(**options)
87
+ options
88
+ end
89
+
90
+ def setup_destroy_hooks
91
+ self.class.i_setup_destroy_hooks || []
92
+ end
93
+
94
+ def before_destroy_hooks
95
+ self.class.i_before_destroy_hooks || []
96
+ end
97
+
98
+ def after_destroy_hooks
99
+ self.class.i_after_destroy_hooks || []
100
+ end
101
+
102
+ def after_destroy_success_hooks
103
+ self.class.i_after_destroy_success_hooks || []
104
+ end
105
+
106
+ def after_destroy_failure_hooks
107
+ self.class.i_after_destroy_failure_hooks || []
108
+ end
109
+
110
+ private
111
+
112
+ def before_setup_destroy_object(**options)
113
+ options = setup_destroy(**options)
114
+ options = setup_destroy_hooks.reduce(options) { |opts, hook| run_hook(hook, opts) }
115
+ options = setup_change(**options)
116
+ options = setup_change_hooks.reduce(options) { |opts, hook| run_hook(hook, opts) }
117
+ options = setup(**options)
118
+ setup_hooks.reduce(options) { |opts, hook| run_hook(hook, opts) }
119
+ end
120
+
121
+ # This method is private to help protect the order of execution of hooks
122
+ def destroy_hooks(options)
123
+ options = before_destroy_save(options)
124
+ options = if options[:object].destroy
125
+ after_destroy_save_success(options)
126
+ else
127
+ after_destroy_save_failure(options)
128
+ end
129
+ after_destroy_save(options)
130
+ end
131
+
132
+ def before_destroy_save(options)
133
+ options = before_destroy(**options)
134
+ options = before_destroy_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
135
+ options = before_change(**options)
136
+ before_change_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
137
+ end
138
+
139
+ def after_destroy_save(options)
140
+ options = after_destroy(**options)
141
+ options = after_destroy_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
142
+ options = after_change(**options)
143
+ after_change_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
144
+ end
145
+
146
+ def after_destroy_save_success(options)
147
+ options = after_destroy_success(**options)
148
+ options = after_destroy_success_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
149
+ options = after_change_success(**options)
150
+ after_change_success_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
151
+ end
152
+
153
+ def after_destroy_save_failure(options)
154
+ options = after_destroy_failure(**options)
155
+ options = after_destroy_failure_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
156
+ options = after_change_failure(**options)
157
+ after_change_failure_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+ require_relative './change_context'
5
+
6
+ module SnFoil
7
+ module Contexts
8
+ module IndexContext
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ include SetupContext
13
+ end
14
+
15
+ class_methods do
16
+ attr_reader :i_searcher, :i_setup_index_hooks
17
+
18
+ def index(params: {}, entity: nil, **options)
19
+ new(entity).index(**options, params: params)
20
+ end
21
+
22
+ def searcher(klass = nil)
23
+ @i_searcher = klass
24
+ end
25
+
26
+ def setup_index(method = nil, **options, &block)
27
+ raise ArgumentError, '#setup_index requires either a method name or a block' if method.nil? && block.nil?
28
+
29
+ (@i_setup_index_hooks ||= []) << { method: method, block: block, if: options[:if], unless: options[:unless] }
30
+ end
31
+ end
32
+
33
+ def searcher
34
+ self.class.i_searcher
35
+ end
36
+
37
+ def setup_index_hooks
38
+ self.class.i_setup_index_hooks || []
39
+ end
40
+
41
+ def index(**options)
42
+ options[:action] = :index
43
+ options = before_setup_index(**options)
44
+ authorize(nil, :index?, **options)
45
+ options.fetch(:searcher) { searcher }
46
+ .new(scope: scope.resolve)
47
+ .search(options.fetch(:params) { {} })
48
+ end
49
+
50
+ def setup_index(**options)
51
+ options
52
+ end
53
+
54
+ private
55
+
56
+ def before_setup_index(**options)
57
+ options = setup_index(**options)
58
+ options = setup_index_hooks.reduce(options) { |opts, hook| run_hook(hook, opts) }
59
+ options = setup(**options)
60
+ setup_hooks.reduce(options) { |opts, hook| run_hook(hook, opts) }
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+ require 'active_support/core_ext/string/inflections'
5
+
6
+ module SnFoil
7
+ module Contexts
8
+ module SetupContext
9
+ extend ActiveSupport::Concern
10
+
11
+ class_methods do
12
+ attr_reader :i_model, :i_policy, :i_setup_hooks
13
+
14
+ def model(klass = nil)
15
+ @i_model = klass
16
+ end
17
+
18
+ def policy(klass = nil)
19
+ @i_policy = klass
20
+ end
21
+
22
+ def setup(method = nil, **options, &block)
23
+ raise ArgumentError, '#setup requires either a method name or a block' if method.nil? && block.nil?
24
+
25
+ (@i_setup_hooks ||= []) << { method: method, block: block, if: options[:if], unless: options[:unless] }
26
+ end
27
+ end
28
+
29
+ def model
30
+ self.class.i_model
31
+ end
32
+
33
+ def policy
34
+ self.class.i_policy
35
+ end
36
+
37
+ def setup(**options)
38
+ options
39
+ end
40
+
41
+ def setup_hooks
42
+ self.class.i_setup_hooks || []
43
+ end
44
+
45
+ attr_reader :entity
46
+ def initialize(entity = nil)
47
+ @entity = entity
48
+ end
49
+
50
+ def authorize(object, action, **options)
51
+ return unless entity # Add logging
52
+
53
+ policy = lookup_policy(object, options)
54
+ raise Pundit::NotAuthorizedError, query: action, record: object, policy: policy unless policy.public_send(action)
55
+
56
+ true
57
+ end
58
+
59
+ def scope(object_class = nil, **options)
60
+ object_class ||= model
61
+ policy_name = lookup_policy(object_class, options).class.name
62
+ "#{policy_name}::Scope".safe_constantize.new(wrap_object(object_class), entity)
63
+ end
64
+
65
+ def wrap_object(object)
66
+ return object unless adapter
67
+
68
+ adapter.new(object)
69
+ end
70
+
71
+ def unwrap_object(object)
72
+ return object unless adapter
73
+
74
+ adapter?(object) ? object.__getobj__ : object
75
+ end
76
+
77
+ def adapter?(object)
78
+ return false unless adapter
79
+
80
+ object.instance_of? adapter
81
+ end
82
+
83
+ def adapter
84
+ @adapter ||= SnFoil.adapter
85
+ end
86
+
87
+ def run_hook(hook, **options)
88
+ return options unless hook_valid?(hook, **options)
89
+
90
+ return send(hook[:method], **options) if hook[:method]
91
+
92
+ instance_exec options, &hook[:block]
93
+ end
94
+
95
+ def hook_valid?(hook, **options)
96
+ return false if !hook[:if].nil? && hook[:if].call(options) == false
97
+ return false if !hook[:unless].nil? && hook[:unless].call(options) == true
98
+
99
+ true
100
+ end
101
+
102
+ private
103
+
104
+ def lookup_policy(object, options)
105
+ lookup = if options[:policy]
106
+ options[:policy].new(entity, object)
107
+ elsif policy
108
+ policy.new(entity, object)
109
+ else
110
+ Pundit.policy!(entity, object)
111
+ end
112
+
113
+ lookup.options = options if lookup.respond_to? :options=
114
+ lookup
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+ require_relative './setup_context'
5
+
6
+ module SnFoil
7
+ module Contexts
8
+ module ShowContext
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ include SetupContext
13
+ end
14
+
15
+ class_methods do
16
+ attr_reader :i_setup_show_hooks
17
+
18
+ def show(id:, entity: nil, **options)
19
+ new(entity).show(**options, id: id)
20
+ end
21
+
22
+ def setup_show(method = nil, **options, &block)
23
+ raise ArgumentError, '#setup_show requires either a method name or a block' if method.nil? && block.nil?
24
+
25
+ (@i_setup_show_hooks ||= []) << { method: method, block: block, if: options[:if], unless: options[:unless] }
26
+ end
27
+ end
28
+
29
+ def setup_show_object(id: nil, object: nil, **options)
30
+ raise ArgumentError, 'one of the following keywords is required: id, object' unless id || object
31
+
32
+ options.merge! object: wrap_object(object || scope.resolve.find(id))
33
+ end
34
+
35
+ def setup_show_hooks
36
+ self.class.i_setup_show_hooks || []
37
+ end
38
+
39
+ def show(**options)
40
+ options[:action] = :show
41
+ options = before_setup_show(**options)
42
+ options = setup_show_object(**options)
43
+ authorize(options[:object], :show?, **options)
44
+ unwrap_object options[:object]
45
+ end
46
+
47
+ def setup_show(**options)
48
+ options
49
+ end
50
+
51
+ private
52
+
53
+ def before_setup_show(**options)
54
+ options = setup_show(**options)
55
+ options = setup_show_hooks.reduce(options) { |opts, hook| run_hook(hook, opts) }
56
+ options = setup(**options)
57
+ setup_hooks.reduce(options) { |opts, hook| run_hook(hook, opts) }
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+ require_relative './setup_context'
5
+ require_relative './change_context'
6
+
7
+ module SnFoil
8
+ module Contexts
9
+ module UpdateContext # rubocop:disable Metrics/ModuleLength
10
+ extend ActiveSupport::Concern
11
+
12
+ included do
13
+ include SetupContext
14
+ include ChangeContext
15
+ end
16
+
17
+ class_methods do
18
+ attr_reader :i_setup_update_hooks, :i_before_update_hooks, :i_after_update_hooks,
19
+ :i_after_update_success_hooks, :i_after_update_failure_hooks
20
+ def update(id:, params:, entity: nil, **options)
21
+ new(entity).update(**options, id: id, params: params)
22
+ end
23
+
24
+ def setup_update(method = nil, **options, &block)
25
+ raise ArgumentError, '#setup_update requires either a method name or a block' if method.nil? && block.nil?
26
+
27
+ (@i_setup_update_hooks ||= []) << { method: method, block: block, if: options[:if], unless: options[:unless] }
28
+ end
29
+
30
+ def before_update(method = nil, **options, &block)
31
+ raise ArgumentError, '#before_update requires either a method name or a block' if method.nil? && block.nil?
32
+
33
+ (@i_before_update_hooks ||= []) << { method: method, block: block, if: options[:if], unless: options[:unless] }
34
+ end
35
+
36
+ def after_update(method = nil, **options, &block)
37
+ raise ArgumentError, '#after_update requires either a method name or a block' if method.nil? && block.nil?
38
+
39
+ (@i_after_update_hooks ||= []) << { method: method, block: block, if: options[:if], unless: options[:unless] }
40
+ end
41
+
42
+ def after_update_success(method = nil, **options, &block)
43
+ raise ArgumentError, '#after_update_success requires either a method name or a block' if method.nil? && block.nil?
44
+
45
+ (@i_after_update_success_hooks ||= []) << { method: method, block: block, if: options[:if], unless: options[:unless] }
46
+ end
47
+
48
+ def after_update_failure(method = nil, **options, &block)
49
+ raise ArgumentError, '#after_update_failure requires either a method name or a block' if method.nil? && block.nil?
50
+
51
+ (@i_after_update_failure_hooks ||= []) << { method: method, block: block, if: options[:if], unless: options[:unless] }
52
+ end
53
+ end
54
+
55
+ def setup_update_object(params: {}, id: nil, object: nil, **options)
56
+ raise ArgumentError, 'one of the following keywords is required: id, object' unless id || object
57
+
58
+ object = wrap_object(object || scope.resolve.find(id))
59
+ authorize(object, :update?, **options)
60
+ object.attributes = params
61
+ options.merge! object: object
62
+ end
63
+
64
+ def update(**options)
65
+ options[:action] = :update
66
+ options = before_setup_update_object(**options)
67
+ options = setup_update_object(**options)
68
+ authorize(options[:object], :update?, **options)
69
+ options = update_hooks(**options)
70
+ unwrap_object(options[:object])
71
+ end
72
+
73
+ def setup_update(**options)
74
+ options
75
+ end
76
+
77
+ def before_update(**options)
78
+ options
79
+ end
80
+
81
+ def after_update(**options)
82
+ options
83
+ end
84
+
85
+ def after_update_success(**options)
86
+ options
87
+ end
88
+
89
+ def after_update_failure(**options)
90
+ options
91
+ end
92
+
93
+ def setup_update_hooks
94
+ self.class.i_setup_update_hooks || []
95
+ end
96
+
97
+ def before_update_hooks
98
+ self.class.i_before_update_hooks || []
99
+ end
100
+
101
+ def after_update_hooks
102
+ self.class.i_after_update_hooks || []
103
+ end
104
+
105
+ def after_update_success_hooks
106
+ self.class.i_after_update_success_hooks || []
107
+ end
108
+
109
+ def after_update_failure_hooks
110
+ self.class.i_after_update_failure_hooks || []
111
+ end
112
+
113
+ private
114
+
115
+ def before_setup_update_object(**options)
116
+ options = setup_update(**options)
117
+ options = setup_update_hooks.reduce(options) { |opts, hook| run_hook(hook, opts) }
118
+ options = setup_change(**options)
119
+ options = setup_change_hooks.reduce(options) { |opts, hook| run_hook(hook, opts) }
120
+ options = setup(**options)
121
+ setup_hooks.reduce(options) { |opts, hook| run_hook(hook, opts) }
122
+ end
123
+
124
+ # This method is private to help protect the order of execution of hooks
125
+ def update_hooks(options)
126
+ options = before_update_save(options)
127
+ options = if options[:object].save
128
+ after_update_save_success(options)
129
+ else
130
+ after_update_save_failure(options)
131
+ end
132
+ after_update_save(options)
133
+ end
134
+
135
+ def before_update_save(options)
136
+ options = before_update(**options)
137
+ options = before_update_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
138
+ options = before_change(**options)
139
+ before_change_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
140
+ end
141
+
142
+ def after_update_save(options)
143
+ options = after_update(**options)
144
+ options = after_update_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
145
+ options = after_change(**options)
146
+ after_change_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
147
+ end
148
+
149
+ def after_update_save_success(options)
150
+ options = after_update_success(**options)
151
+ options = after_update_success_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
152
+ options = after_change_success(**options)
153
+ after_change_success_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
154
+ end
155
+
156
+ def after_update_save_failure(options)
157
+ options = after_update_failure(**options)
158
+ options = after_update_failure_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
159
+ options = after_change_failure(**options)
160
+ after_change_failure_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
161
+ end
162
+ end
163
+ end
164
+ end