squad 0.1.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 +7 -0
- data/.gitignore +0 -0
- data/LICENSE +19 -0
- data/README.md +146 -0
- data/lib/squad.rb +350 -0
- data/makefile +6 -0
- data/squad.gemspec +24 -0
- data/test/resource.rb +148 -0
- metadata +164 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 76376e0bcb5c1cf8a66971357f1a961cc548d902
|
4
|
+
data.tar.gz: 4564cd114ea26fe051f2357344b0d67c9295377c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d38c696b93a71646f5ee586b2c9e0c2c809bcbe32040a4891a2c6deee58e3e1f158b0a65f7e482018a72656c267c9e641adf2be36ead3181496e1d9f34f13a39
|
7
|
+
data.tar.gz: 9a3aba78a2696d06c346957f4adb9e2f21dee539fa133faee2f8c9d1bb569b806fd7bafa691094e58f9a6711d85196428eb96b198daec8ee61a7786a593804e9
|
data/.gitignore
ADDED
File without changes
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2017 Travis Liu
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
# Squad
|
2
|
+
|
3
|
+
Simple, efficient RESTful framework in Ruby with Redis.
|
4
|
+
|
5
|
+
Squad uses Redis to store resources inspired by [Ohm](https://github.com/soveran/ohm), and provides a simple DSL to easily develop APIs.
|
6
|
+
|
7
|
+
## Getting started
|
8
|
+
### Installation
|
9
|
+
``` conosle
|
10
|
+
gem install Squad
|
11
|
+
```
|
12
|
+
|
13
|
+
### Usage
|
14
|
+
|
15
|
+
Squad assumes Redis was started with address `localhost` and port `6379`. If you need to connect to a remote server or different port, try:
|
16
|
+
``` ruby
|
17
|
+
Squad.settings[:redis] = Redic.new 'redis://10.1.0.1:6379'
|
18
|
+
```
|
19
|
+
|
20
|
+
Hear's an example that has two resources users and products.
|
21
|
+
``` ruby
|
22
|
+
# cat hello_squad.rb
|
23
|
+
require "squad"
|
24
|
+
|
25
|
+
Squad.application do
|
26
|
+
resources :users do
|
27
|
+
attribute :name
|
28
|
+
attribute :email
|
29
|
+
|
30
|
+
collection :posts
|
31
|
+
end
|
32
|
+
|
33
|
+
resources :posts do
|
34
|
+
attribute :title
|
35
|
+
attribute :content
|
36
|
+
|
37
|
+
reference :users
|
38
|
+
end
|
39
|
+
end
|
40
|
+
```
|
41
|
+
All resources have the id attribute built in, you don't need to declare it.
|
42
|
+
|
43
|
+
To run it, you can create a `config.ru` file
|
44
|
+
``` ruby
|
45
|
+
# cat config.ru
|
46
|
+
require 'hello_squad.rb'
|
47
|
+
|
48
|
+
run Squad
|
49
|
+
```
|
50
|
+
Then run `rackup`.
|
51
|
+
|
52
|
+
Now, you already get basic CURD and relation functionality as RESTful API.
|
53
|
+
```
|
54
|
+
GET /users
|
55
|
+
POST /users
|
56
|
+
GET /users/:id
|
57
|
+
PUT /users/:id
|
58
|
+
DELETE /users/:id
|
59
|
+
GET /users/:id/posts
|
60
|
+
```
|
61
|
+
|
62
|
+
### Custom action
|
63
|
+
You can operate the single element in custom action.
|
64
|
+
``` ruby
|
65
|
+
require "squad"
|
66
|
+
|
67
|
+
Squad.application do
|
68
|
+
resources :users do
|
69
|
+
attribute :name
|
70
|
+
attribute :email
|
71
|
+
|
72
|
+
element :showcase do
|
73
|
+
# GET /users/:id/showcase
|
74
|
+
show do |params|
|
75
|
+
self.email[1..3] = 'xxx'
|
76
|
+
end
|
77
|
+
|
78
|
+
# PUT /users/:id/showcase
|
79
|
+
update do |params|
|
80
|
+
self.email = params["email"]
|
81
|
+
save
|
82
|
+
end
|
83
|
+
|
84
|
+
# DELETE /users/:id/showcase
|
85
|
+
destory do |params|
|
86
|
+
delete if self.email == params["email"]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
bulk :signup do
|
91
|
+
# POST /users/signup
|
92
|
+
create do |params|
|
93
|
+
if params["email"].include?("@gmail.com")
|
94
|
+
update_attributes(params)
|
95
|
+
save
|
96
|
+
created
|
97
|
+
else
|
98
|
+
bad_request
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
### Index
|
107
|
+
Index helps you quick lookup elements.
|
108
|
+
|
109
|
+
``` ruby
|
110
|
+
require "squad"
|
111
|
+
|
112
|
+
Squad.application do
|
113
|
+
resources :users do
|
114
|
+
attribute :name
|
115
|
+
attribute :email
|
116
|
+
|
117
|
+
# GET /users/?name=travis
|
118
|
+
index :name
|
119
|
+
end
|
120
|
+
end
|
121
|
+
```
|
122
|
+
|
123
|
+
You can have a customize query as well.
|
124
|
+
``` ruby
|
125
|
+
require "squad"
|
126
|
+
|
127
|
+
Squad.application do
|
128
|
+
resources :users do
|
129
|
+
attribute :name
|
130
|
+
attribute :email
|
131
|
+
|
132
|
+
index :name
|
133
|
+
|
134
|
+
bulk :gmail do
|
135
|
+
# There is `all` method can be used here to get all user elements.
|
136
|
+
# GET /users/gmail
|
137
|
+
show do |params|
|
138
|
+
query("name", params["name"]).select |e|
|
139
|
+
e.email.include?("@gmail.com")
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
```
|
146
|
+
|
data/lib/squad.rb
ADDED
@@ -0,0 +1,350 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'rack'
|
3
|
+
require 'nest'
|
4
|
+
require 'seg'
|
5
|
+
require 'ohm_util'
|
6
|
+
|
7
|
+
class Squad
|
8
|
+
DEFAULT_HEADER = {"Content-Type" => 'application/json;charset=UTF-8'}
|
9
|
+
|
10
|
+
def initialize(&block)
|
11
|
+
instance_eval(&block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.redis; @redis = settings[:redis] || Redic.new end
|
15
|
+
def self.settings; @settings ||= {} end
|
16
|
+
|
17
|
+
def self.application(&block)
|
18
|
+
@app = new(&block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.call(env)
|
22
|
+
@app.call(env)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.routes; @routes ||= {} end
|
26
|
+
|
27
|
+
def resources(name, &block)
|
28
|
+
self.class.routes[name] = Resource.factor(&block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def call(env); dup.call!(env) end
|
32
|
+
|
33
|
+
def call!(env)
|
34
|
+
request = Rack::Request.new(env)
|
35
|
+
seg = Seg.new(request.path_info)
|
36
|
+
|
37
|
+
inbox = {}
|
38
|
+
seg.capture(:segment, inbox)
|
39
|
+
segment = inbox[:segment].to_sym
|
40
|
+
raise BadRequestError unless klass = self.class.routes[segment]
|
41
|
+
|
42
|
+
resource = klass.new(segment)
|
43
|
+
resource.run(seg)
|
44
|
+
resource.render(request)
|
45
|
+
[resource.status, resource.header, [resource.to_json]]
|
46
|
+
rescue Error => e
|
47
|
+
[e.status, e.header, [JSON.dump(e.body)]]
|
48
|
+
rescue Exception => e
|
49
|
+
puts "message: #{e.message}, b: #{e.backtrace}"
|
50
|
+
error = Error.new
|
51
|
+
[error.status, error.header, [JSON.dump(error.body)]]
|
52
|
+
end
|
53
|
+
|
54
|
+
class Error < StandardError
|
55
|
+
def status; 500 end
|
56
|
+
def header; DEFAULT_HEADER end
|
57
|
+
def body; {message: 'error'} end
|
58
|
+
end
|
59
|
+
class NotImplementedError < Error
|
60
|
+
def status; 501 end
|
61
|
+
end
|
62
|
+
class BadRequestError < Error
|
63
|
+
def status; 400 end
|
64
|
+
end
|
65
|
+
class NotFoundError < Error
|
66
|
+
def status; 404 end
|
67
|
+
end
|
68
|
+
|
69
|
+
class Resource
|
70
|
+
attr :header
|
71
|
+
attr :id
|
72
|
+
|
73
|
+
def status; @status || ok end
|
74
|
+
|
75
|
+
# status code sytax suger
|
76
|
+
def ok; @status = 200 end
|
77
|
+
def created; @status = 201 end
|
78
|
+
def no_cotent; @status = 204 end
|
79
|
+
def bad_request; @status = 400 end
|
80
|
+
def not_found; @status = 404 end
|
81
|
+
def internal_server_error; @status = 500 end
|
82
|
+
def not_implemented; @status = 501 end
|
83
|
+
def bad_gateway; @status = 502 end
|
84
|
+
|
85
|
+
def run(seg)
|
86
|
+
inbox = {}
|
87
|
+
default_actions
|
88
|
+
|
89
|
+
while seg.capture(:segment, inbox)
|
90
|
+
segment = inbox[:segment].to_sym
|
91
|
+
@request_methods = {}
|
92
|
+
|
93
|
+
if new? && !defined?(@bulk_name)
|
94
|
+
if bulk = self.class.bulks[segment]
|
95
|
+
@bulk_name = segment
|
96
|
+
return instance_eval(&bulk)
|
97
|
+
else
|
98
|
+
@id = segment
|
99
|
+
load!
|
100
|
+
default_element_action
|
101
|
+
next
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
if !defined?(@element_name) && !defined?(@collection_name)
|
106
|
+
if element = self.class.elements[segment]
|
107
|
+
@element_name = segment
|
108
|
+
return instance_eval(&element)
|
109
|
+
elsif collection = self.class.collections[segment]
|
110
|
+
@collection_name = segment
|
111
|
+
return instance_eval(&collection)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
raise BadRequestError
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def render(request)
|
120
|
+
raise NotImplementedError unless method_block = @request_methods[request.request_method]
|
121
|
+
load! unless id.nil?
|
122
|
+
execute(request.params, &method_block)
|
123
|
+
end
|
124
|
+
|
125
|
+
def execute(params, &block)
|
126
|
+
@results = yield params
|
127
|
+
end
|
128
|
+
|
129
|
+
def element_type?; !defined?(@results) || !@results.kind_of?(Collection) end
|
130
|
+
|
131
|
+
def self.factor(&block)
|
132
|
+
klass = dup
|
133
|
+
klass.class_eval(&block)
|
134
|
+
klass
|
135
|
+
end
|
136
|
+
|
137
|
+
def initialize(name, attributes = {})
|
138
|
+
@attributes = Hash[self.class.attributes.map{|key| [key, nil]}]
|
139
|
+
@resource_name = name
|
140
|
+
update_attributes(attributes)
|
141
|
+
@status = nil
|
142
|
+
@header = DEFAULT_HEADER
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.attribute(name)
|
146
|
+
attributes << name unless attributes.include? name
|
147
|
+
define_method(name) do
|
148
|
+
@attributes[name]
|
149
|
+
end
|
150
|
+
define_method(:"#{name}=") do |value|
|
151
|
+
@attributes[name] = value
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.attributes; @attributes ||= [] end
|
156
|
+
|
157
|
+
def self.bulks; @bulks ||= {} end
|
158
|
+
|
159
|
+
def self.bulk(name, &block)
|
160
|
+
bulks[name] = block
|
161
|
+
end
|
162
|
+
|
163
|
+
def self.elements; @elements ||= {} end
|
164
|
+
|
165
|
+
def self.element(name, &block)
|
166
|
+
elements[name] = block
|
167
|
+
end
|
168
|
+
|
169
|
+
def self.indices; @indices ||= [] end
|
170
|
+
def self.index(attribute)
|
171
|
+
indices << attribute unless indices.include?(attribute)
|
172
|
+
end
|
173
|
+
|
174
|
+
def self.collections; @collections ||= {} end
|
175
|
+
def self.collection(name)
|
176
|
+
collections[name] = Proc.new do
|
177
|
+
show do |params|
|
178
|
+
resource = Squad.routes[name].new(name)
|
179
|
+
resource.query("#{@resource_name}_id", @id)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def self.reference(name)
|
185
|
+
field = :"#{name}_id"
|
186
|
+
attribute field
|
187
|
+
index field
|
188
|
+
end
|
189
|
+
|
190
|
+
def resource; self.class end
|
191
|
+
|
192
|
+
def find(id)
|
193
|
+
resource.new(@resource_name, {"id" => id}).load!
|
194
|
+
end
|
195
|
+
|
196
|
+
def all; Collection.new(self, key[:all].call("SMEMBERS")) end
|
197
|
+
|
198
|
+
def query(attribute, value)
|
199
|
+
ids = key[:indices][attribute][value].call("SMEMBERS")
|
200
|
+
Collection.new(self, ids)
|
201
|
+
end
|
202
|
+
|
203
|
+
def load!
|
204
|
+
result = key[id].call("HGETALL")
|
205
|
+
raise NotFoundError if result.size == 0
|
206
|
+
update_attributes(Hash[*result])
|
207
|
+
return self
|
208
|
+
end
|
209
|
+
|
210
|
+
def update_attributes(atts)
|
211
|
+
@attributes.each do |attribute, value|
|
212
|
+
send(:"#{attribute}=", atts[attribute.to_s]) if atts.has_key?(attribute.to_s)
|
213
|
+
end
|
214
|
+
@id = atts["id"] if atts["id"]
|
215
|
+
end
|
216
|
+
|
217
|
+
def save
|
218
|
+
feature = {name: @resource_name}
|
219
|
+
feature["id"] = @id unless new?
|
220
|
+
|
221
|
+
indices = {}
|
222
|
+
resource.indices.each do |field|
|
223
|
+
next unless (value = send(field))
|
224
|
+
indices[field] = Array(value).map(&:to_s)
|
225
|
+
end
|
226
|
+
|
227
|
+
@id = OhmUtil.script(redis,
|
228
|
+
OhmUtil::LUA_SAVE,
|
229
|
+
0,
|
230
|
+
feature.to_json,
|
231
|
+
serialize_attributes.to_json,
|
232
|
+
indices.to_json,
|
233
|
+
{}.to_json
|
234
|
+
)
|
235
|
+
end
|
236
|
+
|
237
|
+
def delete
|
238
|
+
OhmUtil.script(redis,
|
239
|
+
OhmUtil::LUA_DELETE, 0, {
|
240
|
+
"name" => @resource_name,
|
241
|
+
"id" => id,
|
242
|
+
"key" => key[id].to_s
|
243
|
+
}.to_json,
|
244
|
+
{}.to_json,
|
245
|
+
{}.to_json
|
246
|
+
)
|
247
|
+
|
248
|
+
@attributes.each do |key, value|
|
249
|
+
attributes[key] = nil
|
250
|
+
end
|
251
|
+
@id = nil
|
252
|
+
end
|
253
|
+
|
254
|
+
def attributes; @attributes end
|
255
|
+
|
256
|
+
def reproduce(attributes)
|
257
|
+
self.class.new(@resource_name, OhmUtil.dict(attributes))
|
258
|
+
end
|
259
|
+
|
260
|
+
def to_hash
|
261
|
+
hash = attributes
|
262
|
+
hash[:id] = @id unless @id.nil?
|
263
|
+
|
264
|
+
return hash
|
265
|
+
end
|
266
|
+
|
267
|
+
def to_json
|
268
|
+
if element_type?
|
269
|
+
JSON.dump(self.to_hash)
|
270
|
+
else
|
271
|
+
@results.to_json
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def key; @key ||= Nest.new(@resource_name, redis) end
|
276
|
+
def redis; Squad.redis end
|
277
|
+
|
278
|
+
private
|
279
|
+
attr_writer :id
|
280
|
+
|
281
|
+
def new?; !defined?(@id) end
|
282
|
+
def serialize_attributes
|
283
|
+
result = []
|
284
|
+
|
285
|
+
attributes.each do |key, value|
|
286
|
+
result.push(key, value.to_s) if value
|
287
|
+
end
|
288
|
+
|
289
|
+
result
|
290
|
+
end
|
291
|
+
|
292
|
+
def show(&block); @request_methods['GET'] = block end
|
293
|
+
def create(&block); @request_methods['POST'] = block end
|
294
|
+
def update(&block); @request_methods['PUT'] = block end
|
295
|
+
def destory(&block); @request_methods['DELETE'] = block end
|
296
|
+
|
297
|
+
def default_actions
|
298
|
+
@request_methods = {}
|
299
|
+
|
300
|
+
show do |params|
|
301
|
+
if params.size == 0
|
302
|
+
all
|
303
|
+
else
|
304
|
+
query(*params.first)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
create do |params|
|
309
|
+
update_attributes(params)
|
310
|
+
save
|
311
|
+
created
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def default_element_action
|
316
|
+
show { |params| }
|
317
|
+
|
318
|
+
update do |params|
|
319
|
+
update_attributes(params)
|
320
|
+
save
|
321
|
+
end
|
322
|
+
|
323
|
+
destory { |params| delete }
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
class Collection
|
328
|
+
include Enumerable
|
329
|
+
|
330
|
+
def initialize(resource, ids)
|
331
|
+
@resource = resource
|
332
|
+
@ids = ids
|
333
|
+
end
|
334
|
+
|
335
|
+
def each
|
336
|
+
@ids.each { |id| @resource.redis.queue("HGETALL", @resource.key[id])}
|
337
|
+
|
338
|
+
data = @resource.redis.commit
|
339
|
+
return if data.nil?
|
340
|
+
|
341
|
+
data.each_with_index do |atts, idx|
|
342
|
+
yield @resource.reproduce(atts + ['id', @ids[idx]])
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
def to_json
|
347
|
+
JSON.dump(self.map { |e| e.to_hash })
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
data/makefile
ADDED
data/squad.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "squad"
|
3
|
+
s.version = "0.1.2"
|
4
|
+
s.summary = %{Simple, efficient RESTful framework in Ruby with Redis}
|
5
|
+
s.description = %Q{Squad uses Redis to store resources inspired by Ohm, and provides a simple DSL to easily develop APIs.}
|
6
|
+
s.authors = ["Travis Liu"]
|
7
|
+
s.email = ["travisliu.tw@gmail.com"]
|
8
|
+
s.homepage = "https://github.com/travisliu/squad"
|
9
|
+
s.license = "MIT"
|
10
|
+
|
11
|
+
s.files = `git ls-files`.split("\n")
|
12
|
+
|
13
|
+
s.rubyforge_project = "squad"
|
14
|
+
|
15
|
+
s.add_dependency "rack", "~> 2.0"
|
16
|
+
s.add_dependency "redic", "~> 1.5"
|
17
|
+
s.add_dependency "nest", "~> 3"
|
18
|
+
s.add_dependency "stal"
|
19
|
+
s.add_dependency "seg", "~> 1.2"
|
20
|
+
s.add_dependency "ohm_util", "~> 0.1"
|
21
|
+
|
22
|
+
s.add_development_dependency "cutest"
|
23
|
+
s.add_development_dependency "rack-test"
|
24
|
+
end
|
data/test/resource.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
require "cutest"
|
2
|
+
require_relative "../lib/squad"
|
3
|
+
|
4
|
+
def mock_request(app, url, method, payload = nil)
|
5
|
+
env = Rack::MockRequest.env_for( url,
|
6
|
+
"REQUEST_METHOD" => method,
|
7
|
+
:input => payload
|
8
|
+
)
|
9
|
+
|
10
|
+
app.call(env)
|
11
|
+
end
|
12
|
+
|
13
|
+
prepare do
|
14
|
+
Squad.settings[:redis] = Redic.new 'redis://database:6379/2'
|
15
|
+
end
|
16
|
+
|
17
|
+
setup do
|
18
|
+
Squad.settings[:redis].call "FLUSHDB"
|
19
|
+
end
|
20
|
+
|
21
|
+
test "basic create, read, update and destory functionality" do |params|
|
22
|
+
app = Squad.application do
|
23
|
+
resources :users do
|
24
|
+
attribute :name
|
25
|
+
attribute :email
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
_, _, response = mock_request(app, "/users", "POST", "name=kolo&email=kolo@gmail.com")
|
30
|
+
user = JSON.parse(response.first)
|
31
|
+
|
32
|
+
_, _, response = mock_request(app, "/users/#{user["id"]}", "GET")
|
33
|
+
result = JSON.parse(response.first)
|
34
|
+
assert result["name"] == "kolo"
|
35
|
+
|
36
|
+
mock_request(app, "/users/#{user["id"]}?name=kolo2&email=kolo@gmail.com", "PUT")
|
37
|
+
_, _, response = mock_request(app, "/users/#{user["id"]}", "GET")
|
38
|
+
result = JSON.parse(response.first)
|
39
|
+
assert result["name"] == "kolo2"
|
40
|
+
|
41
|
+
mock_request(app, "/users/#{user["id"]}", "DELETE")
|
42
|
+
status_code, _, response = mock_request(app, "/users/#{user["id"]}", "GET")
|
43
|
+
assert status_code == 404
|
44
|
+
end
|
45
|
+
|
46
|
+
test "can be queried with index" do |params|
|
47
|
+
app = Squad.application do
|
48
|
+
resources :users do
|
49
|
+
attribute :name
|
50
|
+
attribute :email
|
51
|
+
|
52
|
+
index :name
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
mock_request(app, "/users", "POST", "name=kolo&email=kolo@gmail.com")
|
57
|
+
mock_request(app, "/users", "POST", "name=kolo&email=kolo2@gmail.com")
|
58
|
+
mock_request(app, "/users", "POST", "name=scott&email=scott@gmail.com")
|
59
|
+
|
60
|
+
_, _, response = mock_request(app, "/users?name=kolo", "GET")
|
61
|
+
users = JSON.parse(response.first)
|
62
|
+
|
63
|
+
assert users.size == 2
|
64
|
+
end
|
65
|
+
|
66
|
+
test "find collection" do
|
67
|
+
app = Squad.application do
|
68
|
+
resources :users do
|
69
|
+
attribute :name
|
70
|
+
attribute :email
|
71
|
+
|
72
|
+
collection :posts
|
73
|
+
end
|
74
|
+
|
75
|
+
resources :posts do
|
76
|
+
attribute :title
|
77
|
+
attribute :content
|
78
|
+
|
79
|
+
reference :users
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
_, _, response = mock_request(app, "/users", "POST", "name=kolo&email=kolo@gmail.com")
|
84
|
+
users = JSON.parse(response.first)
|
85
|
+
|
86
|
+
mock_request(app, "/posts", "POST", "users_id=#{users["id"]}&title=title1&content=content1")
|
87
|
+
mock_request(app, "/posts", "POST", "users_id=#{users["id"]}&title=title2&content=content2")
|
88
|
+
_, _, response = mock_request(app, "/users/#{users["id"]}/posts", "GET")
|
89
|
+
|
90
|
+
posts = JSON.parse(response.first)
|
91
|
+
assert posts.size == 2
|
92
|
+
end
|
93
|
+
|
94
|
+
test "custom action" do
|
95
|
+
app = Squad.application do
|
96
|
+
resources :users do
|
97
|
+
attribute :name
|
98
|
+
attribute :email
|
99
|
+
|
100
|
+
element :showcase do
|
101
|
+
# GET /users/:id/showcase
|
102
|
+
show do |params|
|
103
|
+
self.email[1..3] = 'xxx'
|
104
|
+
end
|
105
|
+
|
106
|
+
# PUT /users/:id/showcase
|
107
|
+
update do |params|
|
108
|
+
self.email = params["email"]
|
109
|
+
save
|
110
|
+
end
|
111
|
+
|
112
|
+
# DELETE /users/:id/showcase
|
113
|
+
destory do |params|
|
114
|
+
delete if self.email == params["email"]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
bulk :signup do
|
119
|
+
# POST /users/signup
|
120
|
+
create do |params|
|
121
|
+
if params["email"].include?("@gmail.com")
|
122
|
+
update_attributes(params)
|
123
|
+
save
|
124
|
+
created
|
125
|
+
else
|
126
|
+
bad_request
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
_, _, response = mock_request(app, "/users", "POST", "name=kolo&email=kolo@gmail.com")
|
134
|
+
user = JSON.parse(response.first)
|
135
|
+
|
136
|
+
_, _, response = mock_request(app, "/users/#{user["id"]}/showcase", "GET")
|
137
|
+
showcase_user = JSON.parse(response.first)
|
138
|
+
assert showcase_user["email"] = "kxxx@gmail.com"
|
139
|
+
|
140
|
+
mock_request(app, "/users/#{user["id"]}/showcase?email=newkolo@gmail.com", "PUT")
|
141
|
+
_, _, response = mock_request(app, "/users/#{user["id"]}", "GET")
|
142
|
+
showcase_user = JSON.parse(response.first)
|
143
|
+
assert showcase_user["email"] = "newkolo@gmail.com"
|
144
|
+
|
145
|
+
_, _, response = mock_request(app, "/users/#{user["id"]}/showcase?email=newkolo@gmail.com", "DELETE")
|
146
|
+
code, _, response = mock_request(app, "/users/#{user["id"]}", "GET")
|
147
|
+
assert code == 404
|
148
|
+
end
|
metadata
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: squad
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Travis Liu
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-03-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rack
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: redic
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.5'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: nest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: stal
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
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: seg
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.2'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.2'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: ohm_util
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.1'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.1'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: cutest
|
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
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rack-test
|
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
|
+
description: Squad uses Redis to store resources inspired by Ohm, and provides a simple
|
126
|
+
DSL to easily develop APIs.
|
127
|
+
email:
|
128
|
+
- travisliu.tw@gmail.com
|
129
|
+
executables: []
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- ".gitignore"
|
134
|
+
- LICENSE
|
135
|
+
- README.md
|
136
|
+
- lib/squad.rb
|
137
|
+
- makefile
|
138
|
+
- squad.gemspec
|
139
|
+
- test/resource.rb
|
140
|
+
homepage: https://github.com/travisliu/squad
|
141
|
+
licenses:
|
142
|
+
- MIT
|
143
|
+
metadata: {}
|
144
|
+
post_install_message:
|
145
|
+
rdoc_options: []
|
146
|
+
require_paths:
|
147
|
+
- lib
|
148
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
154
|
+
requirements:
|
155
|
+
- - ">="
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
requirements: []
|
159
|
+
rubyforge_project: squad
|
160
|
+
rubygems_version: 2.6.8
|
161
|
+
signing_key:
|
162
|
+
specification_version: 4
|
163
|
+
summary: Simple, efficient RESTful framework in Ruby with Redis
|
164
|
+
test_files: []
|