skinny_controllers 0.8.7 → 0.9.0

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