snfoil 0.8.4 → 1.0.1

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 +124 -39
  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 -120
  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,120 +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, options: options)
108
- elsif policy
109
- policy.new(entity, object, options: options)
110
- else
111
- SnFoil.logger.debug 'No policy found. Looking up policy using Pundit. `options` will not be passed to policy'
112
- Pundit.policy!(entity, object)
113
- end
114
-
115
- lookup.options = options if lookup.respond_to? :options=
116
- lookup
117
- end
118
- end
119
- end
120
- 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