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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d45d23ac0f04c5efbf788022aade5462a6a410a1
4
- data.tar.gz: 037b8bbfb81b0e627ad9e799b4cf853c2954c862
3
+ metadata.gz: f20c80e6d30015ce35842974c6ce2bc4b559034f
4
+ data.tar.gz: d43809e60ad51ec26de9a1e329c6ec7c4d1a381b
5
5
  SHA512:
6
- metadata.gz: cb9a6efa65736d0ee7006dcfd79955f3e1aae5d7642db7813b4d61414aaa1672090997546a4f8fe6d5b3540c584d611006d331d80dcf75d3d51d1e1cbbcebb2b
7
- data.tar.gz: b0aeb2fc909bf923bb5cb00e19b625482b314a2bb2ed097b42f83c58a7a1f8397b06b633704e84fd456cb9ed7bac99abc4ac686a04b502ab4f3717ea50cc8e1c
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,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  class OperationGenerator < Rails::Generators::NamedBase
2
3
  # gives us file_name
3
4
  source_root File.expand_path('../templates', __FILE__)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  class PolicyGenerator < Rails::Generators::NamedBase
2
3
  # gives us file_name
3
4
  source_root File.expand_path('../templates', __FILE__)
@@ -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', File.join('app/controllers', class_path, "#{file_name}_controller.rb")
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 DefaultVerbs
3
4
  # show
@@ -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
- Lookup::Operation.from_controller(self.class.name, verb_for_action, self.class.model_class)
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
- Lookup::Controller.model_name(self.class.name).underscore
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
- (action_name && action_name.classify) ||
85
- SkinnyControllers.action_map['default']
95
+ (action_name && action_name.classify) ||
96
+ SkinnyControllers.action_map['default']
86
97
  end
87
98
  end
88
99
  end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+ module SkinnyControllers
3
+ class ModelNotFound < StandardError; end
4
+ class DeniedByPolicy < StandardError; end
5
+ 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
- module Lookup
3
+ class Lookup
3
4
  module Model
4
5
  module_function
5
6
 
@@ -1,5 +1,6 @@
1
+ # frozen_string_literal: true
1
2
  module SkinnyControllers
2
- module Lookup
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 => e
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.constantize
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
- module Lookup
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
- default = "#{namespace.name}::#{verb}".safe_constantize
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
- namespace = "#{parent_namespace}::#{desired_namespace}".safe_constantize
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
- module Lookup
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, :action, :params_for_action, :model_key
19
+ attr_accessor :params, :current_user, :authorized_via_parent,
20
+ :action, :params_for_action, :model_key, :_lookup
19
21
 
20
- def self.run(current_user, params)
21
- object = new(current_user, params)
22
- object.run
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, model_key = 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
- def model_class
50
- @model_class ||= Lookup::Model.class_from_operation(self.class.name)
51
- end
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
- @model.save if allowed?
16
+
17
+ check_allowed!
18
+
19
+ @model.save
16
20
  return @model
17
21
  end
18
22
 
19
- return unless allowed?
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 = (model_params == params) ? {} : model_params.symbolize_keys
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
- id: id,
89
- type: name
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 AllowAll < Base
@@ -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
- # if the method ends in a question mark, re-route to default
22
- if method_name.to_s =~ /(.+)\?/
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
- # we know that these methods don't take any parameters,
31
- # so args and block can be ignored
32
- send(:default?)
33
- else
34
- super
35
- end
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 o.send(accessible_method, user) if o.respond_to?(accessible_method)
54
+ return o.send(accessible_method, user) if o.respond_to?(accessible_method)
51
55
  default?
52
56
  end
53
57
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SkinnyControllers
2
3
  module Policy
3
4
  class Default < Base
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SkinnyControllers
2
3
  module Policy
3
4
  class DenyAll < Base
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module SkinnyControllers
2
- VERSION = '0.8.7'.freeze
3
+ VERSION = '0.9.0'.freeze
3
4
  end
@@ -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
- POLICY_METHOD_SUFFIX = '?'.freeze
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'.freeze
67
+ 'Operations'
39
68
  end
40
69
 
41
70
  cattr_accessor :policy_suffix do
42
- 'Policy'.freeze
71
+ 'Policy'
43
72
  end
44
73
 
45
74
  cattr_accessor :operations_namespace do
46
- ''.freeze
75
+ ''
47
76
  end
48
77
 
49
78
  cattr_accessor :policies_namespace do
50
- ''.freeze
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'.freeze => DefaultVerbs::Read,
84
- 'show'.freeze => DefaultVerbs::Read,
85
- 'index'.freeze => DefaultVerbs::ReadAll,
86
- 'destroy'.freeze => DefaultVerbs::Delete,
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'.freeze => DefaultVerbs::Create,
90
- 'update'.freeze => DefaultVerbs::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.8.7
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-08-15 00:00:00.000000000 Z
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/lookup/controller.rb
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.6
238
+ rubygems_version: 2.6.7
235
239
  signing_key:
236
240
  specification_version: 4
237
- summary: SkinnyControllers-0.8.7
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