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