skinny_controllers 0.8.7 → 0.9.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.
- checksums.yaml +4 -4
- data/LICENSE +22 -0
- data/lib/generators/operation/operation_generator.rb +1 -0
- data/lib/generators/policy/policy_generator.rb +1 -0
- data/lib/generators/skinny_controller/skinny_controller_generator.rb +3 -1
- data/lib/skinny_controllers/default_verbs.rb +1 -0
- data/lib/skinny_controllers/diet.rb +16 -5
- data/lib/skinny_controllers/exceptions.rb +5 -0
- data/lib/skinny_controllers/logging.rb +31 -0
- data/lib/skinny_controllers/lookup/ensure_existence.rb +117 -0
- data/lib/skinny_controllers/lookup/model.rb +2 -1
- data/lib/skinny_controllers/lookup/namespace.rb +8 -3
- data/lib/skinny_controllers/lookup/operation.rb +17 -12
- data/lib/skinny_controllers/lookup/policy.rb +7 -1
- data/lib/skinny_controllers/lookup.rb +139 -0
- data/lib/skinny_controllers/operation/base.rb +35 -27
- data/lib/skinny_controllers/operation/default.rb +10 -2
- data/lib/skinny_controllers/operation/model_helpers.rb +5 -5
- data/lib/skinny_controllers/policy/allow_all.rb +1 -0
- data/lib/skinny_controllers/policy/base.rb +19 -15
- data/lib/skinny_controllers/policy/default.rb +1 -0
- data/lib/skinny_controllers/policy/deny_all.rb +1 -0
- data/lib/skinny_controllers/version.rb +2 -1
- data/lib/skinny_controllers.rb +41 -12
- metadata +9 -6
- data/lib/skinny_controllers/lookup/controller.rb +0 -38
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f20c80e6d30015ce35842974c6ce2bc4b559034f
|
4
|
+
data.tar.gz: d43809e60ad51ec26de9a1e329c6ec7c4d1a381b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e059c8cf6a99882880258002dab529e429929fc98b165a69ff3b24990fd18b7b2a5b3380e7e6973daf83323ece699b4afda8de96d958d3af05432726c542300b
|
7
|
+
data.tar.gz: d6928db4a0884a190aabd50a1195f179b7f6aed7782742130ca5ed63d3bd37025a739f3288ecac98303201fd63f103c024c9605fb7bf69f4ec333dcc9cc596fd
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 L. Preston Sego III
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
class SkinnyControllerGenerator < Rails::Generators::NamedBase
|
2
3
|
# gives us file_name
|
3
4
|
source_root File.expand_path('../templates', __FILE__)
|
4
5
|
|
5
6
|
def generate_layout
|
6
|
-
template 'skinny_controller.rb.erb',
|
7
|
+
template 'skinny_controller.rb.erb',
|
8
|
+
File.join('app/controllers', class_path, "#{file_name}_controller.rb")
|
7
9
|
end
|
8
10
|
|
9
11
|
def operation_name
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module SkinnyControllers
|
2
3
|
module Diet
|
3
4
|
extend ActiveSupport::Concern
|
@@ -16,7 +17,9 @@ module SkinnyControllers
|
|
16
17
|
@operation ||= operation_class.new(
|
17
18
|
current_user,
|
18
19
|
params, params_for_action,
|
19
|
-
action_name, self.class.model_key
|
20
|
+
action_name, self.class.model_key,
|
21
|
+
_lookup
|
22
|
+
)
|
20
23
|
end
|
21
24
|
|
22
25
|
# Assumes the operation name from the controller name
|
@@ -24,7 +27,15 @@ module SkinnyControllers
|
|
24
27
|
# @example SomeObjectsController => Operation::SomeObject::Action
|
25
28
|
# @return [Class] the operation class for the model and verb
|
26
29
|
def operation_class
|
27
|
-
|
30
|
+
_lookup.operation_class
|
31
|
+
end
|
32
|
+
|
33
|
+
def _lookup
|
34
|
+
@_lookup ||= Lookup.from_controller(
|
35
|
+
controller_class: self.class,
|
36
|
+
verb: verb_for_action,
|
37
|
+
model_class: self.class.model_class
|
38
|
+
)
|
28
39
|
end
|
29
40
|
|
30
41
|
# abstraction for `operation.run`
|
@@ -61,7 +72,7 @@ module SkinnyControllers
|
|
61
72
|
elsif klass
|
62
73
|
klass.name.underscore
|
63
74
|
else
|
64
|
-
|
75
|
+
_lookup.model_name.underscore
|
65
76
|
end
|
66
77
|
|
67
78
|
action_params_method = "#{action_name}_#{model_key}_params"
|
@@ -81,8 +92,8 @@ module SkinnyControllers
|
|
81
92
|
|
82
93
|
def verb_for_action
|
83
94
|
SkinnyControllers.action_map[action_name] ||
|
84
|
-
|
85
|
-
|
95
|
+
(action_name && action_name.classify) ||
|
96
|
+
SkinnyControllers.action_map['default']
|
86
97
|
end
|
87
98
|
end
|
88
99
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module SkinnyControllers
|
3
|
+
module Logging
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
self.logger = Logger.new
|
8
|
+
end
|
9
|
+
|
10
|
+
class Logger
|
11
|
+
attr_accessor :_logger
|
12
|
+
attr_accessor :_tag
|
13
|
+
def initialize
|
14
|
+
@_logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
|
15
|
+
@_tag = 'skinny_controllers'
|
16
|
+
end
|
17
|
+
|
18
|
+
def warn(*args)
|
19
|
+
_logger.tagged(_tag) { _logger.warn(*args) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def info(*args)
|
23
|
+
_logger.tagged(_tag) { _logger.info(*args) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
attr_accessor :logger
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module SkinnyControllers
|
2
|
+
class Lookup
|
3
|
+
module EnsureExistence
|
4
|
+
module_function
|
5
|
+
|
6
|
+
# @return [Module] namespace
|
7
|
+
def ensure_namespace!(namespace)
|
8
|
+
klass = namespace_lookup(namespace)
|
9
|
+
klass || Namespace.create_namespace(namespace)
|
10
|
+
end
|
11
|
+
|
12
|
+
def ensure_operation_class!(qualified_name)
|
13
|
+
klass = operation_lookup(qualified_name)
|
14
|
+
klass || use_defailt_operation(qualified_name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def ensure_policy_class!(qualified_name)
|
18
|
+
klass = policy_lookup(qualified_name)
|
19
|
+
klass || Lookup::Policy.define_policy_class(qualified_name)
|
20
|
+
end
|
21
|
+
|
22
|
+
# This assumes the namespace already exists
|
23
|
+
# This is only to be used if there does not exist
|
24
|
+
# operation that goes by the name defined by
|
25
|
+
# qualified_name (hence the warn log at the top)
|
26
|
+
#
|
27
|
+
# @param [String] qualified_name the name of the class to create
|
28
|
+
# @example 'Api::V2::PostOperations::Create'
|
29
|
+
#
|
30
|
+
# @return [Class] a duplicate of the default operation
|
31
|
+
def use_defailt_operation(qualified_name)
|
32
|
+
SkinnyControllers.logger.warn("#{qualified_name} not found. Creating default...")
|
33
|
+
|
34
|
+
parts = qualified_name.split('::')
|
35
|
+
class_name = parts.pop
|
36
|
+
namespace = parts.join('::').safe_constantize
|
37
|
+
|
38
|
+
namespace.const_set(
|
39
|
+
class_name,
|
40
|
+
SkinnyControllers::Operation::Default.dup
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def namespace_lookup(qualified_name)
|
46
|
+
return if qualified_name.blank?
|
47
|
+
klass = qualified_name.safe_constantize
|
48
|
+
return klass if klass
|
49
|
+
return unless qualified_name.include?('::')
|
50
|
+
|
51
|
+
parts = qualified_name.split('::')
|
52
|
+
|
53
|
+
# Api::V2::CategoriesNamespace
|
54
|
+
# => Api::CategoriesNamespace
|
55
|
+
demodulized = qualified_name.demodulize
|
56
|
+
namespace = parts[0..-3]
|
57
|
+
next_lookup = [namespace, demodulized].reject(&:blank?).join('::')
|
58
|
+
result = namespace_lookup(next_lookup)
|
59
|
+
return result if result
|
60
|
+
|
61
|
+
# Api::V2::CategoriesNamespace
|
62
|
+
# => V2::CategoriesNamespace
|
63
|
+
next_lookup = parts[1..-1].join('::')
|
64
|
+
namespace_lookup(next_lookup)
|
65
|
+
end
|
66
|
+
|
67
|
+
def policy_lookup(qualified_name)
|
68
|
+
return if qualified_name.blank?
|
69
|
+
klass = qualified_name.safe_constantize
|
70
|
+
|
71
|
+
# Return if the constant exists, or if we can't travel
|
72
|
+
# up any higher.
|
73
|
+
return klass if klass
|
74
|
+
return unless qualified_name.include?('::')
|
75
|
+
|
76
|
+
# "Api::V1::CategoryPolicy"
|
77
|
+
# => "CategorPolicy"
|
78
|
+
target = qualified_name.demodulize
|
79
|
+
|
80
|
+
# "Api::V1::CategoryPolicy"
|
81
|
+
# => "Api"
|
82
|
+
namespace = qualified_name.deconstantize.deconstantize
|
83
|
+
next_lookup = [namespace, target].reject(&:blank?).join('::')
|
84
|
+
|
85
|
+
# recurse
|
86
|
+
policy_lookup(next_lookup)
|
87
|
+
end
|
88
|
+
|
89
|
+
def operation_lookup(qualified_name)
|
90
|
+
return if qualified_name.blank?
|
91
|
+
klass = qualified_name.safe_constantize
|
92
|
+
|
93
|
+
# Return if the constant exists, or if we can't travel
|
94
|
+
# up any higher.
|
95
|
+
return klass if klass
|
96
|
+
return unless qualified_name.scan(/::/).count > 1
|
97
|
+
|
98
|
+
# "Api::V1::CategoryOperations::Create"
|
99
|
+
# => "CategorOperations::Create"
|
100
|
+
parts = qualified_name.split('::')
|
101
|
+
target = parts[-2..-1]
|
102
|
+
|
103
|
+
# TODO: Lookup Chopping of namespaces going ->
|
104
|
+
# "Api::V1::CategoryOperations::Create"
|
105
|
+
# => "V1::CategoryOperaitons::Create"
|
106
|
+
|
107
|
+
# Lookup Chopping of namespaces going <-
|
108
|
+
# "Api::V1::CategoryOperations::Create"
|
109
|
+
# => "Api::CategoryOperations::Create"
|
110
|
+
namespace = parts[0..-4]
|
111
|
+
next_lookup = [namespace, target].reject(&:blank?).join('::')
|
112
|
+
|
113
|
+
# recurse
|
114
|
+
operation_lookup(next_lookup)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module SkinnyControllers
|
2
|
-
|
3
|
+
class Lookup
|
3
4
|
module Namespace
|
4
5
|
module_function
|
5
6
|
|
@@ -16,15 +17,19 @@ module SkinnyControllers
|
|
16
17
|
namespaces.each do |namespace|
|
17
18
|
current = (existing + [namespace]).join('::')
|
18
19
|
begin
|
20
|
+
# binding.pry
|
19
21
|
Object.const_get(current)
|
20
|
-
rescue NameError
|
22
|
+
rescue NameError
|
23
|
+
SkinnyControllers.logger.warn("Module #{namespace} not found, creating...")
|
21
24
|
previous.const_set(namespace, Module.new)
|
22
25
|
end
|
23
26
|
|
24
27
|
existing << namespace
|
25
|
-
previous = current.
|
28
|
+
previous = current.safe_constantize
|
26
29
|
end
|
27
30
|
|
31
|
+
SkinnyControllers.logger.warn("Namespace is nil. Attempted: '#{desired_namespace}'") unless previous
|
32
|
+
|
28
33
|
previous
|
29
34
|
end
|
30
35
|
end
|
@@ -1,5 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module SkinnyControllers
|
2
|
-
|
3
|
+
class Lookup
|
3
4
|
module Operation
|
4
5
|
module_function
|
5
6
|
|
@@ -14,15 +15,6 @@ module SkinnyControllers
|
|
14
15
|
klass || default_operation_class_for(model_name, verb)
|
15
16
|
end
|
16
17
|
|
17
|
-
# @param [String] controller name of the controller class
|
18
|
-
# @param [String] verb
|
19
|
-
# @param [String] model_name optional
|
20
|
-
# @return [Class] the class or default
|
21
|
-
def from_controller(controller, verb, model_name = nil)
|
22
|
-
model_name ||= Lookup::Controller.model_name(controller)
|
23
|
-
operation_of(model_name, verb)
|
24
|
-
end
|
25
|
-
|
26
18
|
# dynamically creates a module for the model if it
|
27
19
|
# isn't already defined
|
28
20
|
# @return [Class] default operation class
|
@@ -31,7 +23,13 @@ module SkinnyControllers
|
|
31
23
|
default_operation = SkinnyControllers::Operation::Default
|
32
24
|
namespace = Lookup::Operation.default_operation_namespace_for(model_name)
|
33
25
|
|
34
|
-
|
26
|
+
operation_class_name = "#{namespace.name}::#{verb}"
|
27
|
+
default = operation_class_name.safe_constantize
|
28
|
+
|
29
|
+
unless default
|
30
|
+
SkinnyControllers.logger.warn("#{operation_class_name} not found. Creating default...")
|
31
|
+
end
|
32
|
+
|
35
33
|
default || namespace.const_set(verb, default_operation.dup)
|
36
34
|
end
|
37
35
|
|
@@ -40,7 +38,14 @@ module SkinnyControllers
|
|
40
38
|
# binding.pry
|
41
39
|
desired_namespace = namespace_from_model(model_name)
|
42
40
|
parent_namespace = SkinnyControllers.operations_namespace
|
43
|
-
|
41
|
+
|
42
|
+
namespace_name = "#{parent_namespace}::#{desired_namespace}"
|
43
|
+
namespace = namespace_name.safe_constantize
|
44
|
+
|
45
|
+
unless namespace
|
46
|
+
SkinnyControllers.logger.warn("#{namespace_name} not found. Creating...")
|
47
|
+
end
|
48
|
+
|
44
49
|
namespace || Namespace.create_namespace(desired_namespace)
|
45
50
|
end
|
46
51
|
|
@@ -1,5 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module SkinnyControllers
|
2
|
-
|
3
|
+
class Lookup
|
3
4
|
module Policy
|
4
5
|
module_function
|
5
6
|
|
@@ -8,6 +9,11 @@ module SkinnyControllers
|
|
8
9
|
def class_from_model(name)
|
9
10
|
policy_class_name = class_name_from_model(name)
|
10
11
|
klass = policy_class_name.safe_constantize
|
12
|
+
|
13
|
+
unless klass
|
14
|
+
SkinnyControllers.logger.warn("#{policy_class_name} not found. Creating Default...")
|
15
|
+
end
|
16
|
+
|
11
17
|
klass || define_policy_class(policy_class_name)
|
12
18
|
end
|
13
19
|
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module SkinnyControllers
|
3
|
+
# This class provides a way to determine all names / classes of operations
|
4
|
+
# and policies based on he given information.
|
5
|
+
#
|
6
|
+
# The class methods show what is required for each scenario
|
7
|
+
class Lookup
|
8
|
+
include EnsureExistence
|
9
|
+
include Policy
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def from_controller(controller_class:, verb:, model_class:)
|
13
|
+
Lookup.new(
|
14
|
+
controller_class_name: controller_class.name,
|
15
|
+
verb: verb,
|
16
|
+
model_class: model_class
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
def from_operation(operation_class:)
|
21
|
+
qualified_name = operation_class.name
|
22
|
+
parts = qualified_name.split('::')
|
23
|
+
operation_name = parts[-2]
|
24
|
+
operation_parts = operation_name.split(SkinnyControllers.operations_suffix)
|
25
|
+
|
26
|
+
Lookup.new(
|
27
|
+
verb: parts.last,
|
28
|
+
# namespace: parts[0..-3],
|
29
|
+
operation_name: operation_name,
|
30
|
+
operation_class: operation_class,
|
31
|
+
model_name: operation_parts.first,
|
32
|
+
namespace: qualified_name.deconstantize.deconstantize
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param [Hash] args - all possible parameters
|
38
|
+
# - @option [String] controller_class_name
|
39
|
+
# - @option [Class] controller_class
|
40
|
+
# - @option [String] verb
|
41
|
+
# - @option [String] operation_name
|
42
|
+
# - @option [String] model_name
|
43
|
+
# - @option [Class] model_class
|
44
|
+
# - @option [Hash] options
|
45
|
+
def initialize(args = {})
|
46
|
+
@controller_class_name = args[:controller_class_name]
|
47
|
+
@controller_class = args[:controller_class]
|
48
|
+
@verb_for_action = args[:verb]
|
49
|
+
@operation_name = args[:operation_name]
|
50
|
+
@operation_class = args[:operation_class]
|
51
|
+
@model_name = args[:model_name]
|
52
|
+
@namespace = args[:namespace]
|
53
|
+
@model_class = args[:model_class]
|
54
|
+
@policy_method_name = args[:policy_method_name]
|
55
|
+
@options = args[:options] || {}
|
56
|
+
end
|
57
|
+
|
58
|
+
# PostsController
|
59
|
+
# => PostOperations::Verb
|
60
|
+
#
|
61
|
+
# Api::V2::PostsController
|
62
|
+
# => Api::V2::PostOperations::Verb
|
63
|
+
def operation_class
|
64
|
+
@operation_class ||= begin
|
65
|
+
found_namespace = ensure_namespace!(operation_namespace)
|
66
|
+
|
67
|
+
operation = namespaced_operation_name.split(found_namespace.name).last
|
68
|
+
qualified_name = found_namespace ? found_namespace.name + operation : namespaced_operation_name
|
69
|
+
|
70
|
+
ensure_operation_class!(qualified_name)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def policy_class
|
75
|
+
@policy_class ||= begin
|
76
|
+
ensure_namespace!(namespace) if namespace.present?
|
77
|
+
ensure_policy_class!(namespaced_policy_name)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def policy_method_name
|
82
|
+
@policy_method_name ||= @verb_for_action.underscore + POLICY_METHOD_SUFFIX
|
83
|
+
end
|
84
|
+
|
85
|
+
def model_class
|
86
|
+
@model_class ||= @options[:model_class] || model_name.safe_constantize
|
87
|
+
end
|
88
|
+
|
89
|
+
def model_name
|
90
|
+
@model_name ||= @model_class.try(:name) || resource_parts[-1].singularize
|
91
|
+
end
|
92
|
+
|
93
|
+
# @return [String] name of the supposed operation class
|
94
|
+
def namespaced_operation_name
|
95
|
+
@namespaced_operation_name ||= [
|
96
|
+
operation_namespace,
|
97
|
+
@verb_for_action
|
98
|
+
].reject(&:blank?).join('::')
|
99
|
+
end
|
100
|
+
|
101
|
+
def namespaced_policy_name
|
102
|
+
@namespaced_policy_name ||= [
|
103
|
+
namespace,
|
104
|
+
"#{model_name}#{SkinnyControllers.policy_suffix}"
|
105
|
+
].reject(&:blank?).join('::')
|
106
|
+
end
|
107
|
+
|
108
|
+
def operation_namespace
|
109
|
+
@operation_namespace ||= [
|
110
|
+
namespace,
|
111
|
+
operation_name
|
112
|
+
].reject(&:blank?).join('::')
|
113
|
+
end
|
114
|
+
|
115
|
+
def operation_name
|
116
|
+
@operation_name ||= "#{model_name}#{SkinnyControllers.operations_suffix}"
|
117
|
+
end
|
118
|
+
|
119
|
+
# @return [String] the namespace
|
120
|
+
def namespace
|
121
|
+
@namespace ||= begin
|
122
|
+
resource_parts.length > 1 ? resource_parts[0..-2].join('::') : ''
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# PostsController
|
127
|
+
# => Posts
|
128
|
+
#
|
129
|
+
# Api::V2::PostsController
|
130
|
+
# => Api, V2, Posts
|
131
|
+
def resource_parts
|
132
|
+
@resource_parts ||= controller_class_name.split(/::|Controller/)
|
133
|
+
end
|
134
|
+
|
135
|
+
def controller_class_name
|
136
|
+
@controller_class_name ||= @controller_class.name
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module SkinnyControllers
|
2
3
|
module Operation
|
3
4
|
#
|
@@ -15,24 +16,48 @@ module SkinnyControllers
|
|
15
16
|
class Base
|
16
17
|
include ModelHelpers
|
17
18
|
|
18
|
-
attr_accessor :params, :current_user, :authorized_via_parent,
|
19
|
+
attr_accessor :params, :current_user, :authorized_via_parent,
|
20
|
+
:action, :params_for_action, :model_key, :_lookup
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
|
22
|
+
class << self
|
23
|
+
def run(current_user, params)
|
24
|
+
object = new(current_user, params)
|
25
|
+
object.run
|
26
|
+
end
|
27
|
+
|
28
|
+
# To support the shorthand ruby/block syntax
|
29
|
+
# e.g.: MyOperation.()
|
30
|
+
alias_method :call, :run
|
23
31
|
end
|
24
32
|
|
33
|
+
# To be overridden
|
34
|
+
def run; end
|
35
|
+
|
36
|
+
# To support teh shorthand ruby/block syntax
|
37
|
+
# e.g.: MyOperation.new().()
|
38
|
+
alias_method :call, :run
|
39
|
+
|
25
40
|
# @param [Model] current_user the logged in user
|
26
41
|
# @param [Hash] controller_params the params hash raw from the controller
|
27
42
|
# @param [Hash] params_for_action optional params hash, generally the result of strong parameters
|
28
43
|
# @param [string] action the current action on the controller
|
29
|
-
def initialize(current_user, controller_params, params_for_action = nil, action = nil,
|
44
|
+
def initialize(current_user, controller_params, params_for_action = nil, action = nil,
|
45
|
+
model_key = nil, lookup = nil)
|
30
46
|
self.authorized_via_parent = false
|
31
47
|
self.current_user = current_user
|
32
48
|
self.action = action || controller_params[:action]
|
33
49
|
self.params = controller_params
|
34
50
|
self.params_for_action = params_for_action || controller_params
|
35
51
|
self.model_key = model_key
|
52
|
+
self._lookup = lookup
|
53
|
+
end
|
54
|
+
|
55
|
+
def lookup
|
56
|
+
@lookup ||= begin
|
57
|
+
_lookup || Lookup.from_operation(
|
58
|
+
operation_class: self.class
|
59
|
+
)
|
60
|
+
end
|
36
61
|
end
|
37
62
|
|
38
63
|
def id_from_params
|
@@ -46,13 +71,9 @@ module SkinnyControllers
|
|
46
71
|
@id_from_params
|
47
72
|
end
|
48
73
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
def model_name
|
54
|
-
@object_type_name ||= Lookup::Model.name_from_operation(self.class.name)
|
55
|
-
end
|
74
|
+
delegate :model_class, :model_name,
|
75
|
+
:policy_class, :policy_method_name,
|
76
|
+
to: :lookup
|
56
77
|
|
57
78
|
# @example model_name == Namespace::Item
|
58
79
|
# -> model_name.tableize == namespace/items
|
@@ -63,26 +84,13 @@ module SkinnyControllers
|
|
63
84
|
model_name.tableize.split('/').last
|
64
85
|
end
|
65
86
|
|
66
|
-
# Takes the class name of self and converts it to a Policy class name
|
67
|
-
#
|
68
|
-
# @example In Operation::Event::Read, Policy::EventPolicy is returned
|
69
|
-
def policy_class
|
70
|
-
@policy_class ||= Lookup::Policy.class_from_model(model_name)
|
71
|
-
end
|
72
|
-
|
73
|
-
# Converts the class name to the method name to call on the policy
|
74
|
-
#
|
75
|
-
# @example Operation::Event::Read would become read?
|
76
|
-
def policy_method_name
|
77
|
-
@policy_method_name ||= Lookup::Policy.method_name_for_operation(self.class.name)
|
78
|
-
end
|
79
|
-
|
80
87
|
# @return a new policy object and caches it
|
81
88
|
def policy_for(object)
|
82
89
|
@policy ||= policy_class.new(
|
83
90
|
current_user,
|
84
91
|
object,
|
85
|
-
authorized_via_parent: authorized_via_parent
|
92
|
+
authorized_via_parent: authorized_via_parent
|
93
|
+
)
|
86
94
|
end
|
87
95
|
|
88
96
|
def allowed?
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module SkinnyControllers
|
2
3
|
module Operation
|
3
4
|
class Default < Base
|
@@ -12,11 +13,14 @@ module SkinnyControllers
|
|
12
13
|
# - EventOperations::Destroy
|
13
14
|
if creating?
|
14
15
|
@model = model_class.new(model_params)
|
15
|
-
|
16
|
+
|
17
|
+
check_allowed!
|
18
|
+
|
19
|
+
@model.save
|
16
20
|
return @model
|
17
21
|
end
|
18
22
|
|
19
|
-
|
23
|
+
check_allowed!
|
20
24
|
|
21
25
|
if updating?
|
22
26
|
model.update(model_params)
|
@@ -40,6 +44,10 @@ module SkinnyControllers
|
|
40
44
|
def destroying?
|
41
45
|
action == 'destroy'
|
42
46
|
end
|
47
|
+
|
48
|
+
def check_allowed!
|
49
|
+
raise DeniedByPolicy, action unless allowed?
|
50
|
+
end
|
43
51
|
end
|
44
52
|
end
|
45
53
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module SkinnyControllers
|
2
3
|
module Operation
|
3
4
|
module ModelHelpers
|
@@ -42,7 +43,7 @@ module SkinnyControllers
|
|
42
43
|
unless @model_params
|
43
44
|
model_params = (params_for_action[model_param_name] || params_for_action)
|
44
45
|
|
45
|
-
@model_params =
|
46
|
+
@model_params = model_params == params ? {} : model_params.symbolize_keys
|
46
47
|
end
|
47
48
|
|
48
49
|
@model_params
|
@@ -84,10 +85,9 @@ module SkinnyControllers
|
|
84
85
|
def model_from_named_id(key, id)
|
85
86
|
name = key.gsub(/_id$/, '')
|
86
87
|
name = name.camelize
|
87
|
-
model_from_scope(
|
88
|
-
|
89
|
-
|
90
|
-
)
|
88
|
+
association = model_from_scope(id: id, type: name)
|
89
|
+
|
90
|
+
SkinnyControllers.search_proc.call(association)
|
91
91
|
end
|
92
92
|
|
93
93
|
def model_from_scope(scope = params[:scope])
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module SkinnyControllers
|
2
3
|
module Policy
|
3
4
|
class Base
|
@@ -18,21 +19,24 @@ module SkinnyControllers
|
|
18
19
|
# @param [Array] args
|
19
20
|
# @param [Proc] block
|
20
21
|
def method_missing(method_name, *args, &block)
|
21
|
-
#
|
22
|
-
|
23
|
-
action = Regexp.last_match(1)
|
24
|
-
# alias destroy to delete
|
25
|
-
# TODO: this means that a destroy method, if defined,
|
26
|
-
# will never be called.... good or bad?
|
27
|
-
# should there be a difference between delete and destroy?
|
28
|
-
return send('delete?'.freeze) if action == 'destroy'.freeze
|
22
|
+
# unless the method ends in a question mark, re-route to default method_missing
|
23
|
+
return super unless method_name.to_s =~ /(.+)\?/
|
29
24
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
25
|
+
action = Regexp.last_match(1)
|
26
|
+
# alias destroy to delete
|
27
|
+
# TODO: this means that a destroy method, if defined,
|
28
|
+
# will never be called.... good or bad?
|
29
|
+
# should there be a difference between delete and destroy?
|
30
|
+
return send('delete?'.freeze) if action == 'destroy'.freeze
|
31
|
+
|
32
|
+
# we know that these methods don't take any parameters,
|
33
|
+
# so args and block can be ignored
|
34
|
+
SkinnyControllers.logger.warn("method '#{action}' in policy '#{self.class.name}' was not found. Using :default?")
|
35
|
+
send(:default?)
|
36
|
+
end
|
37
|
+
|
38
|
+
def respond_to_missing?(method_name, include_private = false)
|
39
|
+
method_name.to_s =~ /(.+)\?/ || super
|
36
40
|
end
|
37
41
|
|
38
42
|
# if a method is not defined for a particular verb or action,
|
@@ -47,7 +51,7 @@ module SkinnyControllers
|
|
47
51
|
|
48
52
|
# this should be used when checking access to a single object
|
49
53
|
def read?(o = object)
|
50
|
-
return
|
54
|
+
return o.send(accessible_method, user) if o.respond_to?(accessible_method)
|
51
55
|
default?
|
52
56
|
end
|
53
57
|
|
data/lib/skinny_controllers.rb
CHANGED
@@ -1,17 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
# required gems
|
2
3
|
require 'active_support'
|
3
4
|
require 'active_support/core_ext/class'
|
4
5
|
require 'active_support/core_ext/object/blank'
|
5
6
|
require 'active_support/core_ext/object/try'
|
6
7
|
require 'active_support/core_ext/string/inflections'
|
8
|
+
require 'active_support/core_ext/module/delegation'
|
7
9
|
|
8
10
|
# files for this gem
|
9
11
|
require 'skinny_controllers/default_verbs'
|
12
|
+
require 'skinny_controllers/exceptions'
|
13
|
+
require 'skinny_controllers/logging'
|
10
14
|
require 'skinny_controllers/lookup/namespace'
|
11
|
-
require 'skinny_controllers/lookup/controller'
|
12
15
|
require 'skinny_controllers/lookup/model'
|
13
16
|
require 'skinny_controllers/lookup/operation'
|
14
17
|
require 'skinny_controllers/lookup/policy'
|
18
|
+
require 'skinny_controllers/lookup/ensure_existence'
|
19
|
+
require 'skinny_controllers/lookup'
|
15
20
|
require 'skinny_controllers/policy/base'
|
16
21
|
require 'skinny_controllers/policy/default'
|
17
22
|
require 'skinny_controllers/policy/deny_all'
|
@@ -23,7 +28,9 @@ require 'skinny_controllers/diet'
|
|
23
28
|
require 'skinny_controllers/version'
|
24
29
|
|
25
30
|
module SkinnyControllers
|
26
|
-
|
31
|
+
include Logging
|
32
|
+
|
33
|
+
POLICY_METHOD_SUFFIX = '?'
|
27
34
|
|
28
35
|
# Tells the Diet what namespace of the controller
|
29
36
|
# isn't going to be part of the model name
|
@@ -34,20 +41,42 @@ module SkinnyControllers
|
|
34
41
|
# # 'API::' would be removed from 'API::Namespace::ObjectNamesController'
|
35
42
|
cattr_accessor :controller_namespace
|
36
43
|
|
44
|
+
# Allows integration of a search gem, like ransack.
|
45
|
+
# The scope of this proc is within an operation, so all operation
|
46
|
+
# instance variables will be available.
|
47
|
+
#
|
48
|
+
# - params
|
49
|
+
# - params_for_action
|
50
|
+
# - current_user
|
51
|
+
# - action
|
52
|
+
# - model_key
|
53
|
+
# - model_params
|
54
|
+
#
|
55
|
+
# @example
|
56
|
+
# # config/initializers/skinny_controllers.rb
|
57
|
+
# SkinnyControllers.search_proc = ->(association) {
|
58
|
+
# association.ransack(params[:q]).result
|
59
|
+
# }
|
60
|
+
cattr_accessor :search_proc do
|
61
|
+
lambda do |association|
|
62
|
+
association
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
37
66
|
cattr_accessor :operations_suffix do
|
38
|
-
'Operations'
|
67
|
+
'Operations'
|
39
68
|
end
|
40
69
|
|
41
70
|
cattr_accessor :policy_suffix do
|
42
|
-
'Policy'
|
71
|
+
'Policy'
|
43
72
|
end
|
44
73
|
|
45
74
|
cattr_accessor :operations_namespace do
|
46
|
-
''
|
75
|
+
''
|
47
76
|
end
|
48
77
|
|
49
78
|
cattr_accessor :policies_namespace do
|
50
|
-
''
|
79
|
+
''
|
51
80
|
end
|
52
81
|
|
53
82
|
cattr_accessor :allow_by_default do
|
@@ -80,14 +109,14 @@ module SkinnyControllers
|
|
80
109
|
cattr_accessor :action_map do
|
81
110
|
{
|
82
111
|
# @note the only way default will get called, is if action_name is nil
|
83
|
-
'default'
|
84
|
-
'show'
|
85
|
-
'index'
|
86
|
-
'destroy'
|
112
|
+
'default' => DefaultVerbs::Read,
|
113
|
+
'show' => DefaultVerbs::Read,
|
114
|
+
'index' => DefaultVerbs::ReadAll,
|
115
|
+
'destroy' => DefaultVerbs::Delete,
|
87
116
|
# these two are redundant, as the action will be
|
88
117
|
# converted to a verb via inflection
|
89
|
-
'create'
|
90
|
-
'update'
|
118
|
+
'create' => DefaultVerbs::Create,
|
119
|
+
'update' => DefaultVerbs::Update
|
91
120
|
}
|
92
121
|
end
|
93
122
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: skinny_controllers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- L. Preston Sego III
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-10-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -185,6 +185,7 @@ executables: []
|
|
185
185
|
extensions: []
|
186
186
|
extra_rdoc_files: []
|
187
187
|
files:
|
188
|
+
- LICENSE
|
188
189
|
- README.md
|
189
190
|
- lib/generators/operation/USAGE
|
190
191
|
- lib/generators/operation/operation_generator.rb
|
@@ -198,7 +199,10 @@ files:
|
|
198
199
|
- lib/skinny_controllers.rb
|
199
200
|
- lib/skinny_controllers/default_verbs.rb
|
200
201
|
- lib/skinny_controllers/diet.rb
|
201
|
-
- lib/skinny_controllers/
|
202
|
+
- lib/skinny_controllers/exceptions.rb
|
203
|
+
- lib/skinny_controllers/logging.rb
|
204
|
+
- lib/skinny_controllers/lookup.rb
|
205
|
+
- lib/skinny_controllers/lookup/ensure_existence.rb
|
202
206
|
- lib/skinny_controllers/lookup/model.rb
|
203
207
|
- lib/skinny_controllers/lookup/namespace.rb
|
204
208
|
- lib/skinny_controllers/lookup/operation.rb
|
@@ -231,9 +235,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
231
235
|
version: '0'
|
232
236
|
requirements: []
|
233
237
|
rubyforge_project:
|
234
|
-
rubygems_version: 2.6.
|
238
|
+
rubygems_version: 2.6.7
|
235
239
|
signing_key:
|
236
240
|
specification_version: 4
|
237
|
-
summary: SkinnyControllers-0.
|
241
|
+
summary: SkinnyControllers-0.9.0
|
238
242
|
test_files: []
|
239
|
-
has_rdoc:
|
@@ -1,38 +0,0 @@
|
|
1
|
-
module SkinnyControllers
|
2
|
-
module Lookup
|
3
|
-
module Controller
|
4
|
-
module_function
|
5
|
-
|
6
|
-
# @example ObjectsController => Objects
|
7
|
-
# @return [String] the resource name
|
8
|
-
def resource_name(controller_name)
|
9
|
-
name = controller_name.gsub('Controller', '')
|
10
|
-
# remove the namespace if one exists
|
11
|
-
name.slice! namespace
|
12
|
-
name
|
13
|
-
end
|
14
|
-
|
15
|
-
# @example <ObjectsContreller> => Object
|
16
|
-
# @return [String] name of the class of the model
|
17
|
-
def model_name(controller)
|
18
|
-
resource_name = Controller.resource_name(controller)
|
19
|
-
# Convert Resources to Resource
|
20
|
-
resource_name.singularize
|
21
|
-
end
|
22
|
-
|
23
|
-
# TODO: add option to configure this per controller
|
24
|
-
#
|
25
|
-
# @example
|
26
|
-
# If the controller_namespace is specified as 'API', and
|
27
|
-
# the controller name is API::ObjectsController,
|
28
|
-
# API:: will be ignored from the name. However if the
|
29
|
-
# controller_namespace is left blank, API will be assumed to
|
30
|
-
# be a part of the model's namespace.
|
31
|
-
# @return [String] the optional namespace of all controllers
|
32
|
-
def namespace
|
33
|
-
namespace = SkinnyControllers.controller_namespace || ''
|
34
|
-
"#{namespace}::" if namespace
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|