yodatra 0.1.8 → 0.2.0
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.
- data/lib/yodatra/base.rb +1 -0
- data/lib/yodatra/models_controller.rb +128 -57
- data/lib/yodatra/throttling.rb +1 -1
- data/lib/yodatra/version.rb +1 -1
- data/lib/yodatra.rb +2 -1
- data/spec/data/model.rb +50 -0
- data/spec/unit/models_controller_spec.rb +109 -39
- data/spec/unit/throttling_spec.rb +22 -0
- metadata +5 -2
data/lib/yodatra/base.rb
CHANGED
@@ -1,74 +1,116 @@
|
|
1
1
|
module Yodatra
|
2
|
+
# This is a generic model controller that expose a REST API for your models.
|
3
|
+
# The responses are encoded in JSON.
|
4
|
+
#
|
5
|
+
# Simply create your controller that inherits from this class, keeping the naming convention.
|
6
|
+
#
|
7
|
+
# For example, given a <b>User</b> model, creating a <b>class UsersController < Yodatra::ModelsController</b>, it will expose these routes:
|
8
|
+
# GET /users
|
9
|
+
# => retrieves all users <i>(attributes exposed are limited by the <b>read_scope</b> method defined in the <b>UsersController</b>)</i>
|
10
|
+
#
|
11
|
+
# GET /users/:id
|
12
|
+
# => retrieves a user <i>(attributes exposed are limited by the <b>read_scope</b> method defined in the <b>UsersController</b>)</i>
|
13
|
+
#
|
14
|
+
# POST /users
|
15
|
+
# => creates a user <i>(attributes assignable are limited by the <b>user_params</b> method defined in the <b>UsersController</b>)</i>
|
16
|
+
#
|
17
|
+
# PUT /users/:id
|
18
|
+
# => updates a user <i>(attributes assignable are limited by the <b>user_params</b> method defined in the <b>UsersController</b>)</i>
|
19
|
+
#
|
20
|
+
# DELETE /users/:id
|
21
|
+
# => deletes a user
|
22
|
+
#
|
23
|
+
# If your model is referenced by another model, nested routes are also created for you. And you don't need to worry about the references/joins, they are done automaticly!
|
24
|
+
# For example, imagine a <b>Team</b> model that has many <b>User</b>s, the following routes will be exposed:
|
25
|
+
# GET /team/:team_id/users, GET /team/:team_id/users/:id, POST /team/:team_id/users, PUT /team/:team_id/users/:id and DESTROY /team/:team_id/users/:id
|
2
26
|
class ModelsController < Sinatra::Base
|
3
27
|
|
4
28
|
before do
|
5
29
|
content_type 'application/json'
|
6
30
|
end
|
7
31
|
|
32
|
+
# Generic route to target ONE resource
|
33
|
+
ONE_ROUTE =
|
34
|
+
%r{\A/([\w]+?)/([0-9]+)(?:/([\w]+?)/([0-9]+)){0,1}\Z}
|
35
|
+
|
36
|
+
# Generic route to target ALL resources
|
37
|
+
ALL_ROUTE =
|
38
|
+
%r{\A/([\w]+?)(?:/([0-9]+)/([\w]+?)){0,1}\Z}
|
39
|
+
|
8
40
|
READ_ALL = :read_all
|
9
|
-
get
|
10
|
-
|
11
|
-
|
12
|
-
|
41
|
+
get ALL_ROUTE do
|
42
|
+
retrieve_resources READ_ALL do |resource|
|
43
|
+
resource.all.as_json(read_scope).to_json
|
44
|
+
end
|
13
45
|
end
|
14
46
|
|
15
47
|
READ_ONE = :read
|
16
|
-
get
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
@one = model_name.constantize.find params[:id]
|
21
|
-
@one.as_json(read_scope).to_json
|
48
|
+
get ONE_ROUTE do
|
49
|
+
retrieve_resources READ_ONE do |resource|
|
50
|
+
resource.as_json(read_scope).to_json
|
51
|
+
end
|
22
52
|
end
|
23
53
|
|
24
54
|
CREATE_ONE = :create
|
25
|
-
post
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
hash = self.send("#{model_name.underscore}_params".to_sym)
|
30
|
-
@one = model_name.constantize.new hash
|
55
|
+
post ALL_ROUTE do
|
56
|
+
retrieve_resources CREATE_ONE do |resource|
|
57
|
+
hash = self.send("#{model_name.underscore}_params".to_sym)
|
58
|
+
@one = resource.create hash
|
31
59
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
60
|
+
if @one.save
|
61
|
+
@one.as_json(read_scope).to_json
|
62
|
+
else
|
63
|
+
status 400
|
64
|
+
@one.errors.full_messages.to_json
|
65
|
+
end
|
37
66
|
end
|
38
67
|
end
|
39
68
|
|
40
69
|
UPDATE_ONE = :update
|
41
|
-
put
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
if !@one.nil? && @one.update_attributes(params)
|
48
|
-
@one.as_json(read_scope).to_json
|
49
|
-
else
|
50
|
-
status 400
|
51
|
-
if !@one.nil?
|
52
|
-
@one.errors.full_messages.to_json
|
70
|
+
put ONE_ROUTE do
|
71
|
+
retrieve_resources UPDATE_ONE do |resource|
|
72
|
+
hash = self.send("#{model_name.underscore}_params".to_sym)
|
73
|
+
if resource.update_attributes(hash)
|
74
|
+
resource.as_json(read_scope).to_json
|
53
75
|
else
|
54
|
-
|
76
|
+
status 400
|
77
|
+
resource.errors.full_messages.to_json
|
55
78
|
end
|
56
79
|
end
|
57
80
|
end
|
58
81
|
|
59
82
|
DELETE_ONE = :delete
|
60
|
-
delete
|
83
|
+
delete ONE_ROUTE do
|
84
|
+
retrieve_resources DELETE_ONE do |resource|
|
85
|
+
if resource.destroy
|
86
|
+
resource.as_json(read_scope).to_json
|
87
|
+
else
|
88
|
+
status 400
|
89
|
+
resource.errors.full_messages.to_json
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Defines a nested route or not and retrieves the correct resource (or resources)
|
95
|
+
# @param disables is the name to check if it was disabled
|
96
|
+
# @param &block to be yield with the retrieved resource
|
97
|
+
def retrieve_resources(disables)
|
61
98
|
pass unless involved?
|
62
|
-
no_route if disabled?
|
99
|
+
no_route if disabled? disables
|
63
100
|
|
64
|
-
|
101
|
+
model = model_name.constantize
|
102
|
+
nested = nested_resources if nested?
|
65
103
|
|
66
|
-
if
|
67
|
-
|
104
|
+
if model.nil? || nested.nil? && nested?
|
105
|
+
raise ActiveRecord::RecordNotFound
|
68
106
|
else
|
69
|
-
|
70
|
-
|
107
|
+
model = nested if nested?
|
108
|
+
one_id = nested? ? params[:captures].fourth : params[:captures].second if params[:captures].length == 4
|
109
|
+
model = model.find one_id unless one_id.nil?
|
110
|
+
yield(model)
|
71
111
|
end
|
112
|
+
rescue ActiveRecord::RecordNotFound
|
113
|
+
record_not_found
|
72
114
|
end
|
73
115
|
|
74
116
|
class << self
|
@@ -76,15 +118,51 @@ module Yodatra
|
|
76
118
|
self.name.split('::').last.gsub(/sController/, '')
|
77
119
|
end
|
78
120
|
|
79
|
-
|
80
|
-
|
121
|
+
# This helper gives the ability to disable default root by specifying
|
122
|
+
# a list of routes to disable.
|
123
|
+
# @param *opts list of routes to disable (e.g. :create, :destroy)
|
124
|
+
def disable(*opts)
|
125
|
+
opts.each do |key|
|
126
|
+
method = "#{key}_disabled?".to_sym
|
127
|
+
undef_method method if method_defined? method
|
128
|
+
define_method method, Proc.new {|| true}
|
129
|
+
end
|
81
130
|
end
|
82
131
|
end
|
83
132
|
|
84
133
|
private
|
85
134
|
|
135
|
+
def nested?
|
136
|
+
params[:captures].length >= 3 && params[:captures].first(3).none?(&:nil?)
|
137
|
+
end
|
138
|
+
|
139
|
+
def nested_resources
|
140
|
+
resources = nil
|
141
|
+
begin
|
142
|
+
parent_model = params[:captures].first.classify.constantize
|
143
|
+
rescue NameError
|
144
|
+
parent_model = nil
|
145
|
+
end
|
146
|
+
|
147
|
+
unless parent_model.nil?
|
148
|
+
parent = parent_model.find params[:captures].second
|
149
|
+
resources = parent.send(involved.to_sym) unless parent.reflections[involved.to_sym].nil?
|
150
|
+
end
|
151
|
+
resources
|
152
|
+
rescue ActiveRecord::RecordNotFound
|
153
|
+
nil
|
154
|
+
end
|
155
|
+
|
156
|
+
def involved
|
157
|
+
involved = params[:splat] && params[:splat].first
|
158
|
+
params[:captures].each_index { |i|
|
159
|
+
involved ||= params[:captures].last(i+1).first if !params[:captures].last(i+1).first.nil? && params[:captures].last(i+1).first.match(/[\d]+/).nil?
|
160
|
+
} unless params[:captures].nil?
|
161
|
+
involved
|
162
|
+
end
|
163
|
+
|
86
164
|
def involved?
|
87
|
-
|
165
|
+
!involved.match(/#{model_name.underscore}[s]?/).nil?
|
88
166
|
end
|
89
167
|
|
90
168
|
# read_scope defaults to all attrs of the model
|
@@ -95,7 +173,7 @@ module Yodatra
|
|
95
173
|
# create/update scope defaults to all data given in the POST/PUT
|
96
174
|
def method_missing(name, *args)
|
97
175
|
if name.to_s == "#{model_name.underscore}_params"
|
98
|
-
return params.reject{|k,v| %w(splat captures).include? k}
|
176
|
+
return params.reject{|k,v| %w(splat captures id updated_at created_at).include? k}
|
99
177
|
end
|
100
178
|
end
|
101
179
|
|
@@ -103,25 +181,18 @@ module Yodatra
|
|
103
181
|
self.class.model_name
|
104
182
|
end
|
105
183
|
|
106
|
-
def route_name
|
107
|
-
self.class.route_name
|
108
|
-
end
|
109
|
-
|
110
184
|
def disabled? key
|
111
|
-
|
185
|
+
method = ((nested? ? 'nested_' : '')+"#{key}_disabled?").to_sym
|
186
|
+
self.class.method_defined?(method) && self.send(method)
|
112
187
|
end
|
113
188
|
|
114
189
|
def no_route
|
115
190
|
pass
|
116
191
|
end
|
117
192
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
undef_method(key) if method_defined? key
|
122
|
-
define_method(key, Proc.new {|| true})
|
123
|
-
end
|
124
|
-
end
|
193
|
+
def record_not_found
|
194
|
+
status 404
|
195
|
+
['record not found'].to_json
|
125
196
|
end
|
126
197
|
|
127
198
|
end
|
data/lib/yodatra/throttling.rb
CHANGED
@@ -22,5 +22,5 @@ begin
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
rescue LoadError
|
25
|
-
raise "
|
25
|
+
raise LoadError, "In order to use Yodatra's throttling middleware you will need Redis. Add 'redis' to your Gemfile or simply gem install 'redis'", __FILE__
|
26
26
|
end
|
data/lib/yodatra/version.rb
CHANGED
data/lib/yodatra.rb
CHANGED
@@ -1 +1,2 @@
|
|
1
|
-
require 'yodatra/base'
|
1
|
+
require 'yodatra/base'
|
2
|
+
require 'yodatra/version'
|
data/spec/data/model.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# Mock model constructed for the tests
|
2
|
+
class Model
|
3
|
+
ALL = %w(a b c)
|
4
|
+
class << self
|
5
|
+
def all; ALL.map{ |e| Model.new({:data=> e }) }; end
|
6
|
+
def find(id)
|
7
|
+
if id.to_i < ALL.length
|
8
|
+
Model.new({:data => ALL[id.to_i]})
|
9
|
+
else
|
10
|
+
raise ActiveRecord::RecordNotFound
|
11
|
+
end
|
12
|
+
end
|
13
|
+
def create(param); me = self.new(param); me.save; me; end
|
14
|
+
end
|
15
|
+
def initialize(param); @data = param[:data]; self; end
|
16
|
+
def save
|
17
|
+
unless @data.nil? || @data.match(/[\d]+/)
|
18
|
+
ALL.push(@data) unless ALL.include?(@data)
|
19
|
+
true
|
20
|
+
else
|
21
|
+
false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
def update_attributes params
|
25
|
+
if params[:data] != @data
|
26
|
+
unless params[:data].match(/[\d]+/)
|
27
|
+
ALL[ALL.index(@data)] = params[:data]
|
28
|
+
@data = params[:data]
|
29
|
+
true
|
30
|
+
else
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
def destroy
|
36
|
+
if ALL.include? @data
|
37
|
+
ALL.delete @data
|
38
|
+
true
|
39
|
+
else
|
40
|
+
false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
def reflections
|
44
|
+
{:models => Hash.new}
|
45
|
+
end
|
46
|
+
def models
|
47
|
+
Model
|
48
|
+
end
|
49
|
+
def errors; []; end
|
50
|
+
end
|
@@ -1,31 +1,5 @@
|
|
1
1
|
require File.expand_path '../../spec_helper.rb', __FILE__
|
2
|
-
|
3
|
-
# Mock model constructed for the tests
|
4
|
-
class Model
|
5
|
-
ALL = %w(a b c)
|
6
|
-
class << self
|
7
|
-
def all; ALL.map{ |e| Model.new({:data=> e }) }; end
|
8
|
-
def find(id); Model.new({:data => ALL[id.to_i]}); end
|
9
|
-
end
|
10
|
-
def initialize(param); @data = param[:data]; end
|
11
|
-
def save
|
12
|
-
if @data.is_a? String
|
13
|
-
ALL.push(@data)
|
14
|
-
true
|
15
|
-
else
|
16
|
-
false
|
17
|
-
end
|
18
|
-
end
|
19
|
-
def destroy
|
20
|
-
if ALL.include? @data
|
21
|
-
ALL.delete @data
|
22
|
-
true
|
23
|
-
else
|
24
|
-
false
|
25
|
-
end
|
26
|
-
end
|
27
|
-
def errors; []; end
|
28
|
-
end
|
2
|
+
require File.expand_path '../../data/model.rb', __FILE__
|
29
3
|
|
30
4
|
describe 'Model controller' do
|
31
5
|
|
@@ -37,7 +11,15 @@ describe 'Model controller' do
|
|
37
11
|
describe 'Getting a collection of the Model' do
|
38
12
|
context 'default' do
|
39
13
|
it 'should have a GET all route' do
|
40
|
-
get '/
|
14
|
+
get '/models'
|
15
|
+
|
16
|
+
last_response.should be_ok
|
17
|
+
expect(last_response.body).to eq(Model::ALL.map{|e| {:data => e} }.to_json)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
context 'nested' do
|
21
|
+
it 'should have a GET all route' do
|
22
|
+
get '/models/1/models'
|
41
23
|
|
42
24
|
last_response.should be_ok
|
43
25
|
expect(last_response.body).to eq(Model::ALL.map{|e| {:data => e} }.to_json)
|
@@ -50,7 +32,7 @@ describe 'Model controller' do
|
|
50
32
|
end
|
51
33
|
end
|
52
34
|
it 'should fail with no route available' do
|
53
|
-
get '/
|
35
|
+
get '/models'
|
54
36
|
|
55
37
|
last_response.should_not be_ok
|
56
38
|
end
|
@@ -58,7 +40,7 @@ describe 'Model controller' do
|
|
58
40
|
end
|
59
41
|
describe 'getting an specific Model instance' do
|
60
42
|
it 'should have a GET one route' do
|
61
|
-
get '/
|
43
|
+
get '/models/2'
|
62
44
|
|
63
45
|
last_response.should be_ok
|
64
46
|
expect(last_response.body).to eq({ :data => 'c'}.to_json)
|
@@ -70,7 +52,7 @@ describe 'Model controller' do
|
|
70
52
|
end
|
71
53
|
end
|
72
54
|
it 'should fail with no route available' do
|
73
|
-
get '/
|
55
|
+
get '/models/1'
|
74
56
|
|
75
57
|
last_response.should_not be_ok
|
76
58
|
end
|
@@ -78,18 +60,18 @@ describe 'Model controller' do
|
|
78
60
|
end
|
79
61
|
describe 'creating a Model instance' do
|
80
62
|
context 'with correct model params' do
|
81
|
-
it '
|
63
|
+
it 'creates an instance, saves it and succeed' do
|
82
64
|
expect{
|
83
|
-
post '/
|
65
|
+
post '/models', {:data => 'd'}
|
84
66
|
}.to change(Model::ALL, :length).by(1)
|
85
67
|
|
86
68
|
last_response.should be_ok
|
87
69
|
end
|
88
70
|
end
|
89
71
|
context 'with incorrect params' do
|
90
|
-
it '
|
72
|
+
it 'does not create an instance and fails' do
|
91
73
|
expect{
|
92
|
-
post '/
|
74
|
+
post '/models', {}
|
93
75
|
}.to change(Model::ALL, :length).by(0)
|
94
76
|
|
95
77
|
last_response.should_not be_ok
|
@@ -103,26 +85,86 @@ describe 'Model controller' do
|
|
103
85
|
end
|
104
86
|
end
|
105
87
|
it 'should fail with no route available' do
|
106
|
-
post '/
|
88
|
+
post '/models', {:data => 'd'}
|
107
89
|
|
108
90
|
last_response.should_not be_ok
|
109
91
|
end
|
110
92
|
end
|
111
93
|
end
|
94
|
+
describe 'updating a Model instance' do
|
95
|
+
context 'that does not exist' do
|
96
|
+
it 'replies with an error' do
|
97
|
+
expect{
|
98
|
+
put '/models/21', {:data => 'e'}
|
99
|
+
}.to change(Model::ALL, :length).by(0)
|
100
|
+
|
101
|
+
last_response.should_not be_ok
|
102
|
+
expect(last_response.body).to eq(['record not found'].to_json)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
context 'that already exist' do
|
106
|
+
context 'with correct model params' do
|
107
|
+
it 'updates the model, saves it and succeed' do
|
108
|
+
expect{
|
109
|
+
put '/models/2', {:data => 'e'}
|
110
|
+
}.to change(Model::ALL, :length).by(0)
|
111
|
+
|
112
|
+
last_response.should be_ok
|
113
|
+
expect(last_response.body).to eq({ :data => 'e'}.to_json)
|
114
|
+
expect(Model.find(2).to_json).to eq({ :data => 'e'}.to_json)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
context 'with incorrect params' do
|
118
|
+
it 'replies with an error message' do
|
119
|
+
expect{
|
120
|
+
put '/models/2', {:data => 321}
|
121
|
+
}.to change(Model::ALL, :length).by(0)
|
122
|
+
|
123
|
+
last_response.should_not be_ok
|
124
|
+
expect(last_response.body).to eq(@errors.to_json)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
context 'when the updating route is disabled' do
|
128
|
+
before do
|
129
|
+
class Yodatra::ModelsController
|
130
|
+
disable :update
|
131
|
+
end
|
132
|
+
end
|
133
|
+
it 'should fail with no route available' do
|
134
|
+
put '/models', {:data => 'd'}
|
135
|
+
|
136
|
+
last_response.should_not be_ok
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
112
141
|
describe 'deleting a Model instance' do
|
113
142
|
context 'targeting an existing instance' do
|
114
143
|
it 'deletes the instance and succeed' do
|
115
144
|
expect{
|
116
|
-
delete '/
|
145
|
+
delete '/models/1'
|
117
146
|
}.to change(Model::ALL, :length).by(-1)
|
118
147
|
|
119
148
|
last_response.should be_ok
|
120
149
|
end
|
121
150
|
end
|
151
|
+
context 'targeting an existing instance but deletion fails' do
|
152
|
+
before do
|
153
|
+
allow_any_instance_of(Model).to receive(:destroy).and_return(false)
|
154
|
+
end
|
155
|
+
it 'should not delete the instance and fails' do
|
156
|
+
expect{
|
157
|
+
delete '/models/1/models/1'
|
158
|
+
}.to change(Model::ALL, :length).by(0)
|
159
|
+
|
160
|
+
last_response.should_not be_ok
|
161
|
+
expect(last_response.body).to eq(@errors.to_json)
|
162
|
+
end
|
163
|
+
end
|
122
164
|
context 'targeting a not existing instance' do
|
123
165
|
it 'does not delete any instance and fails' do
|
124
166
|
expect{
|
125
|
-
delete '/
|
167
|
+
delete '/models/6'
|
126
168
|
}.to change(Model::ALL, :length).by(0)
|
127
169
|
|
128
170
|
last_response.should_not be_ok
|
@@ -135,11 +177,39 @@ describe 'Model controller' do
|
|
135
177
|
end
|
136
178
|
end
|
137
179
|
it 'should fail with no route available' do
|
138
|
-
delete '/
|
180
|
+
delete '/models/2'
|
139
181
|
|
140
182
|
last_response.should_not be_ok
|
141
183
|
end
|
142
184
|
end
|
143
185
|
end
|
144
186
|
|
187
|
+
describe 'non existing models' do
|
188
|
+
context 'in nested routes' do
|
189
|
+
context 'with wrong route name' do
|
190
|
+
before do
|
191
|
+
class Yodatra::ModelsController
|
192
|
+
method = "read_all_disabled?".to_sym
|
193
|
+
undef_method method if method_defined? method
|
194
|
+
end
|
195
|
+
end
|
196
|
+
it 'fails with a record not found message' do
|
197
|
+
get '/modeels/1/models'
|
198
|
+
|
199
|
+
last_response.should_not be_ok
|
200
|
+
expect(last_response.body).to eq(['record not found'].to_json)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
context 'with non existant parent model' do
|
204
|
+
it 'fails with a record not found message' do
|
205
|
+
get '/models/123/models'
|
206
|
+
|
207
|
+
last_response.should_not be_ok
|
208
|
+
expect(last_response.body).to eq(['record not found'].to_json)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
|
145
215
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.expand_path '../../spec_helper.rb', __FILE__
|
2
|
+
|
3
|
+
describe 'Throttling middleware' do
|
4
|
+
context 'When redis gem is not installed' do
|
5
|
+
it 'raises an exception when required' do
|
6
|
+
expect{
|
7
|
+
require 'yodatra/throttling'
|
8
|
+
}.to raise_error LoadError, /gem install 'redis'/
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
#context 'When redis is installed' do
|
13
|
+
# it 'does not raise an exception when required' do
|
14
|
+
# allow_any_instance_of(Kernel).to receive(:require).and_call_original
|
15
|
+
# allow_any_instance_of(Kernel).to receive(:require).with('redis').and_return(true)
|
16
|
+
#
|
17
|
+
# expect{
|
18
|
+
# require 'yodatra/throttling'
|
19
|
+
# }.not_to raise_error
|
20
|
+
# end
|
21
|
+
#end
|
22
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yodatra
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-03-
|
12
|
+
date: 2014-03-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rack
|
@@ -142,8 +142,10 @@ files:
|
|
142
142
|
- lib/yodatra/throttling.rb
|
143
143
|
- lib/yodatra/utils.rb
|
144
144
|
- lib/yodatra/version.rb
|
145
|
+
- spec/data/model.rb
|
145
146
|
- spec/spec_helper.rb
|
146
147
|
- spec/unit/models_controller_spec.rb
|
148
|
+
- spec/unit/throttling_spec.rb
|
147
149
|
- yodatra.gemspec
|
148
150
|
homepage: http://squareteam.github.io/yodatra
|
149
151
|
licenses:
|
@@ -179,3 +181,4 @@ specification_version: 3
|
|
179
181
|
summary: Classy backend development with the speed of Sinatra and the power of ActiveRecord
|
180
182
|
test_files:
|
181
183
|
- spec/unit/models_controller_spec.rb
|
184
|
+
- spec/unit/throttling_spec.rb
|