rest-core 1.0.3 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +6 -7
- data/CHANGES.md +137 -0
- data/Gemfile +1 -1
- data/README.md +183 -191
- data/TODO.md +5 -8
- data/example/multi.rb +31 -24
- data/example/simple.rb +28 -0
- data/example/use-cases.rb +194 -0
- data/lib/rest-core.rb +26 -19
- data/lib/rest-core/builder.rb +2 -2
- data/lib/rest-core/client.rb +40 -27
- data/lib/rest-core/client/universal.rb +16 -13
- data/lib/rest-core/client_oauth1.rb +5 -5
- data/lib/rest-core/engine/auto.rb +25 -0
- data/lib/rest-core/{app → engine}/dry.rb +1 -2
- data/lib/rest-core/engine/em-http-request.rb +39 -0
- data/lib/rest-core/engine/future/future.rb +106 -0
- data/lib/rest-core/engine/future/future_fiber.rb +39 -0
- data/lib/rest-core/engine/future/future_thread.rb +29 -0
- data/lib/rest-core/engine/rest-client.rb +56 -0
- data/lib/rest-core/middleware.rb +27 -5
- data/lib/rest-core/middleware/auth_basic.rb +5 -5
- data/lib/rest-core/middleware/bypass.rb +2 -2
- data/lib/rest-core/middleware/cache.rb +67 -54
- data/lib/rest-core/middleware/common_logger.rb +5 -8
- data/lib/rest-core/middleware/default_headers.rb +2 -2
- data/lib/rest-core/middleware/default_payload.rb +26 -2
- data/lib/rest-core/middleware/default_query.rb +4 -2
- data/lib/rest-core/middleware/default_site.rb +8 -6
- data/lib/rest-core/middleware/error_detector.rb +9 -16
- data/lib/rest-core/middleware/error_handler.rb +25 -11
- data/lib/rest-core/middleware/follow_redirect.rb +11 -14
- data/lib/rest-core/middleware/json_request.rb +19 -0
- data/lib/rest-core/middleware/json_response.rb +28 -0
- data/lib/rest-core/middleware/oauth1_header.rb +2 -7
- data/lib/rest-core/middleware/oauth2_header.rb +4 -7
- data/lib/rest-core/middleware/oauth2_query.rb +2 -2
- data/lib/rest-core/middleware/timeout.rb +21 -65
- data/lib/rest-core/middleware/timeout/{eventmachine_timer.rb → timer_em.rb} +3 -1
- data/lib/rest-core/middleware/timeout/timer_thread.rb +36 -0
- data/lib/rest-core/patch/multi_json.rb +8 -0
- data/lib/rest-core/test.rb +3 -12
- data/lib/rest-core/util/json.rb +65 -0
- data/lib/rest-core/util/parse_query.rb +2 -2
- data/lib/rest-core/version.rb +1 -1
- data/lib/rest-core/wrapper.rb +16 -16
- data/rest-core.gemspec +28 -27
- data/test/test_auth_basic.rb +14 -10
- data/test/test_builder.rb +7 -7
- data/test/test_cache.rb +126 -37
- data/test/test_client.rb +3 -1
- data/test/test_client_oauth1.rb +2 -3
- data/test/test_default_query.rb +17 -23
- data/test/test_em_http_request.rb +146 -0
- data/test/test_error_detector.rb +0 -1
- data/test/test_error_handler.rb +44 -0
- data/test/test_follow_redirect.rb +17 -19
- data/test/test_json_request.rb +28 -0
- data/test/test_json_response.rb +51 -0
- data/test/test_oauth1_header.rb +4 -4
- data/test/test_payload.rb +20 -12
- data/test/test_simple.rb +14 -0
- data/test/test_timeout.rb +11 -19
- data/test/test_universal.rb +5 -5
- data/test/test_wrapper.rb +19 -13
- metadata +28 -29
- data/doc/ToC.md +0 -7
- data/doc/dependency.md +0 -4
- data/doc/design.md +0 -4
- data/example/auto.rb +0 -51
- data/example/coolio.rb +0 -21
- data/example/eventmachine.rb +0 -30
- data/example/rest-client.rb +0 -16
- data/lib/rest-core/app/abstract/async_fiber.rb +0 -13
- data/lib/rest-core/app/auto.rb +0 -23
- data/lib/rest-core/app/coolio-async.rb +0 -32
- data/lib/rest-core/app/coolio-fiber.rb +0 -30
- data/lib/rest-core/app/coolio.rb +0 -9
- data/lib/rest-core/app/em-http-request-async.rb +0 -37
- data/lib/rest-core/app/em-http-request-fiber.rb +0 -45
- data/lib/rest-core/app/em-http-request.rb +0 -9
- data/lib/rest-core/app/rest-client.rb +0 -41
- data/lib/rest-core/middleware/json_decode.rb +0 -93
- data/lib/rest-core/middleware/timeout/coolio_timer.rb +0 -10
- data/pending/test_multi.rb +0 -123
- data/pending/test_test_util.rb +0 -86
- data/test/test_json_decode.rb +0 -24
data/doc/ToC.md
DELETED
data/doc/dependency.md
DELETED
data/doc/design.md
DELETED
data/example/auto.rb
DELETED
@@ -1,51 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'rest-core'
|
3
|
-
require 'eventmachine'
|
4
|
-
require 'cool.io'
|
5
|
-
|
6
|
-
YourClient = RestCore::Builder.client do
|
7
|
-
s = RestCore
|
8
|
-
use s::DefaultSite , 'https://api.github.com/users/'
|
9
|
-
use s::JsonDecode , true
|
10
|
-
use s::CommonLogger, method(:puts)
|
11
|
-
use s::Cache , nil, 3600
|
12
|
-
run s::Auto
|
13
|
-
end
|
14
|
-
|
15
|
-
client = YourClient.new(:cache => {})
|
16
|
-
p client.get('cardinalblue') # cache miss
|
17
|
-
puts
|
18
|
-
p client.get('cardinalblue') # cache hit
|
19
|
-
|
20
|
-
puts
|
21
|
-
|
22
|
-
client = YourClient.new(:cache => {})
|
23
|
-
EM.run{
|
24
|
-
client.get('cardinalblue'){ |response|
|
25
|
-
p response
|
26
|
-
EM.stop
|
27
|
-
}
|
28
|
-
}
|
29
|
-
|
30
|
-
puts
|
31
|
-
|
32
|
-
EM.run{
|
33
|
-
Fiber.new{
|
34
|
-
p client.get('cardinalblue')
|
35
|
-
EM.stop
|
36
|
-
}.resume
|
37
|
-
}
|
38
|
-
|
39
|
-
puts
|
40
|
-
|
41
|
-
client = YourClient.new(:cache => {})
|
42
|
-
Coolio::TimerWatcher.new(1).attach(Coolio::Loop.default).on_timer{detach}
|
43
|
-
client.get('cardinalblue'){ |response|
|
44
|
-
p response
|
45
|
-
}
|
46
|
-
Coolio::Loop.default.run
|
47
|
-
|
48
|
-
puts
|
49
|
-
Coolio::TimerWatcher.new(1).attach(Coolio::Loop.default).on_timer{detach}
|
50
|
-
Fiber.new{ p client.get('cardinalblue') }.resume
|
51
|
-
Coolio::Loop.default.run
|
data/example/coolio.rb
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'rest-core'
|
3
|
-
|
4
|
-
AsynchronousClient = RestCore::Builder.client do
|
5
|
-
s = RestCore
|
6
|
-
use s::DefaultSite , 'https://api.github.com/users/'
|
7
|
-
use s::JsonDecode , true
|
8
|
-
use s::CommonLogger, method(:puts)
|
9
|
-
use s::Cache , nil, 3600
|
10
|
-
run s::Coolio
|
11
|
-
end
|
12
|
-
|
13
|
-
AsynchronousClient.new.get('cardinalblue'){ |response|
|
14
|
-
p response
|
15
|
-
}
|
16
|
-
Coolio::Loop.default.run
|
17
|
-
|
18
|
-
puts
|
19
|
-
|
20
|
-
Fiber.new{ p AsynchronousClient.new.get('cardinalblue') }.resume
|
21
|
-
Coolio::Loop.default.run
|
data/example/eventmachine.rb
DELETED
@@ -1,30 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'rest-core'
|
3
|
-
|
4
|
-
AsynchronousClient = RestCore::Builder.client do
|
5
|
-
s = RestCore
|
6
|
-
use s::DefaultSite , 'https://api.github.com/users/'
|
7
|
-
use s::JsonDecode , true
|
8
|
-
use s::CommonLogger, method(:puts)
|
9
|
-
use s::Cache , nil, 3600
|
10
|
-
run s::EmHttpRequest
|
11
|
-
end
|
12
|
-
|
13
|
-
client = AsynchronousClient.new
|
14
|
-
EM.run{
|
15
|
-
client.get('cardinalblue'){ |response|
|
16
|
-
p response
|
17
|
-
EM.stop
|
18
|
-
}
|
19
|
-
puts "It's not blocking..."
|
20
|
-
}
|
21
|
-
|
22
|
-
puts
|
23
|
-
|
24
|
-
EM.run{
|
25
|
-
Fiber.new{
|
26
|
-
p client.get('cardinalblue')
|
27
|
-
EM.stop
|
28
|
-
}.resume
|
29
|
-
puts "It's not blocking..."
|
30
|
-
}
|
data/example/rest-client.rb
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'rest-core'
|
3
|
-
|
4
|
-
YourClient = RestCore::Builder.client do
|
5
|
-
s = RestCore
|
6
|
-
use s::DefaultSite , 'https://api.github.com/users/'
|
7
|
-
use s::JsonDecode , true
|
8
|
-
use s::CommonLogger, method(:puts)
|
9
|
-
use s::Cache , nil, 3600
|
10
|
-
run s::RestClient
|
11
|
-
end
|
12
|
-
|
13
|
-
client = YourClient.new(:cache => {})
|
14
|
-
p client.get('cardinalblue') # cache miss
|
15
|
-
puts
|
16
|
-
p client.get('cardinalblue') # cache hit
|
data/lib/rest-core/app/auto.rb
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'rest-core/middleware'
|
3
|
-
|
4
|
-
class RestCore::Auto
|
5
|
-
include RestCore::Middleware
|
6
|
-
def call env
|
7
|
-
client = http_client
|
8
|
-
client.call(log(env, "Auto picked: #{client.class}"))
|
9
|
-
end
|
10
|
-
|
11
|
-
def http_client
|
12
|
-
if Object.const_defined?(:EventMachine) && ::EventMachine.reactor_running?
|
13
|
-
@emhttprequest ||= RestCore::EmHttpRequest.new
|
14
|
-
|
15
|
-
elsif Object.const_defined?(:Coolio) && ::Coolio::Loop.default.
|
16
|
-
has_active_watchers?
|
17
|
-
@coolio ||= RestCore::Coolio.new
|
18
|
-
|
19
|
-
else
|
20
|
-
@restclient ||= RestCore::RestClient.new
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'rest-core/middleware'
|
3
|
-
|
4
|
-
require 'cool.io-http'
|
5
|
-
|
6
|
-
class RestCore::CoolioAsync
|
7
|
-
include RestCore::Middleware
|
8
|
-
def call env
|
9
|
-
client = ::Coolio::Http.request(:method => env[REQUEST_METHOD] ,
|
10
|
-
:url => request_uri(env) ,
|
11
|
-
:payload => env[REQUEST_PAYLOAD],
|
12
|
-
:headers => env[REQUEST_HEADERS]){ |res|
|
13
|
-
|
14
|
-
env[TIMER].detach if env[TIMER]
|
15
|
-
env[ASYNC].call(env.merge(RESPONSE_BODY => res.body ,
|
16
|
-
RESPONSE_STATUS => res.status,
|
17
|
-
RESPONSE_HEADERS => res.headers)) if
|
18
|
-
env[ASYNC]
|
19
|
-
}
|
20
|
-
|
21
|
-
env[TIMER].on_timer{
|
22
|
-
detach
|
23
|
-
client.detach
|
24
|
-
env[ASYNC].call(env.merge(RESPONSE_BODY => env[TIMER].error,
|
25
|
-
RESPONSE_STATUS => 0 ,
|
26
|
-
RESPONSE_HEADERS => {} )) if
|
27
|
-
env[ASYNC]
|
28
|
-
} if env[TIMER]
|
29
|
-
|
30
|
-
env
|
31
|
-
end
|
32
|
-
end
|
@@ -1,30 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'rest-core/middleware'
|
3
|
-
|
4
|
-
require 'cool.io-http'
|
5
|
-
|
6
|
-
class RestCore::CoolioFiber
|
7
|
-
include RestCore::Middleware
|
8
|
-
def call env
|
9
|
-
process(env,
|
10
|
-
::Coolio::HttpFiber.request(:method => env[REQUEST_METHOD] ,
|
11
|
-
:url => request_uri(env) ,
|
12
|
-
:payload => env[REQUEST_PAYLOAD],
|
13
|
-
:headers => env[REQUEST_HEADERS]))
|
14
|
-
rescue FiberError
|
15
|
-
end
|
16
|
-
|
17
|
-
def process env, response
|
18
|
-
Thread.current[:coolio_http_client].detach if
|
19
|
-
Thread.current[:coolio_http_client].kind_of?(::Coolio::HttpFiber)
|
20
|
-
|
21
|
-
raise response if response.kind_of?(::Exception)
|
22
|
-
|
23
|
-
result = env.merge(RESPONSE_BODY => response.body ,
|
24
|
-
RESPONSE_STATUS => response.status,
|
25
|
-
RESPONSE_HEADERS => response.headers)
|
26
|
-
|
27
|
-
result[ASYNC].call(result) if result[ASYNC]
|
28
|
-
result
|
29
|
-
end
|
30
|
-
end
|
data/lib/rest-core/app/coolio.rb
DELETED
@@ -1,9 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'rest-core/app/abstract/async_fiber'
|
3
|
-
require 'rest-core/app/coolio-async'
|
4
|
-
require 'rest-core/app/coolio-fiber'
|
5
|
-
|
6
|
-
class RestCore::Coolio < RestCore::AsyncFiber
|
7
|
-
def async; @async ||= CoolioAsync.new; end
|
8
|
-
def fiber; @fiber ||= CoolioFiber.new; end
|
9
|
-
end
|
@@ -1,37 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'rest-core/middleware'
|
3
|
-
|
4
|
-
require 'restclient/payload'
|
5
|
-
require 'em-http-request'
|
6
|
-
|
7
|
-
class RestCore::EmHttpRequestAsync
|
8
|
-
include RestCore::Middleware
|
9
|
-
def call env
|
10
|
-
payload = ::RestClient::Payload.generate(env[REQUEST_PAYLOAD])
|
11
|
-
client = ::EventMachine::HttpRequest.new(request_uri(env)).send(
|
12
|
-
env[REQUEST_METHOD],
|
13
|
-
:body => payload.read,
|
14
|
-
:head => payload.headers.merge(env[REQUEST_HEADERS]))
|
15
|
-
|
16
|
-
client.callback{ respond(env, client) }
|
17
|
-
client. errback{ respond(env, client) }
|
18
|
-
|
19
|
-
env[TIMER].on_timeout{
|
20
|
-
client.close
|
21
|
-
env[ASYNC].call(env.merge(RESPONSE_BODY => env[TIMER].error,
|
22
|
-
RESPONSE_STATUS => 0 ,
|
23
|
-
RESPONSE_HEADERS => {} )) if
|
24
|
-
env[ASYNC]
|
25
|
-
} if env[TIMER]
|
26
|
-
|
27
|
-
env
|
28
|
-
end
|
29
|
-
|
30
|
-
def respond env, client
|
31
|
-
env[TIMER].cancel if env[TIMER] && !env[TIMER].canceled?
|
32
|
-
env[ASYNC].call(env.merge(
|
33
|
-
RESPONSE_BODY => client.response,
|
34
|
-
RESPONSE_STATUS => client.response_header.status,
|
35
|
-
RESPONSE_HEADERS => client.response_header)) if env[ASYNC]
|
36
|
-
end
|
37
|
-
end
|
@@ -1,45 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'rest-core/middleware'
|
3
|
-
|
4
|
-
require 'restclient/payload'
|
5
|
-
require 'em-http-request'
|
6
|
-
require 'fiber'
|
7
|
-
|
8
|
-
class RestCore::EmHttpRequestFiber
|
9
|
-
include RestCore::Middleware
|
10
|
-
def call env
|
11
|
-
f = Fiber.current
|
12
|
-
|
13
|
-
payload = ::RestClient::Payload.generate(env[REQUEST_PAYLOAD])
|
14
|
-
client = ::EventMachine::HttpRequest.new(request_uri(env)).send(
|
15
|
-
env[REQUEST_METHOD],
|
16
|
-
:body => payload.read,
|
17
|
-
:head => payload.headers.merge(env[REQUEST_HEADERS]))
|
18
|
-
|
19
|
-
client.callback{ respond(f, env, client) }
|
20
|
-
client. errback{ respond(f, env, client) }
|
21
|
-
|
22
|
-
if (response = Fiber.yield).kind_of?(::Exception)
|
23
|
-
client.close
|
24
|
-
raise response
|
25
|
-
else
|
26
|
-
response
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def respond f, env, client
|
31
|
-
f.resume(process(env, client)) if f.alive?
|
32
|
-
rescue FiberError
|
33
|
-
# whenever timeout, client.close would be called,
|
34
|
-
# and then errback would be called. in this case,
|
35
|
-
# the fiber is already resumed by the timer
|
36
|
-
end
|
37
|
-
|
38
|
-
def process env, client
|
39
|
-
result = env.merge(RESPONSE_BODY => client.response,
|
40
|
-
RESPONSE_STATUS => client.response_header.status,
|
41
|
-
RESPONSE_HEADERS => client.response_header)
|
42
|
-
result[ASYNC].call(result) if result[ASYNC]
|
43
|
-
result
|
44
|
-
end
|
45
|
-
end
|
@@ -1,9 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'rest-core/app/abstract/async_fiber'
|
3
|
-
require 'rest-core/app/em-http-request-async'
|
4
|
-
require 'rest-core/app/em-http-request-fiber'
|
5
|
-
|
6
|
-
class RestCore::EmHttpRequest < RestCore::AsyncFiber
|
7
|
-
def async; @async ||= EmHttpRequestAsync.new; end
|
8
|
-
def fiber; @fiber ||= EmHttpRequestFiber.new; end
|
9
|
-
end
|
@@ -1,41 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'rest-core/middleware'
|
3
|
-
|
4
|
-
require 'restclient'
|
5
|
-
|
6
|
-
require 'rest-core/patch/rest-client'
|
7
|
-
|
8
|
-
class RestCore::RestClient
|
9
|
-
include RestCore::Middleware
|
10
|
-
def call env
|
11
|
-
process(env,
|
12
|
-
::RestClient::Request.execute(:method => env[REQUEST_METHOD ],
|
13
|
-
:url => request_uri(env) ,
|
14
|
-
:payload => env[REQUEST_PAYLOAD],
|
15
|
-
:headers => env[REQUEST_HEADERS],
|
16
|
-
:max_redirects => 0))
|
17
|
-
|
18
|
-
rescue ::RestClient::Exception => e
|
19
|
-
process(env, e.response)
|
20
|
-
end
|
21
|
-
|
22
|
-
def process env, response
|
23
|
-
result = env.merge(RESPONSE_BODY => response.body,
|
24
|
-
RESPONSE_STATUS => response.code,
|
25
|
-
RESPONSE_HEADERS => normalize_headers(
|
26
|
-
response.raw_headers))
|
27
|
-
result[ASYNC].call(result) if result[ASYNC]
|
28
|
-
result
|
29
|
-
end
|
30
|
-
|
31
|
-
def normalize_headers raw_headers
|
32
|
-
raw_headers.inject({}){ |r, (k, v)|
|
33
|
-
r[k.to_s.upcase.tr('-', '_')] = if v.kind_of?(Array) && v.size == 1
|
34
|
-
v.first
|
35
|
-
else
|
36
|
-
v
|
37
|
-
end
|
38
|
-
r
|
39
|
-
}
|
40
|
-
end
|
41
|
-
end
|
@@ -1,93 +0,0 @@
|
|
1
|
-
|
2
|
-
require 'rest-core/middleware'
|
3
|
-
|
4
|
-
class RestCore::JsonDecode
|
5
|
-
def self.members; [:json_decode]; end
|
6
|
-
include RestCore::Middleware
|
7
|
-
|
8
|
-
def call env
|
9
|
-
return app.call(env) if env[DRY]
|
10
|
-
if env[ASYNC]
|
11
|
-
app.call(env.merge(ASYNC => lambda{ |response|
|
12
|
-
env[ASYNC].call(process(response))
|
13
|
-
}))
|
14
|
-
else
|
15
|
-
process(app.call(env))
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def process response
|
20
|
-
if json_decode(response)
|
21
|
-
response.merge(RESPONSE_BODY =>
|
22
|
-
self.class.json_decode("[#{response[RESPONSE_BODY]}]").first)
|
23
|
-
# [this].first is not needed for yajl-ruby
|
24
|
-
else
|
25
|
-
response
|
26
|
-
end
|
27
|
-
rescue self.class.const_get(:ParseError) => error
|
28
|
-
fail(response, error)
|
29
|
-
end
|
30
|
-
|
31
|
-
module YajlRuby
|
32
|
-
def self.extended mod
|
33
|
-
mod.const_set(:ParseError, Yajl::ParseError)
|
34
|
-
end
|
35
|
-
def json_encode hash
|
36
|
-
Yajl::Encoder.encode(hash)
|
37
|
-
end
|
38
|
-
def json_decode json
|
39
|
-
Yajl::Parser.parse(json)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
module Json
|
44
|
-
def self.extended mod
|
45
|
-
mod.const_set(:ParseError, JSON::ParserError)
|
46
|
-
end
|
47
|
-
def json_encode hash
|
48
|
-
JSON.dump(hash)
|
49
|
-
end
|
50
|
-
def json_decode json
|
51
|
-
JSON.parse(json)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
module Gsub
|
56
|
-
class ParseError < RuntimeError; end
|
57
|
-
def self.extended mod
|
58
|
-
mod.const_set(:ParseError, Gsub::ParseError)
|
59
|
-
end
|
60
|
-
# only works for flat hash
|
61
|
-
def json_encode hash
|
62
|
-
middle = hash.inject([]){ |r, (k, v)|
|
63
|
-
r << "\"#{k}\":\"#{v.gsub('"','\\"')}\""
|
64
|
-
}.join(',')
|
65
|
-
"{#{middle}}"
|
66
|
-
end
|
67
|
-
def json_decode json
|
68
|
-
raise NotImplementedError.new(
|
69
|
-
'You need to install either yajl-ruby, json, or json_pure gem')
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def self.select_json! mod, picked=false
|
74
|
-
if Object.const_defined?(:Yajl)
|
75
|
-
mod.send(:extend, YajlRuby)
|
76
|
-
elsif Object.const_defined?(:JSON)
|
77
|
-
mod.send(:extend, Json)
|
78
|
-
elsif picked
|
79
|
-
mod.send(:extend, Gsub)
|
80
|
-
else
|
81
|
-
# pick a json gem if available
|
82
|
-
%w[yajl json].each{ |json|
|
83
|
-
begin
|
84
|
-
require json
|
85
|
-
break
|
86
|
-
rescue LoadError
|
87
|
-
end
|
88
|
-
}
|
89
|
-
select_json!(mod, true)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
select_json!(self)
|
93
|
-
end
|