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.
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: []