snfoil 0.8.5 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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