skinny_controllers 0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 22e75d7a70d070311ace3af0a33cfa1df96dce4d
4
+ data.tar.gz: cee31b4824b93e8d425746a443212c1f36ca7f31
5
+ SHA512:
6
+ metadata.gz: 0e1c99dd3bfd118eac0cb5081327328bf0dfbee23a5f93cee0a2d9fdf9f30d22eebab32aa0e7b6329a903ba88788a3e488893f12a6c0311f6153bc3a837adb61
7
+ data.tar.gz: e8ad57f6833d21b4148f8f5307e262d072698f83d114a027813297e7ad831df38afcce29436964077814935c5c3fc590855c4b2ca8335da1643cdd15800873e7
data/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # skinny-controllers
2
+ [![Gem Version](http://img.shields.io/gem/v/skinny_controllers.svg?style=flat-square)](http://badge.fury.io/rb/skinny_controllers)
3
+ [![Build Status](http://img.shields.io/travis/NullVoxPopuli/skinny_controllers.svg?style=flat-square)](https://travis-ci.org/NullVoxPopuli/skinny_controllers)
4
+ [![Code Climate](http://img.shields.io/codeclimate/github/NullVoxPopuli/skinny_controllers.svg?style=flat-square)](https://codeclimate.com/github/NullVoxPopuli/skinny_controllers)
5
+ [![Test Coverage](http://img.shields.io/codeclimate/coverage/github/NullVoxPopuli/skinny_controllers.svg?style=flat-square)](https://codeclimate.com/github/NullVoxPopuli/skinny_controllers)
6
+ [![Dependency Status](http://img.shields.io/gemnasium/NullVoxPopuli/skinny_controllers.svg?style=flat-square)](https://gemnasium.com/NullVoxPopuli/skinny_controllers)
7
+
8
+ An implementation of role-based policies and operations to help controllers lose weight.
9
+
10
+ The goal of this project is to help API apps be more slim, and separate logic as much as possible.
11
+
12
+ # Installation
13
+
14
+ ```ruby
15
+ gem 'skinny_controllers'
16
+ ```
17
+ or
18
+
19
+ `gem install skinny_controllers`
20
+
21
+ # Usage
22
+
23
+ TODO: this section
@@ -0,0 +1,54 @@
1
+ # required gems
2
+ require 'active_support'
3
+ require 'active_support/core_ext/class'
4
+ require 'active_support/core_ext/object/blank'
5
+ require 'active_support/core_ext/object/try'
6
+ require 'active_support/core_ext/string/inflections'
7
+
8
+ # files for this gem
9
+ require 'skinny_controllers/default_verbs'
10
+ require 'skinny_controllers/policy/base'
11
+ require 'skinny_controllers/operation/policy_helpers'
12
+ require 'skinny_controllers/operation/model_helpers'
13
+ require 'skinny_controllers/operation/base'
14
+ require 'skinny_controllers/operation/default'
15
+ require 'skinny_controllers/diet'
16
+ require 'skinny_controllers/version'
17
+
18
+ module SkinnyControllers
19
+ # Tells the Diet what namespace of the controller
20
+ # isn't going to be part of the model name
21
+ #
22
+ # @example
23
+ # # config/initializers/skinny_controllers.rb
24
+ # SkinnyControllers.controller_namespace = 'API'
25
+ # # 'API::' would be removed from 'API::Namespace::ObjectNamesController'
26
+ cattr_accessor :controller_namespace
27
+
28
+ #
29
+ cattr_accessor :operation_namespace do
30
+ 'Operation'.freeze
31
+ end
32
+
33
+ cattr_accessor :allow_by_default do
34
+ true
35
+ end
36
+
37
+ # the diet uses ActionController::Base's
38
+ # `action_name` method to get the current action.
39
+ # From that action, we map what verb we want to use for our operation
40
+ #
41
+ # If an action is not be listed, the action name will be
42
+ # manipulated to fit the verb naming convention.
43
+ #
44
+ # @example POST controller#send_receipt will use 'SendReceipt' for the verb
45
+ cattr_accessor :action_map do
46
+ {
47
+ 'default'.freeze => DefaultVerbs::Read,
48
+ 'create'.freeze => DefaultVerbs::Create,
49
+ 'index'.freeze => DefaultVerbs::ReadAll,
50
+ 'destroy'.freeze => DefaultVerbs::Delete,
51
+ 'update'.freeze => DefaultVerbs::Update
52
+ }
53
+ end
54
+ end
@@ -0,0 +1,14 @@
1
+ module SkinnyControllers
2
+ module DefaultVerbs
3
+ # show
4
+ Read = 'Read'.freeze
5
+ # index
6
+ ReadAll = 'ReadAll'.freeze
7
+ # create
8
+ Create = 'Create'.freeze
9
+ # destroy
10
+ Delete = 'Delete'.freeze
11
+ # update
12
+ Update = 'Update'.freeze
13
+ end
14
+ end
@@ -0,0 +1,77 @@
1
+ module SkinnyControllers
2
+ module Diet
3
+ extend ActiveSupport::Concern
4
+
5
+ # TODO: what if we want multiple operations per action?
6
+ #
7
+ # @return an instance of the operation with default parameters
8
+ def operation
9
+ @operation ||= operation_class.new(current_user, params)
10
+ end
11
+
12
+ # Assumes the operation name from the controller name
13
+ #
14
+ # @example SomeObjectsController => Operation::SomeObject::Action
15
+ #
16
+ 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
+ parent_namespace = Operation::Default.name.deconstantize
35
+ namespace = "#{parent_namespace}::#{model_name}".safe_constantize
36
+ namespace || Operation.const_set(model_name, Module.new)
37
+ end
38
+
39
+ # abstraction for `operation.run`
40
+ # useful when there is no logic needed for deciding what to
41
+ # do with an operation or if there is no logic to decide which operation
42
+ # to use
43
+ def model
44
+ @model ||= operation.run
45
+ end
46
+
47
+ private
48
+
49
+ # action name is inherited from ActionController::Base
50
+ # http://www.rubydoc.info/docs/rails/2.3.8/ActionController%2FBase%3Aaction_name
51
+ def verb_for_action
52
+ SkinnyControllers.action_map[action_name] || SkinnyControllers.action_map['default']
53
+ end
54
+
55
+ def model_name_from_controller
56
+ object_name = resource_name_from_controller.singularize
57
+ # remove the namespace if one exists
58
+ object_name.slice! controller_name_prefix
59
+ object_name
60
+ end
61
+
62
+ def resource_name_from_controller
63
+ controller_name = self.class.name
64
+ controller_name.gsub('Controller', '')
65
+ end
66
+
67
+ def operation_class_from_model(model_name)
68
+ prefix = SkinnyControllers.operation_namespace
69
+ "#{prefix}::#{model_name}::#{verb_for_action}"
70
+ end
71
+
72
+ def controller_name_prefix
73
+ namespace = SkinnyControllers.controller_namespace || ''
74
+ "#{namespace}::" if namespace
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,57 @@
1
+ module SkinnyControllers
2
+ module Operation
3
+ #
4
+ # An example Operation may looy like
5
+ #
6
+ # module Operations
7
+ # class Event::Read < Base
8
+ # def run
9
+ # model if allowed?
10
+ # end
11
+ # end
12
+ # end
13
+ #
14
+ # TODO: make the above the 'default' and not require to be defined
15
+ class Base
16
+ include PolicyHelpers
17
+ include ModelHelpers
18
+
19
+ attr_accessor :params, :current_user, :authorized_via_parent
20
+
21
+ class << self
22
+ def run(current_user, params)
23
+ new(current_user, params).run
24
+ end
25
+ end
26
+
27
+ def initialize(current_user, params)
28
+ self.current_user = current_user
29
+ self.params = params
30
+ self.authorized_via_parent = false
31
+ end
32
+
33
+ def id_from_params
34
+ unless @id_from_params
35
+ @id_from_params = params[:id]
36
+ if filter = params[:filter]
37
+ @id_from_params = filter[:id].split(',')
38
+ end
39
+ end
40
+
41
+ @id_from_params
42
+ end
43
+
44
+ def object_class
45
+ @object_class ||= object_type_of_interest.demodulize.constantize
46
+ end
47
+
48
+ def object_type_of_interest
49
+ @object_type_name ||= self.class.name.deconstantize.demodulize
50
+ end
51
+
52
+ def association_name_from_object
53
+ object_type_of_interest.tableize
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,9 @@
1
+ module SkinnyControllers
2
+ module Operation
3
+ class Default < Base
4
+ def run
5
+ model if allowed?
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,59 @@
1
+ module SkinnyControllers
2
+ module Operation
3
+ module ModelHelpers
4
+ def model
5
+ # TODO: not sure if multiple ids is a good idea here
6
+ # if we don't have a(ny) id(s), get all of them
7
+ @model ||=
8
+ if id_from_params
9
+ model_from_id
10
+ elsif params[:scope]
11
+ model_from_scope
12
+ elsif key = params.keys.grep(/_id$/)
13
+ # hopefully there is only ever one of these passed
14
+ model_from_named_id(key.first)
15
+ else
16
+ model_from_params
17
+ end
18
+ end
19
+
20
+ def scoped_model(scoped_params)
21
+ unless @scoped_model
22
+ klass_name = scoped_params[:type]
23
+ operation_name = "Operations::#{klass_name}::Read"
24
+ operation = operation_name.constantize.new(current_user, id: scoped_params[:id])
25
+ @scoped_model = operation.run
26
+ self.authorized_via_parent = !!@scoped_model
27
+ end
28
+
29
+ @scoped_model
30
+ end
31
+
32
+ def model_from_params
33
+ object_class.where(params).accessible_to(current_user)
34
+ end
35
+
36
+ def model_from_named_id(key)
37
+ name, id = key.split('_')
38
+ name = name.camelize
39
+ model_from_scope(
40
+ id: id,
41
+ type: name
42
+ )
43
+ end
44
+
45
+ def model_from_scope(scope = params[:scope])
46
+ if scoped = scoped_model(scope)
47
+ association = association_name_from_object
48
+ scoped.send(association)
49
+ else
50
+ fail "Parent object of type #{scope[:type]} not accessible"
51
+ end
52
+ end
53
+
54
+ def model_from_id
55
+ object_class.find(id_from_params)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,37 @@
1
+ module SkinnyControllers
2
+ module Operation
3
+ module PolicyHelpers
4
+ POLICY_CLASS_PREFIX = 'Policy::'.freeze
5
+ POLICY_CLASS_SUFFIX = 'Policy'.freeze
6
+ POLICY_SUFFIX = '?'.freeze
7
+
8
+ def policy_class
9
+ @policy_class ||= (
10
+ POLICY_CLASS_PREFIX +
11
+ object_type_of_interest +
12
+ POLICY_CLASS_SUFFIX
13
+ ).constantize
14
+ end
15
+
16
+ def policy_name
17
+ @policy_name ||= self.class.name.demodulize.downcase + POLICY_SUFFIX
18
+ end
19
+
20
+ def policy_for(object)
21
+ @policy ||= policy_class.new(
22
+ current_user,
23
+ object,
24
+ authorized_via_parent: authorized_via_parent)
25
+ end
26
+
27
+ def allowed?
28
+ policy_for(model)
29
+ end
30
+
31
+ # checks the policy
32
+ def allowed_for?(object)
33
+ policy_for(object).send(policy_name)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,32 @@
1
+ module SkinnyControllers
2
+ module Policy
3
+ class Base
4
+ attr_accessor :user, :object, :authorized_via_parent
5
+
6
+ def initialize(user, object, authorized_via_parent: false)
7
+ self.user = user
8
+ self.object = object
9
+ self.authorized_via_parent = authorized_via_parent
10
+ end
11
+
12
+ def default?
13
+ SkinnyControllers.allow_by_default
14
+ end
15
+
16
+ # defaults
17
+ def read?(o = object)
18
+ o.is_accessible_to? user
19
+ end
20
+
21
+ def read_all?
22
+ return true if authorized_via_parent
23
+ # This is expensive, so try to avoid it
24
+ # TODO: look in to creating a cache for
25
+ # these look ups that's invalidated upon
26
+ # object save
27
+ accessible = object.map { |ea| read?(ea) }
28
+ accessible.all?
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,3 @@
1
+ module SkinnyControllers
2
+ VERSION = 0.1
3
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: skinny_controllers
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - L. Preston Sego III
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-11-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: awesome_print
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry-byebug
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: codeclimate-test-reporter
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: An implementation of role-based policies and operations to help controllers
112
+ lose weight.
113
+ email: LPSego3+dev@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - README.md
119
+ - lib/skinny_controllers.rb
120
+ - lib/skinny_controllers/default_verbs.rb
121
+ - lib/skinny_controllers/diet.rb
122
+ - lib/skinny_controllers/operation/base.rb
123
+ - lib/skinny_controllers/operation/default.rb
124
+ - lib/skinny_controllers/operation/model_helpers.rb
125
+ - lib/skinny_controllers/operation/policy_helpers.rb
126
+ - lib/skinny_controllers/policy/base.rb
127
+ - lib/skinny_controllers/version.rb
128
+ homepage: https://github.com/NullVoxPopuli/skinny-controllers
129
+ licenses:
130
+ - MIT
131
+ metadata: {}
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '2.0'
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirements: []
147
+ rubyforge_project:
148
+ rubygems_version: 2.4.8
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: SkinnyControllers-0.1
152
+ test_files: []