snfoil 0.9.0 → 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 +15 -1
  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 +100 -33
  27. data/Rakefile +0 -4
  28. data/lib/sn_foil/context.rb +0 -24
  29. data/lib/sn_foil/contexts/build_context.rb +0 -67
  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 -164
  33. data/lib/sn_foil/contexts/index_context.rb +0 -64
  34. data/lib/sn_foil/contexts/setup_context.rb +0 -119
  35. data/lib/sn_foil/contexts/show_context.rb +0 -61
  36. data/lib/sn_foil/contexts/update_context.rb +0 -168
  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,164 +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.merge! object: wrap_object(object || scope.resolve.find(id))
60
- end
61
-
62
- def destroy(**options)
63
- options[:action] = :destroy
64
- options = before_setup_destroy_object(**options)
65
- options = setup_destroy_object(**options)
66
- authorize(options[:object], options.fetch(:authorize, :destroy?), **options)
67
- options = destroy_hooks(**options)
68
- options[:object]
69
- end
70
-
71
- def setup_destroy(**options)
72
- options
73
- end
74
-
75
- def before_destroy(**options)
76
- options
77
- end
78
-
79
- def after_destroy(**options)
80
- options
81
- end
82
-
83
- def after_destroy_success(**options)
84
- options
85
- end
86
-
87
- def after_destroy_failure(**options)
88
- options
89
- end
90
-
91
- def setup_destroy_hooks
92
- self.class.i_setup_destroy_hooks || []
93
- end
94
-
95
- def before_destroy_hooks
96
- self.class.i_before_destroy_hooks || []
97
- end
98
-
99
- def after_destroy_hooks
100
- self.class.i_after_destroy_hooks || []
101
- end
102
-
103
- def after_destroy_success_hooks
104
- self.class.i_after_destroy_success_hooks || []
105
- end
106
-
107
- def after_destroy_failure_hooks
108
- self.class.i_after_destroy_failure_hooks || []
109
- end
110
-
111
- private
112
-
113
- def before_setup_destroy_object(**options)
114
- options = setup(**options)
115
- options = setup_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
116
- options = setup_change(**options)
117
- options = setup_change_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
118
- options = setup_destroy(**options)
119
- setup_destroy_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
120
- end
121
-
122
- # This method is private to help protect the order of execution of hooks
123
- def destroy_hooks(options)
124
- options = before_destroy_save(options)
125
- destroy_successful = options[:object].destroy
126
- options[:object] = unwrap_object(options[:object])
127
- options = if destroy_successful
128
- after_destroy_save_success(options)
129
- else
130
- after_destroy_save_failure(options)
131
- end
132
- after_destroy_save(options)
133
- end
134
-
135
- def before_destroy_save(options)
136
- options = before_change(**options)
137
- options = before_change_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
138
- options = before_destroy(**options)
139
- before_destroy_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
140
- end
141
-
142
- def after_destroy_save(options)
143
- options = after_change(**options)
144
- options = after_change_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
145
- options = after_destroy(**options)
146
- after_destroy_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
147
- end
148
-
149
- def after_destroy_save_success(options)
150
- options = after_change_success(**options)
151
- options = after_change_success_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
152
- options = after_destroy_success(**options)
153
- after_destroy_success_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
154
- end
155
-
156
- def after_destroy_save_failure(options)
157
- options = after_change_failure(**options)
158
- options = after_change_failure_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
159
- options = after_destroy_failure(**options)
160
- after_destroy_failure_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
161
- end
162
- end
163
- end
164
- 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,119 +0,0 @@
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
-
47
- def initialize(entity = nil)
48
- @entity = entity
49
- end
50
-
51
- def authorize(object, action, **options)
52
- return unless entity # Add logging
53
-
54
- policy = lookup_policy(object, options)
55
- raise Pundit::NotAuthorizedError, query: action, record: object, policy: policy unless policy.public_send(action)
56
-
57
- true
58
- end
59
-
60
- def scope(object_class = nil, **options)
61
- object_class ||= model
62
- policy_name = lookup_policy(object_class, options).class.name
63
- "#{policy_name}::Scope".safe_constantize.new(wrap_object(object_class), entity)
64
- end
65
-
66
- def wrap_object(object)
67
- return object unless adapter
68
-
69
- adapter.new(object)
70
- end
71
-
72
- def unwrap_object(object)
73
- return object unless adapter
74
-
75
- adapter?(object) ? object.__getobj__ : object
76
- end
77
-
78
- def adapter?(object)
79
- return false unless adapter
80
-
81
- object.instance_of? adapter
82
- end
83
-
84
- def adapter
85
- @adapter ||= SnFoil.adapter
86
- end
87
-
88
- def run_hook(hook, **options)
89
- return options unless hook_valid?(hook, **options)
90
-
91
- return send(hook[:method], **options) if hook[:method]
92
-
93
- instance_exec **options, &hook[:block]
94
- end
95
-
96
- def hook_valid?(hook, **options)
97
- return false if !hook[:if].nil? && hook[:if].call(options) == false
98
- return false if !hook[:unless].nil? && hook[:unless].call(options) == true
99
-
100
- true
101
- end
102
-
103
- private
104
-
105
- def lookup_policy(object, options)
106
- lookup = if options[:policy]
107
- options[:policy].new(entity, object)
108
- elsif policy
109
- policy.new(entity, object)
110
- else
111
- Pundit.policy!(entity, object)
112
- end
113
-
114
- lookup.options = options if lookup.respond_to? :options=
115
- lookup
116
- end
117
- end
118
- end
119
- end
@@ -1,61 +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.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(**options)
55
- options = setup_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
56
- options = setup_show(**options)
57
- setup_show_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
58
- end
59
- end
60
- end
61
- end
@@ -1,168 +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(id: id, **options, 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.merge! object: object
64
- end
65
-
66
- def update(**options)
67
- options[:action] = :update
68
- options = before_setup_update_object(**options)
69
- options = setup_update_object(**options)
70
- authorize(options[:object], options.fetch(:authorize, :update?), **options)
71
- options = update_hooks(**options)
72
- options[:object]
73
- end
74
-
75
- def setup_update(**options)
76
- options
77
- end
78
-
79
- def before_update(**options)
80
- options
81
- end
82
-
83
- def after_update(**options)
84
- options
85
- end
86
-
87
- def after_update_success(**options)
88
- options
89
- end
90
-
91
- def after_update_failure(**options)
92
- options
93
- end
94
-
95
- def setup_update_hooks
96
- self.class.i_setup_update_hooks || []
97
- end
98
-
99
- def before_update_hooks
100
- self.class.i_before_update_hooks || []
101
- end
102
-
103
- def after_update_hooks
104
- self.class.i_after_update_hooks || []
105
- end
106
-
107
- def after_update_success_hooks
108
- self.class.i_after_update_success_hooks || []
109
- end
110
-
111
- def after_update_failure_hooks
112
- self.class.i_after_update_failure_hooks || []
113
- end
114
-
115
- private
116
-
117
- def before_setup_update_object(**options)
118
- options = setup(**options)
119
- options = setup_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
120
- options = setup_change(**options)
121
- options = setup_change_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
122
- options = setup_update(**options)
123
- setup_update_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
124
- end
125
-
126
- # This method is private to help protect the order of execution of hooks
127
- def update_hooks(options)
128
- options = before_update_save(options)
129
- update_successful = options[:object].save
130
- options[:object] = unwrap_object(options[:object])
131
- options = if update_successful
132
- after_update_save_success(options)
133
- else
134
- after_update_save_failure(options)
135
- end
136
- after_update_save(options)
137
- end
138
-
139
- def before_update_save(options)
140
- options = before_change(**options)
141
- options = before_change_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
142
- options = before_update(**options)
143
- before_update_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
144
- end
145
-
146
- def after_update_save(options)
147
- options = after_change(**options)
148
- options = after_change_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
149
- options = after_update(**options)
150
- after_update_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
151
- end
152
-
153
- def after_update_save_success(options)
154
- options = after_change_success(**options)
155
- options = after_change_success_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
156
- options = after_update_success(**options)
157
- after_update_success_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
158
- end
159
-
160
- def after_update_save_failure(options)
161
- options = after_change_failure(**options)
162
- options = after_change_failure_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
163
- options = after_update_failure(**options)
164
- after_update_failure_hooks.reduce(options) { |opts, hook| run_hook(hook, **opts) }
165
- end
166
- end
167
- end
168
- 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