skinny_controllers 0.1

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