skinny_controllers 0.2 → 0.3

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: ad259a752520b2a0fbfd467b7f4dd50ee97ba1a2
4
- data.tar.gz: b2a7c7b3d82895b1e66b1677f0137c6d91947503
3
+ metadata.gz: f549f005c93de6eb099efb11b80a429c99077cf0
4
+ data.tar.gz: 44dd3003008e227b826fff4c51b3c1e9d28cf9d7
5
5
  SHA512:
6
- metadata.gz: 5d276829328864ccc91cda85e6b7c866a7e22b397fe598645eb0b5b2e0414c9bf23b54b13233951f56e156221a351d1ecd753be7339cd28ba1e016f6d8696fee
7
- data.tar.gz: cc2d9d76498663038ea08a401cff03672da41115cfa1aa35b9f4ac4530129387225d699351e91f46d5b686c3411e321653f7c2b1e755fa79ffc5fdf717f59e9a
6
+ metadata.gz: 975ae25a383bee2c3b88d79b126c95be07c379cf69382ce24666ef0963cd89a6e312fa8cd530944f784c0ec63599b2afcdc294678cfe9f13f58bd4c649ee5c6d
7
+ data.tar.gz: c0a2877d7e19e90deaa5c5e43f54222ffc256fd2a7b1487ad79fd149011504288ffea052e8510bd9dd164bff20dd319f9e940727277a1d791a01ee683af92e15
data/README.md CHANGED
@@ -42,7 +42,7 @@ The above does a multitude of assumptions to make sure that you can type the lea
42
42
  3. Your model responds to `find`, and `where`
43
43
  4. Your model responds to `is_accessible_to?`. This can be changed at `SkinnyControllers.accessible_to_method`
44
44
 
45
- ### Your model name is different from your resource name
45
+ ### Your model name might be different from your resource name
46
46
  Lets say you have a JSON API resource that you'd like to render that has some additional/subset of data.
47
47
  Maybe the model is an `Event`, and the resource an `EventSummary` (which could do some aggregation of `Event` data).
48
48
 
@@ -128,11 +128,14 @@ SkinnyControllers.controller_namespace = 'API'
128
128
  ```
129
129
 
130
130
  The following options are available:
131
- - `operations_namespace`
132
- - `operations_suffix`
133
- - `policy_suffix`
134
- - `controller_namespace` defaults to `''`
135
- - `allow_by_default` defaults to `true`
136
- - `accessible_to_method` defaults to `is_accessible_to?`
137
- - `accessible_to_scope` defaults to `accessible_to`
138
- - `action_map` see [skinny_controllers.rb](./lib/skinny_controllers.rb#L61)
131
+
132
+ |Option|Default|Note|
133
+ |------|-------|----|
134
+ |`operations_namespace` | '' | Optional namespace to put all the operations in. |
135
+ |`operations_suffix`|`'Operations'` | Default suffix for the operations namespaces. |
136
+ |`policy_suffix`|`'Policy'` | Default suffix for policies classes. |
137
+ |`controller_namespace`|`''`| Global Namespace for all controllers (e.g.: `'API'`) |
138
+ |`allow_by_default`| `true` | Default permission |
139
+ |`accessible_to_method`|`is_accessible_to?`| method to call an the object that the user might be able to access |
140
+ |`accessible_to_scope`| `accessible_to`| scope / class method on an object that the user might be able to access |
141
+ |`action_map`| see [skinny_controllers.rb](./lib/skinny_controllers.rb#L61)| |
@@ -2,6 +2,10 @@ module SkinnyControllers
2
2
  module Diet
3
3
  extend ActiveSupport::Concern
4
4
 
5
+ included do
6
+ cattr_accessor :model_class
7
+ end
8
+
5
9
  # TODO: what if we want multiple operations per action?
6
10
  #
7
11
  # @return an instance of the operation with default parameters
@@ -12,73 +16,27 @@ module SkinnyControllers
12
16
  # Assumes the operation name from the controller name
13
17
  #
14
18
  # @example SomeObjectsController => Operation::SomeObject::Action
15
- #
19
+ # @return [Class] the operation class for the model and verb
16
20
  def operation_class
17
- model_name = model_name_from_controller
18
- klass_name = operation_class_from_model(model_name)
19
- klass = klass_name.safe_constantize
20
- klass || default_operation_class_for(model_name)
21
- end
22
-
23
- # dynamically creates a module for the model if it
24
- # isn't already defined
25
- def default_operation_class_for(model_name)
26
- default_operation = Operation::Default
27
- namespace = default_operation_namespace_for(model_name)
28
-
29
- default = "#{namespace.name}::Default".safe_constantize
30
- default || namespace.const_set('Default'.freeze, default_operation.dup)
31
- end
32
-
33
- def default_operation_namespace_for(model_name)
34
- desired_namespace = operation_namespace_from_model(model_name)
35
-
36
- parent_namespace = SkinnyControllers.operations_namespace
37
- namespace = "#{parent_namespace}::#{desired_namespace}".safe_constantize
38
- namespace || Object.const_set(desired_namespace, Module.new)
21
+ Lookup::Operation.from_controller(self.class.name, verb_for_action, model_class)
39
22
  end
40
23
 
41
24
  # abstraction for `operation.run`
42
25
  # useful when there is no logic needed for deciding what to
43
26
  # do with an operation or if there is no logic to decide which operation
44
27
  # to use
28
+ #
29
+ # @return [ActiveRecord::Base] the model
45
30
  def model
46
31
  @model ||= operation.run
47
32
  end
48
33
 
49
34
  private
50
35
 
51
- def operation_namespace_from_model(model_name)
52
- "#{model_name}#{SkinnyControllers::operations_suffix}"
53
- end
54
-
55
36
  # action name is inherited from ActionController::Base
56
37
  # http://www.rubydoc.info/docs/rails/2.3.8/ActionController%2FBase%3Aaction_name
57
38
  def verb_for_action
58
39
  SkinnyControllers.action_map[action_name] || SkinnyControllers.action_map['default']
59
40
  end
60
-
61
- def model_name_from_controller
62
- object_name = resource_name_from_controller.singularize
63
- # remove the namespace if one exists
64
- object_name.slice! controller_name_prefix
65
- object_name
66
- end
67
-
68
- def resource_name_from_controller
69
- controller_name = self.class.name
70
- controller_name.gsub('Controller', '')
71
- end
72
-
73
- def operation_class_from_model(model_name)
74
- prefix = SkinnyControllers.operations_namespace
75
- namespace = operation_namespace_from_model(model_name)
76
- "#{prefix}::#{namespace}::#{verb_for_action}"
77
- end
78
-
79
- def controller_name_prefix
80
- namespace = SkinnyControllers.controller_namespace || ''
81
- "#{namespace}::" if namespace
82
- end
83
41
  end
84
42
  end
@@ -0,0 +1,38 @@
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
+ controller_name.gsub('Controller', '')
10
+ end
11
+
12
+ # @example <ObjectsContreller> => Object
13
+ # @return [String] name of the class of the model
14
+ def model_name(controller)
15
+ resource_name = Controller.resource_name(controller)
16
+ # Convert Resources to Resource
17
+ object_name = resource_name.singularize
18
+ # remove the namespace if one exists
19
+ object_name.slice! namespace
20
+ object_name
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
@@ -0,0 +1,33 @@
1
+ module SkinnyControllers
2
+ module Lookup
3
+ module Model
4
+ module_function
5
+
6
+ # @example 'ObjectOperations::Verb' => Object
7
+ #
8
+ # @return [Class] class based on the operation
9
+ def class_from_operation(operation_name)
10
+ # "Namespace::Model" => "Model"
11
+ model_name = Model.name_from_operation(operation_name)
12
+ # object_type_of_interest.demodulize
13
+
14
+ # "Model" => Model
15
+ model_name.constantize
16
+ end
17
+
18
+ # @example 'Namespace::ModelOperation::Verb' => 'Model'
19
+ # @return [String] the model name corresponding to the operation
20
+ def name_from_operation(operation_name)
21
+ # operation_name is something of the form:
22
+ # Namespace::ModelOperations::Verb
23
+
24
+ # Namespace::ModelOperations::Verb => Namespace::ModelOperations
25
+ namespace = operation_name.deconstantize
26
+ # Namespace::ModelOperations => ModelOperations
27
+ nested_namespace = namespace.demodulize
28
+ # ModelOperations => Model
29
+ nested_namespace.gsub(SkinnyControllers.operations_suffix, '')
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,65 @@
1
+ module SkinnyControllers
2
+ module Lookup
3
+ module Operation
4
+ module_function
5
+
6
+ # @example ObjectOperations::Verb
7
+ #
8
+ # @param [String] model_name name of the model
9
+ # @param [String] verb the verb/action for the operation
10
+ # @return [Class] the operation based on the model name and the verb
11
+ def operation_of(model_name, verb)
12
+ klass_name = Lookup::Operation.name_from_model(model_name, verb)
13
+ klass_name.safe_constantize
14
+ end
15
+
16
+ # @param [String] controller name of the controller class
17
+ # @param [String] verb
18
+ # @param [String] model_name optional
19
+ # @return [Class] the class or default
20
+ def from_controller(controller, verb, model_name = nil)
21
+ model_name ||= Lookup::Controller.model_name(controller)
22
+ klass = operation_of(model_name, verb)
23
+ klass || default_operation_class_for(model_name)
24
+ end
25
+
26
+ # dynamically creates a module for the model if it
27
+ # isn't already defined
28
+ # @return [Class] default operation class
29
+ def default_operation_class_for(model_name)
30
+ default_operation = SkinnyControllers::Operation::Default
31
+ namespace = Lookup::Operation.default_operation_namespace_for(model_name)
32
+
33
+ default = "#{namespace.name}::Default".safe_constantize
34
+ default || namespace.const_set('Default'.freeze, default_operation.dup)
35
+ end
36
+
37
+ # @return [Class] namespace for the default operation class
38
+ def default_operation_namespace_for(model_name)
39
+ desired_namespace = namespace_from_model(model_name)
40
+
41
+ parent_namespace = SkinnyControllers.operations_namespace
42
+ namespace = "#{parent_namespace}::#{desired_namespace}".safe_constantize
43
+ namespace || Object.const_set(desired_namespace, Module.new)
44
+ end
45
+
46
+ # @example 'Object' => 'ObjectOperations'
47
+ # @return [String] the operation namespace based on the model name
48
+ def namespace_from_model(model_name)
49
+ "#{model_name}#{SkinnyControllers.operations_suffix}"
50
+ end
51
+
52
+ # @example 'Model', 'Verb' => [optional namespace]::ModelOperations::Verb
53
+ #
54
+ # @param [String] model_name name of the model
55
+ # @param [String] the verb/action for the operation
56
+ # @return [String] the operation based on the model name
57
+ def name_from_model(model_name, verb)
58
+ # this namespace is '' by default
59
+ prefix = SkinnyControllers.operations_namespace
60
+ namespace = Lookup::Operation.namespace_from_model(model_name)
61
+ "#{prefix}::#{namespace}::#{verb}"
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,25 @@
1
+ module SkinnyControllers
2
+ module Lookup
3
+ module Policy
4
+ module_function
5
+
6
+ # @param [String] name the name of the model
7
+ # @return [Class] the policy class
8
+ def class_from_model(name)
9
+ (
10
+ "#{namespace}::#{name}" +
11
+ SkinnyControllers.policy_suffix
12
+ ).constantize
13
+ end
14
+
15
+ # @param [String] class_name name of the operation class
16
+ def method_name_for_operation(class_name)
17
+ class_name.demodulize.downcase + POLICY_METHOD_SUFFIX
18
+ end
19
+
20
+ def namespace
21
+ SkinnyControllers.policies_namespace
22
+ end
23
+ end
24
+ end
25
+ end
@@ -3,8 +3,8 @@ module SkinnyControllers
3
3
  #
4
4
  # An example Operation may looy like
5
5
  #
6
- # module Operations
7
- # class Event::Read < Base
6
+ # module EventOperations
7
+ # class Read < SkinnyControllers::Policy::Base
8
8
  # def run
9
9
  # model if allowed?
10
10
  # end
@@ -13,13 +13,12 @@ module SkinnyControllers
13
13
  #
14
14
  # TODO: make the above the 'default' and not require to be defined
15
15
  class Base
16
- include PolicyHelpers
17
16
  include ModelHelpers
18
17
 
19
18
  attr_accessor :params, :current_user, :authorized_via_parent
20
19
 
21
20
  def self.run(current_user, params)
22
- object = self.new(current_user, params)
21
+ object = new(current_user, params)
23
22
  object.run
24
23
  end
25
24
 
@@ -41,34 +40,47 @@ module SkinnyControllers
41
40
  end
42
41
 
43
42
  def object_class
44
- unless @object_class
45
- # "Namespace::Model" => "Model"
46
- model_name = object_type_of_interest.demodulize
47
- # "Model" => Model
48
- @object_class = model_name.constantize
49
- end
50
-
51
- @object_class
43
+ @object_class ||= Lookup::Model.class_from_operation(self.class.name)
52
44
  end
53
45
 
54
46
  def object_type_of_interest
55
- unless @object_type_name
56
- # Namespace::ModelOperations::Verb
57
- klass_name = self.class.name
58
- # Namespace::ModelOperations::Verb => Namespace::ModelOperations
59
- namespace = klass_name.deconstantize
60
- # Namespace::ModelOperations => ModelOperations
61
- nested_namespace = namespace.demodulize
62
- # ModelOperations => Model
63
- @object_type_name = nested_namespace.gsub(SkinnyControllers.operations_suffix, '')
64
- end
65
-
66
- @object_type_name
47
+ @object_type_name ||= Lookup::Model.name_from_operation(self.class.name)
67
48
  end
68
49
 
69
50
  def association_name_from_object
70
51
  object_type_of_interest.tableize
71
52
  end
53
+
54
+ # Takes the class name of self and converts it to a Policy class name
55
+ #
56
+ # @example In Operation::Event::Read, Policy::EventPolicy is returned
57
+ def policy_class
58
+ @policy_class ||= Lookup::Policy.class_from_model(object_type_of_interest)
59
+ end
60
+
61
+ # Converts the class name to the method name to call on the policy
62
+ #
63
+ # @example Operation::Event::Read would become read?
64
+ def policy_method_name
65
+ @policy_method_name ||= Lookup::Policy.method_name_for_operation(self.class.name)
66
+ end
67
+
68
+ # @return a new policy object and caches it
69
+ def policy_for(object)
70
+ @policy ||= policy_class.new(
71
+ current_user,
72
+ object,
73
+ authorized_via_parent: authorized_via_parent)
74
+ end
75
+
76
+ def allowed?
77
+ allowed_for?(model)
78
+ end
79
+
80
+ # checks the policy
81
+ def allowed_for?(object)
82
+ policy_for(object).send(policy_method_name)
83
+ end
72
84
  end
73
85
  end
74
86
  end
@@ -4,8 +4,6 @@ module SkinnyControllers
4
4
  def model
5
5
  # TODO: not sure if multiple ids is a good idea here
6
6
  # if we don't have a(ny) id(s), get all of them
7
-
8
-
9
7
  @model ||=
10
8
  if id_from_params
11
9
  model_from_id
@@ -27,8 +25,8 @@ module SkinnyControllers
27
25
  def scoped_model(scoped_params)
28
26
  unless @scoped_model
29
27
  klass_name = scoped_params[:type]
30
- operation_name = operation_for(klass_name, 'Read'.freeze)
31
- operation = operation_name.constantize.new(current_user, id: scoped_params[:id])
28
+ operation_class = Lookup::Operation.operation_of(klass_name, DefaultVerbs::Read)
29
+ operation = operation_class.new(current_user, id: scoped_params[:id])
32
30
  @scoped_model = operation.run
33
31
  self.authorized_via_parent = !!@scoped_model
34
32
  end
@@ -36,22 +34,13 @@ module SkinnyControllers
36
34
  @scoped_model
37
35
  end
38
36
 
39
- def operation_for(klass_name, verb)
40
- operations_class_namespace +
41
- klass_name +
42
- SkinnyControllers.operations_suffix +
43
- "::#{verb}"
44
- end
45
-
46
- def operations_class_namespace
47
- namespace = SkinnyControllers.policies_namespace
48
- "#{namespace}::" if namespace
49
- end
50
-
51
37
  def model_from_params
52
38
  ar_proxy = object_class.where(sanitized_params)
53
39
 
54
40
  if ar_proxy.respond_to? SkinnyControllers.accessible_to_scope
41
+ # It's better to filter in sql, than in the app, so if there is
42
+ # a way to do the filtering in active query, do that. This will help
43
+ # mitigate n+1 query scenarios
55
44
  return ar_proxy.accessible_to(current_user)
56
45
  end
57
46
 
@@ -1,3 +1,3 @@
1
1
  module SkinnyControllers
2
- VERSION = 0.2
2
+ VERSION = 0.3
3
3
  end
@@ -7,21 +7,20 @@ require 'active_support/core_ext/string/inflections'
7
7
 
8
8
  # files for this gem
9
9
  require 'skinny_controllers/default_verbs'
10
+ require 'skinny_controllers/lookup/controller'
11
+ require 'skinny_controllers/lookup/model'
12
+ require 'skinny_controllers/lookup/operation'
13
+ require 'skinny_controllers/lookup/policy'
10
14
  require 'skinny_controllers/policy/base'
11
- require 'skinny_controllers/operation/policy_helpers'
12
15
  require 'skinny_controllers/operation/model_helpers'
13
16
  require 'skinny_controllers/operation/base'
14
17
  require 'skinny_controllers/operation/default'
15
18
  require 'skinny_controllers/diet'
16
19
  require 'skinny_controllers/version'
17
20
 
18
- # load the policies and operations to the top level name space.
19
- if defined? Rails
20
- $LOAD_PATH.unshift Rails.root + '/app/operations'
21
- $LOAD_PATH.unshift Rails.root + '/app/policies'
22
- end
23
-
24
21
  module SkinnyControllers
22
+ POLICY_METHOD_SUFFIX = '?'.freeze
23
+
25
24
  # Tells the Diet what namespace of the controller
26
25
  # isn't going to be part of the model name
27
26
  #
@@ -32,11 +31,11 @@ module SkinnyControllers
32
31
  cattr_accessor :controller_namespace
33
32
 
34
33
  cattr_accessor :operations_suffix do
35
- 'Operations'
34
+ 'Operations'.freeze
36
35
  end
37
36
 
38
37
  cattr_accessor :policy_suffix do
39
- 'Policy'
38
+ 'Policy'.freeze
40
39
  end
41
40
 
42
41
  cattr_accessor :operations_namespace do
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.2'
4
+ version: '0.3'
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: 2015-11-17 00:00:00.000000000 Z
11
+ date: 2015-11-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -189,10 +189,13 @@ files:
189
189
  - lib/skinny_controllers.rb
190
190
  - lib/skinny_controllers/default_verbs.rb
191
191
  - lib/skinny_controllers/diet.rb
192
+ - lib/skinny_controllers/lookup/controller.rb
193
+ - lib/skinny_controllers/lookup/model.rb
194
+ - lib/skinny_controllers/lookup/operation.rb
195
+ - lib/skinny_controllers/lookup/policy.rb
192
196
  - lib/skinny_controllers/operation/base.rb
193
197
  - lib/skinny_controllers/operation/default.rb
194
198
  - lib/skinny_controllers/operation/model_helpers.rb
195
- - lib/skinny_controllers/operation/policy_helpers.rb
196
199
  - lib/skinny_controllers/policy/base.rb
197
200
  - lib/skinny_controllers/version.rb
198
201
  homepage: https://github.com/NullVoxPopuli/skinny-controllers
@@ -218,5 +221,5 @@ rubyforge_project:
218
221
  rubygems_version: 2.4.8
219
222
  signing_key:
220
223
  specification_version: 4
221
- summary: SkinnyControllers-0.2
224
+ summary: SkinnyControllers-0.3
222
225
  test_files: []
@@ -1,49 +0,0 @@
1
- module SkinnyControllers
2
- module Operation
3
- module PolicyHelpers
4
- POLICY_METHOD_SUFFIX = '?'.freeze
5
-
6
- # Takes the class name of self and converts it to a Policy class name
7
- #
8
- # @example In Operation::Event::Read, Policy::EventPolicy is returned
9
- def policy_class
10
- @policy_class ||= (
11
- policy_class_namespace +
12
- object_type_of_interest +
13
- SkinnyControllers.policy_suffix
14
- ).constantize
15
- end
16
-
17
- # Converts the class name to the method name to call on the policy
18
- #
19
- # @example Operation::Event::Read would become read?
20
- def policy_method_name
21
- @policy_method_name ||= self.class.name.demodulize.downcase + POLICY_METHOD_SUFFIX
22
- end
23
-
24
- # @return a new policy object and caches it
25
- def policy_for(object)
26
- @policy ||= policy_class.new(
27
- current_user,
28
- object,
29
- authorized_via_parent: authorized_via_parent)
30
- end
31
-
32
- def allowed?
33
- allowed_for?(model)
34
- end
35
-
36
- # checks the policy
37
- def allowed_for?(object)
38
- policy_for(object).send(policy_method_name)
39
- end
40
-
41
- private
42
-
43
- def policy_class_namespace
44
- namespace = SkinnyControllers.policies_namespace
45
- "#{namespace}::" if namespace
46
- end
47
- end
48
- end
49
- end