squad 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (9) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +0 -0
  3. data/LICENSE +19 -0
  4. data/README.md +146 -0
  5. data/lib/squad.rb +350 -0
  6. data/makefile +6 -0
  7. data/squad.gemspec +24 -0
  8. data/test/resource.rb +148 -0
  9. metadata +164 -0
@@ -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
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.
@@ -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
+
@@ -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
@@ -0,0 +1,6 @@
1
+ .PHONY: all test examples
2
+
3
+ all: test
4
+
5
+ test:
6
+ cutest ./test/*.rb
@@ -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
@@ -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: []