skinny_controllers 0.1 → 0.2
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 +4 -4
- data/README.md +116 -1
- data/lib/skinny_controllers/diet.rb +12 -5
- data/lib/skinny_controllers/operation/base.rb +23 -6
- data/lib/skinny_controllers/operation/model_helpers.rb +28 -3
- data/lib/skinny_controllers/operation/policy_helpers.rb +21 -9
- data/lib/skinny_controllers/policy/base.rb +27 -2
- data/lib/skinny_controllers/version.rb +1 -1
- data/lib/skinny_controllers.rb +31 -4
- metadata +77 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ad259a752520b2a0fbfd467b7f4dd50ee97ba1a2
|
4
|
+
data.tar.gz: b2a7c7b3d82895b1e66b1677f0137c6d91947503
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5d276829328864ccc91cda85e6b7c866a7e22b397fe598645eb0b5b2e0414c9bf23b54b13233951f56e156221a351d1ecd753be7339cd28ba1e016f6d8696fee
|
7
|
+
data.tar.gz: cc2d9d76498663038ea08a401cff03672da41115cfa1aa35b9f4ac4530129387225d699351e91f46d5b686c3411e321653f7c2b1e755fa79ffc5fdf717f59e9a
|
data/README.md
CHANGED
@@ -9,6 +9,8 @@ An implementation of role-based policies and operations to help controllers lose
|
|
9
9
|
|
10
10
|
The goal of this project is to help API apps be more slim, and separate logic as much as possible.
|
11
11
|
|
12
|
+
This gem is inspired by [trailblazer](https://github.com/apotonick/trailblazer), following similar patterns, yet allowing the structure of the rails app to not be entirely overhauled.
|
13
|
+
|
12
14
|
# Installation
|
13
15
|
|
14
16
|
```ruby
|
@@ -20,4 +22,117 @@ or
|
|
20
22
|
|
21
23
|
# Usage
|
22
24
|
|
23
|
-
|
25
|
+
## In a controller:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
include SkinnyControllers::Diet
|
29
|
+
# ...
|
30
|
+
# in your action
|
31
|
+
render json: model
|
32
|
+
```
|
33
|
+
|
34
|
+
and that's it!
|
35
|
+
|
36
|
+
The above does a multitude of assumptions to make sure that you can type the least amount code possible.
|
37
|
+
|
38
|
+
1. Your controller name is based off your model name (configurable per controller)
|
39
|
+
2. Any defined policies or operations follow the formats (though they don't have to exist):
|
40
|
+
- `#{Model.name}Policy`
|
41
|
+
- `#{Model.name}Operations`
|
42
|
+
3. Your model responds to `find`, and `where`
|
43
|
+
4. Your model responds to `is_accessible_to?`. This can be changed at `SkinnyControllers.accessible_to_method`
|
44
|
+
|
45
|
+
### Your model name is different from your resource name
|
46
|
+
Lets say you have a JSON API resource that you'd like to render that has some additional/subset of data.
|
47
|
+
Maybe the model is an `Event`, and the resource an `EventSummary` (which could do some aggregation of `Event` data).
|
48
|
+
|
49
|
+
The naming of all the objects should be as follows:
|
50
|
+
- `EventSummariesController`
|
51
|
+
- `EventSummaryOperations::*`
|
52
|
+
- `EventSummaryPolicy`
|
53
|
+
- and the model is still `Event`
|
54
|
+
|
55
|
+
In `EventSummariesController`, you would make the following additions:
|
56
|
+
```ruby
|
57
|
+
class EventSummariesController < ApiController # or whatever your superclass is
|
58
|
+
include SkinnyControllers::Diet
|
59
|
+
model_class = Event
|
60
|
+
|
61
|
+
def index
|
62
|
+
render json: model, each_serializer: EventSummariesSerializer
|
63
|
+
end
|
64
|
+
|
65
|
+
def show
|
66
|
+
render json: model, serializer: EventSummariesSerializer
|
67
|
+
end
|
68
|
+
end
|
69
|
+
```
|
70
|
+
Note that `each_serializer` and `serializer` is not part of `SkinnyControllers`, and is part of [ActiveModel::Serializers](https://github.com/rails-api/active_model_serializers).
|
71
|
+
|
72
|
+
## Defining Operations
|
73
|
+
|
74
|
+
Operations should be placed in `app/operations` of your rails app.
|
75
|
+
|
76
|
+
For operations concerning an `Event`, they should be under `app/operations/event_operations/`.
|
77
|
+
|
78
|
+
Using the example from the specs:
|
79
|
+
```ruby
|
80
|
+
module EventOperations
|
81
|
+
class Read < SkinnyControllers::Operation::Base
|
82
|
+
def run
|
83
|
+
model if allowed?
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
alternatively, all operation verbs can be stored in the same file under (for example) `app/operations/user_operations.rb`
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
module UserOperations
|
93
|
+
class Read < SkinnyControllers::Operation::Base
|
94
|
+
def run
|
95
|
+
model if allowed?
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class ReadAll < SkinnyControllers::Operation::Base
|
100
|
+
def run
|
101
|
+
model if allowed?
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
```
|
106
|
+
|
107
|
+
|
108
|
+
## Defining Policies
|
109
|
+
|
110
|
+
Policies should be placed in `app/policies` of your rails app.
|
111
|
+
These are where you define your access logic, and how to decide if a user has access to the `object`
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
class EventPolicy < SkinnyControllers::Policy::Base
|
115
|
+
def read?(o = object)
|
116
|
+
o.is_accessible_to?(user)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
```
|
120
|
+
|
121
|
+
|
122
|
+
## Globally Configurable Options
|
123
|
+
|
124
|
+
All of these can be set on `SkinnyControllers`,
|
125
|
+
e.g.:
|
126
|
+
```ruby
|
127
|
+
SkinnyControllers.controller_namespace = 'API'
|
128
|
+
```
|
129
|
+
|
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)
|
@@ -31,9 +31,11 @@ module SkinnyControllers
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def default_operation_namespace_for(model_name)
|
34
|
-
|
35
|
-
|
36
|
-
|
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)
|
37
39
|
end
|
38
40
|
|
39
41
|
# abstraction for `operation.run`
|
@@ -46,6 +48,10 @@ module SkinnyControllers
|
|
46
48
|
|
47
49
|
private
|
48
50
|
|
51
|
+
def operation_namespace_from_model(model_name)
|
52
|
+
"#{model_name}#{SkinnyControllers::operations_suffix}"
|
53
|
+
end
|
54
|
+
|
49
55
|
# action name is inherited from ActionController::Base
|
50
56
|
# http://www.rubydoc.info/docs/rails/2.3.8/ActionController%2FBase%3Aaction_name
|
51
57
|
def verb_for_action
|
@@ -65,8 +71,9 @@ module SkinnyControllers
|
|
65
71
|
end
|
66
72
|
|
67
73
|
def operation_class_from_model(model_name)
|
68
|
-
prefix = SkinnyControllers.
|
69
|
-
|
74
|
+
prefix = SkinnyControllers.operations_namespace
|
75
|
+
namespace = operation_namespace_from_model(model_name)
|
76
|
+
"#{prefix}::#{namespace}::#{verb_for_action}"
|
70
77
|
end
|
71
78
|
|
72
79
|
def controller_name_prefix
|
@@ -18,10 +18,9 @@ module SkinnyControllers
|
|
18
18
|
|
19
19
|
attr_accessor :params, :current_user, :authorized_via_parent
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
21
|
+
def self.run(current_user, params)
|
22
|
+
object = self.new(current_user, params)
|
23
|
+
object.run
|
25
24
|
end
|
26
25
|
|
27
26
|
def initialize(current_user, params)
|
@@ -42,11 +41,29 @@ module SkinnyControllers
|
|
42
41
|
end
|
43
42
|
|
44
43
|
def object_class
|
45
|
-
@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
|
46
52
|
end
|
47
53
|
|
48
54
|
def object_type_of_interest
|
49
|
-
@object_type_name
|
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
|
50
67
|
end
|
51
68
|
|
52
69
|
def association_name_from_object
|
@@ -4,12 +4,14 @@ 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
|
+
|
7
9
|
@model ||=
|
8
10
|
if id_from_params
|
9
11
|
model_from_id
|
10
12
|
elsif params[:scope]
|
11
13
|
model_from_scope
|
12
|
-
elsif key = params.keys.grep(
|
14
|
+
elsif (key = params.keys.grep(/\_id$/)).present?
|
13
15
|
# hopefully there is only ever one of these passed
|
14
16
|
model_from_named_id(key.first)
|
15
17
|
else
|
@@ -17,10 +19,15 @@ module SkinnyControllers
|
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
22
|
+
def sanitized_params
|
23
|
+
keys = (object_class.column_names & params.keys)
|
24
|
+
params.slice(*keys).symbolize_keys
|
25
|
+
end
|
26
|
+
|
20
27
|
def scoped_model(scoped_params)
|
21
28
|
unless @scoped_model
|
22
29
|
klass_name = scoped_params[:type]
|
23
|
-
operation_name =
|
30
|
+
operation_name = operation_for(klass_name, 'Read'.freeze)
|
24
31
|
operation = operation_name.constantize.new(current_user, id: scoped_params[:id])
|
25
32
|
@scoped_model = operation.run
|
26
33
|
self.authorized_via_parent = !!@scoped_model
|
@@ -29,8 +36,26 @@ module SkinnyControllers
|
|
29
36
|
@scoped_model
|
30
37
|
end
|
31
38
|
|
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
|
+
|
32
51
|
def model_from_params
|
33
|
-
object_class.where(
|
52
|
+
ar_proxy = object_class.where(sanitized_params)
|
53
|
+
|
54
|
+
if ar_proxy.respond_to? SkinnyControllers.accessible_to_scope
|
55
|
+
return ar_proxy.accessible_to(current_user)
|
56
|
+
end
|
57
|
+
|
58
|
+
ar_proxy
|
34
59
|
end
|
35
60
|
|
36
61
|
def model_from_named_id(key)
|
@@ -1,22 +1,27 @@
|
|
1
1
|
module SkinnyControllers
|
2
2
|
module Operation
|
3
3
|
module PolicyHelpers
|
4
|
-
|
5
|
-
POLICY_CLASS_SUFFIX = 'Policy'.freeze
|
6
|
-
POLICY_SUFFIX = '?'.freeze
|
4
|
+
POLICY_METHOD_SUFFIX = '?'.freeze
|
7
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
|
8
9
|
def policy_class
|
9
10
|
@policy_class ||= (
|
10
|
-
|
11
|
+
policy_class_namespace +
|
11
12
|
object_type_of_interest +
|
12
|
-
|
13
|
+
SkinnyControllers.policy_suffix
|
13
14
|
).constantize
|
14
15
|
end
|
15
16
|
|
16
|
-
|
17
|
-
|
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
|
18
22
|
end
|
19
23
|
|
24
|
+
# @return a new policy object and caches it
|
20
25
|
def policy_for(object)
|
21
26
|
@policy ||= policy_class.new(
|
22
27
|
current_user,
|
@@ -25,12 +30,19 @@ module SkinnyControllers
|
|
25
30
|
end
|
26
31
|
|
27
32
|
def allowed?
|
28
|
-
|
33
|
+
allowed_for?(model)
|
29
34
|
end
|
30
35
|
|
31
36
|
# checks the policy
|
32
37
|
def allowed_for?(object)
|
33
|
-
policy_for(object).send(
|
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
|
34
46
|
end
|
35
47
|
end
|
36
48
|
end
|
@@ -3,21 +3,40 @@ module SkinnyControllers
|
|
3
3
|
class Base
|
4
4
|
attr_accessor :user, :object, :authorized_via_parent
|
5
5
|
|
6
|
+
# @param [User] user the being to check if they have access to `object`
|
7
|
+
# @param [ActiveRecord::Base] object the object that we are wanting to check
|
8
|
+
# the authorization of `user` for
|
9
|
+
# @param [Boolean] authorized_via_parent if this object is authorized via
|
10
|
+
# a prior authorization from a parent class / association
|
6
11
|
def initialize(user, object, authorized_via_parent: false)
|
7
12
|
self.user = user
|
8
13
|
self.object = object
|
9
14
|
self.authorized_via_parent = authorized_via_parent
|
10
15
|
end
|
11
16
|
|
17
|
+
# if a method is not defined for a particular verb or action,
|
18
|
+
# this will be used.
|
19
|
+
#
|
20
|
+
# @example Operation::Object::SendReceipt.run is called, since
|
21
|
+
# `send_receipt` doesn't exist in this class, this `default?`
|
22
|
+
# method will be used.
|
12
23
|
def default?
|
13
24
|
SkinnyControllers.allow_by_default
|
14
25
|
end
|
15
26
|
|
16
|
-
#
|
27
|
+
# this should be used when checking access to a single object
|
17
28
|
def read?(o = object)
|
18
|
-
o.
|
29
|
+
o.send(accessible_method, user)
|
19
30
|
end
|
20
31
|
|
32
|
+
# this should be used when checking access to multilpe objects
|
33
|
+
# it will call `read?` on each object of the array
|
34
|
+
#
|
35
|
+
# if the operation used a scope to find records from
|
36
|
+
# an association, then `authorized_via_parent` could be true,
|
37
|
+
# in which case, the loop would be skipped.
|
38
|
+
#
|
39
|
+
# TODO: think of a way to override the authorized_via_parent functionality
|
21
40
|
def read_all?
|
22
41
|
return true if authorized_via_parent
|
23
42
|
# This is expensive, so try to avoid it
|
@@ -27,6 +46,12 @@ module SkinnyControllers
|
|
27
46
|
accessible = object.map { |ea| read?(ea) }
|
28
47
|
accessible.all?
|
29
48
|
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def accessible_method
|
53
|
+
SkinnyControllers.accessible_to_method
|
54
|
+
end
|
30
55
|
end
|
31
56
|
end
|
32
57
|
end
|
data/lib/skinny_controllers.rb
CHANGED
@@ -15,6 +15,12 @@ require 'skinny_controllers/operation/default'
|
|
15
15
|
require 'skinny_controllers/diet'
|
16
16
|
require 'skinny_controllers/version'
|
17
17
|
|
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
|
+
|
18
24
|
module SkinnyControllers
|
19
25
|
# Tells the Diet what namespace of the controller
|
20
26
|
# isn't going to be part of the model name
|
@@ -25,15 +31,34 @@ module SkinnyControllers
|
|
25
31
|
# # 'API::' would be removed from 'API::Namespace::ObjectNamesController'
|
26
32
|
cattr_accessor :controller_namespace
|
27
33
|
|
28
|
-
|
29
|
-
|
30
|
-
|
34
|
+
cattr_accessor :operations_suffix do
|
35
|
+
'Operations'
|
36
|
+
end
|
37
|
+
|
38
|
+
cattr_accessor :policy_suffix do
|
39
|
+
'Policy'
|
40
|
+
end
|
41
|
+
|
42
|
+
cattr_accessor :operations_namespace do
|
43
|
+
''.freeze
|
44
|
+
end
|
45
|
+
|
46
|
+
cattr_accessor :policies_namespace do
|
47
|
+
''.freeze
|
31
48
|
end
|
32
49
|
|
33
50
|
cattr_accessor :allow_by_default do
|
34
51
|
true
|
35
52
|
end
|
36
53
|
|
54
|
+
cattr_accessor :accessible_to_method do
|
55
|
+
:is_accessible_to?
|
56
|
+
end
|
57
|
+
|
58
|
+
cattr_accessor :accessible_to_scope do
|
59
|
+
:accessible_to
|
60
|
+
end
|
61
|
+
|
37
62
|
# the diet uses ActionController::Base's
|
38
63
|
# `action_name` method to get the current action.
|
39
64
|
# From that action, we map what verb we want to use for our operation
|
@@ -45,9 +70,11 @@ module SkinnyControllers
|
|
45
70
|
cattr_accessor :action_map do
|
46
71
|
{
|
47
72
|
'default'.freeze => DefaultVerbs::Read,
|
48
|
-
'create'.freeze => DefaultVerbs::Create,
|
49
73
|
'index'.freeze => DefaultVerbs::ReadAll,
|
50
74
|
'destroy'.freeze => DefaultVerbs::Delete,
|
75
|
+
# these two are redundant, as the action will be
|
76
|
+
# converted to a verb via inflection
|
77
|
+
'create'.freeze => DefaultVerbs::Create,
|
51
78
|
'update'.freeze => DefaultVerbs::Update
|
52
79
|
}
|
53
80
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: skinny_controllers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.2'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- L. Preston Sego III
|
@@ -25,7 +25,7 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: rubocop
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
@@ -39,7 +39,7 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
@@ -53,7 +53,21 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: codeclimate-test-reporter
|
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: awesome_print
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
58
72
|
requirements:
|
59
73
|
- - ">="
|
@@ -81,7 +95,7 @@ dependencies:
|
|
81
95
|
- !ruby/object:Gem::Version
|
82
96
|
version: '0'
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
98
|
+
name: bundler
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
86
100
|
requirements:
|
87
101
|
- - ">="
|
@@ -95,7 +109,63 @@ dependencies:
|
|
95
109
|
- !ruby/object:Gem::Version
|
96
110
|
version: '0'
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
112
|
+
name: rails
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: factory_girl
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: factory_girl_rails
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: rspec-rails
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: sqlite3
|
99
169
|
requirement: !ruby/object:Gem::Requirement
|
100
170
|
requirements:
|
101
171
|
- - ">="
|
@@ -148,5 +218,5 @@ rubyforge_project:
|
|
148
218
|
rubygems_version: 2.4.8
|
149
219
|
signing_key:
|
150
220
|
specification_version: 4
|
151
|
-
summary: SkinnyControllers-0.
|
221
|
+
summary: SkinnyControllers-0.2
|
152
222
|
test_files: []
|