typhoid 0.0.1
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/.gitignore +19 -0
- data/.rvmrc +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +34 -0
- data/LICENSE +22 -0
- data/README.md +3 -0
- data/Rakefile +13 -0
- data/lib/typhoid/attributes.rb +76 -0
- data/lib/typhoid/builder.rb +58 -0
- data/lib/typhoid/multi.rb +21 -0
- data/lib/typhoid/parser.rb +52 -0
- data/lib/typhoid/queued_request.rb +32 -0
- data/lib/typhoid/request_builder.rb +29 -0
- data/lib/typhoid/request_queue.rb +33 -0
- data/lib/typhoid/resource.rb +116 -0
- data/lib/typhoid/uri.rb +37 -0
- data/lib/typhoid/version.rb +3 -0
- data/lib/typhoid.rb +10 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/controller.rb +5 -0
- data/spec/support/game.rb +15 -0
- data/spec/support/player_stat.rb +10 -0
- data/spec/typhoid/builder_spec.rb +83 -0
- data/spec/typhoid/multi_spec.rb +33 -0
- data/spec/typhoid/parser_spec.rb +59 -0
- data/spec/typhoid/request_builder_spec.rb +15 -0
- data/spec/typhoid/resource_spec.rb +143 -0
- data/spec/typhoid/typhoid_spec.rb +4 -0
- data/spec/typhoid/uri_spec.rb +27 -0
- data/typhoid.gemspec +22 -0
- metadata +140 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
typhoid (0.0.1)
|
5
|
+
typhoeus
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
diff-lcs (1.1.3)
|
11
|
+
ffi (1.0.11)
|
12
|
+
json_pure (1.7.3)
|
13
|
+
mime-types (1.18)
|
14
|
+
rake (0.9.2.2)
|
15
|
+
rspec (2.10.0)
|
16
|
+
rspec-core (~> 2.10.0)
|
17
|
+
rspec-expectations (~> 2.10.0)
|
18
|
+
rspec-mocks (~> 2.10.0)
|
19
|
+
rspec-core (2.10.1)
|
20
|
+
rspec-expectations (2.10.0)
|
21
|
+
diff-lcs (~> 1.1.3)
|
22
|
+
rspec-mocks (2.10.1)
|
23
|
+
typhoeus (0.4.2)
|
24
|
+
ffi (~> 1.0)
|
25
|
+
mime-types (~> 1.18)
|
26
|
+
|
27
|
+
PLATFORMS
|
28
|
+
ruby
|
29
|
+
|
30
|
+
DEPENDENCIES
|
31
|
+
json_pure (>= 1.4.1)
|
32
|
+
rake
|
33
|
+
rspec
|
34
|
+
typhoid!
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Doug Rohde
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
require "rspec/core/rake_task"
|
4
|
+
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
|
7
|
+
namespace :spec do
|
8
|
+
RSpec::Core::RakeTask.new(:docs) do |t|
|
9
|
+
t.rspec_opts = ["--format doc"]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
task :default => :spec
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Typhoid
|
2
|
+
module Attributes
|
3
|
+
def load_values(params = {})
|
4
|
+
@attributes = Hash[params.map { |key, value| [key.to_s, value] }]
|
5
|
+
end
|
6
|
+
|
7
|
+
def attributes
|
8
|
+
@attributes ||= {}
|
9
|
+
@attributes
|
10
|
+
end
|
11
|
+
|
12
|
+
def read_attribute(name)
|
13
|
+
attributes[name.to_s]
|
14
|
+
end
|
15
|
+
alias :[] :read_attribute
|
16
|
+
|
17
|
+
def after_build(response, exception = nil)
|
18
|
+
assign_request_error(exception) if !response.success? || !exception.nil?
|
19
|
+
end
|
20
|
+
|
21
|
+
def assign_request_error(exception = nil)
|
22
|
+
self.resource_exception = exception || StandardError.new("Could not retrieve data from remote service")
|
23
|
+
end
|
24
|
+
private :assign_request_error
|
25
|
+
|
26
|
+
def self.included(base)
|
27
|
+
base.extend(ClassMethods)
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
module ClassMethods
|
33
|
+
def field(*field_names)
|
34
|
+
raise ArgumentError, "Must specify at least one field" if field_names.length == 0
|
35
|
+
@auto_init_fields ||= []
|
36
|
+
field_names.each do |field_name|
|
37
|
+
define_accessor field_name
|
38
|
+
@auto_init_fields << field_name.to_sym
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def define_accessor(field_name)
|
43
|
+
define_method field_name do
|
44
|
+
attributes[field_name.to_s]
|
45
|
+
end
|
46
|
+
define_method "#{field_name}=" do |new_value|
|
47
|
+
attributes[field_name.to_s] = new_value
|
48
|
+
end
|
49
|
+
end
|
50
|
+
private :define_accessor
|
51
|
+
|
52
|
+
def auto_init_fields
|
53
|
+
@auto_init_fields || []
|
54
|
+
end
|
55
|
+
|
56
|
+
def builder
|
57
|
+
Builder
|
58
|
+
end
|
59
|
+
|
60
|
+
def parser
|
61
|
+
Parser
|
62
|
+
end
|
63
|
+
|
64
|
+
def build(klass, response)
|
65
|
+
builder.call(klass, response)
|
66
|
+
end
|
67
|
+
|
68
|
+
def load_values(object, response)
|
69
|
+
object.tap { |obj|
|
70
|
+
obj.load_values(parser.call response.body)
|
71
|
+
obj.after_build response
|
72
|
+
}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Typhoid
|
2
|
+
class Builder
|
3
|
+
attr_reader :klass
|
4
|
+
attr_reader :response
|
5
|
+
attr_reader :body
|
6
|
+
attr_reader :parsed_body
|
7
|
+
attr_reader :exception
|
8
|
+
|
9
|
+
def self.call(klass, response)
|
10
|
+
new(klass, response).build
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(klass, response)
|
14
|
+
@klass = klass
|
15
|
+
@response = response
|
16
|
+
@body = response.body
|
17
|
+
begin
|
18
|
+
@parsed_body = parser.call body
|
19
|
+
rescue StandardError => e
|
20
|
+
@parsed_body = {}
|
21
|
+
@exception = e
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def build
|
26
|
+
array? ? build_array : build_single
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_from_klass(attributes)
|
30
|
+
klass.new(attributes).tap { |item|
|
31
|
+
item.after_build(response, exception) if item.respond_to? :after_build
|
32
|
+
}
|
33
|
+
end
|
34
|
+
private :build_from_klass
|
35
|
+
|
36
|
+
def array?
|
37
|
+
parsed_body.is_a?(Array)
|
38
|
+
end
|
39
|
+
private :array?
|
40
|
+
|
41
|
+
def build_array
|
42
|
+
parsed_body.collect { |single|
|
43
|
+
build_from_klass(single)
|
44
|
+
}
|
45
|
+
end
|
46
|
+
private :build_array
|
47
|
+
|
48
|
+
def build_single
|
49
|
+
build_from_klass parsed_body
|
50
|
+
end
|
51
|
+
private :build_single
|
52
|
+
|
53
|
+
def parser
|
54
|
+
klass.parser
|
55
|
+
end
|
56
|
+
private :parser
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Typhoid
|
2
|
+
module Multi
|
3
|
+
def remote_resources(hydra = nil)
|
4
|
+
request_queue = RequestQueue.new(self, hydra)
|
5
|
+
yield request_queue if block_given?
|
6
|
+
|
7
|
+
request_queue.run
|
8
|
+
|
9
|
+
request_queue.requests.each do |req|
|
10
|
+
parse_queued_response req
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def parse_queued_response(req)
|
17
|
+
varname = "@" + req.name.to_s
|
18
|
+
req.target.instance_variable_set varname.to_sym, Typhoid::Resource.build(req.klass, req.response)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Typhoid
|
2
|
+
class Parser
|
3
|
+
attr_reader :json_string
|
4
|
+
|
5
|
+
def self.call(json_string)
|
6
|
+
new(json_string).parse
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(json_string)
|
10
|
+
@json_string = json_string
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse
|
14
|
+
parsed_body
|
15
|
+
end
|
16
|
+
|
17
|
+
def parsed_body
|
18
|
+
engine.call json_string
|
19
|
+
rescue
|
20
|
+
raise ReadError, json_string
|
21
|
+
end
|
22
|
+
private :parsed_body
|
23
|
+
|
24
|
+
def engine
|
25
|
+
JSON.method(:parse)
|
26
|
+
end
|
27
|
+
private :engine
|
28
|
+
end
|
29
|
+
|
30
|
+
class ReadError < StandardError
|
31
|
+
attr_reader :body
|
32
|
+
def initialize(body)
|
33
|
+
@body = body
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
"Could not parse JSON body: #{cleaned_body}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def cleaned_body
|
41
|
+
clean = body[0..10]
|
42
|
+
clean = clean + "..." if add_dots?
|
43
|
+
clean
|
44
|
+
end
|
45
|
+
private :cleaned_body
|
46
|
+
|
47
|
+
def add_dots?
|
48
|
+
body.length > 10
|
49
|
+
end
|
50
|
+
private :add_dots?
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Typhoid
|
2
|
+
class QueuedRequest
|
3
|
+
attr_accessor :name, :request, :target, :klass
|
4
|
+
attr_accessor :on_complete
|
5
|
+
|
6
|
+
def initialize(hydra, name, req, target)
|
7
|
+
self.name = name
|
8
|
+
self.request = Typhoeus::Request.new(req.request_uri, req.options)
|
9
|
+
self.klass = req.klass
|
10
|
+
self.target = target
|
11
|
+
hydra.queue(self.request)
|
12
|
+
end
|
13
|
+
|
14
|
+
def on_complete
|
15
|
+
self.request.on_complete do
|
16
|
+
yield self if block_given?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def status
|
21
|
+
self.request.handled_response.code
|
22
|
+
end
|
23
|
+
|
24
|
+
def response
|
25
|
+
self.request.handled_response
|
26
|
+
end
|
27
|
+
|
28
|
+
def klass
|
29
|
+
@klass
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Typhoid
|
2
|
+
class RequestBuilder
|
3
|
+
attr_accessor :klass
|
4
|
+
attr_writer :method
|
5
|
+
|
6
|
+
def initialize(klass, uri, options = {})
|
7
|
+
@uri = uri
|
8
|
+
@request_options = options
|
9
|
+
@klass = klass
|
10
|
+
end
|
11
|
+
|
12
|
+
def request_uri
|
13
|
+
@uri
|
14
|
+
end
|
15
|
+
|
16
|
+
def options
|
17
|
+
@request_options
|
18
|
+
end
|
19
|
+
|
20
|
+
def http_method
|
21
|
+
options[:method] || :get
|
22
|
+
end
|
23
|
+
|
24
|
+
def run
|
25
|
+
klass.run(self)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'typhoeus'
|
2
|
+
|
3
|
+
module Typhoid
|
4
|
+
class RequestQueue
|
5
|
+
attr_reader :queue
|
6
|
+
attr_accessor :target
|
7
|
+
|
8
|
+
def initialize(target, hydra = nil)
|
9
|
+
@target = target
|
10
|
+
@hydra = hydra || Typhoeus::Hydra.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def resource(name, req, &block)
|
14
|
+
@queue ||= []
|
15
|
+
@queue << QueuedRequest.new(@hydra, name, req, @target)
|
16
|
+
#@queue[name].on_complete &block if block != nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def resource_with_target(name, req, target, &block)
|
20
|
+
@queue ||= []
|
21
|
+
@queue << QueuedRequest.new(@hydra, name, req, target)
|
22
|
+
#@queue[name].on_complete &block if block != nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def requests
|
26
|
+
@queue ||= []
|
27
|
+
end
|
28
|
+
|
29
|
+
def run
|
30
|
+
@hydra.run
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'typhoid/uri'
|
3
|
+
|
4
|
+
module Typhoid
|
5
|
+
class Resource
|
6
|
+
include Typhoid::Multi
|
7
|
+
include Typhoid::Attributes
|
8
|
+
|
9
|
+
class << self
|
10
|
+
attr_accessor :site, :path
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :resource_exception
|
14
|
+
|
15
|
+
def self.build_request(uri, options = {})
|
16
|
+
Typhoid::RequestBuilder.new(self, uri, options)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.run(request)
|
20
|
+
method = request.http_method
|
21
|
+
build(request.klass, (Typhoeus::Request.send method, request.request_uri, request.options))
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.uri_join(*paths)
|
25
|
+
Uri.new(*paths).to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
# Get this request URI based on site and path, can attach
|
29
|
+
# more paths
|
30
|
+
def self.request_uri(*more_paths)
|
31
|
+
uri_join site, path, *more_paths
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(params = {})
|
35
|
+
load_values(params)
|
36
|
+
end
|
37
|
+
|
38
|
+
def success?
|
39
|
+
!resource_exception
|
40
|
+
end
|
41
|
+
|
42
|
+
def save!(method = nil)
|
43
|
+
save method
|
44
|
+
raise resource_exception unless success?
|
45
|
+
end
|
46
|
+
|
47
|
+
def destroy!
|
48
|
+
destroy
|
49
|
+
raise resource_exception unless success?
|
50
|
+
end
|
51
|
+
|
52
|
+
def save(method = nil)
|
53
|
+
request_and_load do
|
54
|
+
Typhoeus::Request.send save_http_method(method), save_request.request_uri, save_request.options
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def destroy
|
59
|
+
request_and_load do
|
60
|
+
Typhoeus::Request.delete(delete_request.request_uri, delete_request.options)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def save_request
|
65
|
+
(new_record?) ? create_request : update_request
|
66
|
+
end
|
67
|
+
|
68
|
+
def save_http_method(method = nil)
|
69
|
+
return method if method
|
70
|
+
(new_record?) ? :post : :put
|
71
|
+
end
|
72
|
+
|
73
|
+
# Request URI is either in the object we retrieveed initially, built from
|
74
|
+
# site + path + id, or fail to the regular class#request_uri
|
75
|
+
#
|
76
|
+
# Also, check that the server we're speaking to isn't hypermedia inclined so
|
77
|
+
# look at our attributes for a URI
|
78
|
+
def request_uri
|
79
|
+
attributes["uri"] || (new_record? ? self.class.request_uri : self.class.request_uri(id))
|
80
|
+
end
|
81
|
+
|
82
|
+
def request_and_load(&block)
|
83
|
+
self.resource_exception = nil
|
84
|
+
response = yield
|
85
|
+
self.class.load_values(self, response)
|
86
|
+
success?
|
87
|
+
end
|
88
|
+
|
89
|
+
def persisted?
|
90
|
+
!new_record?
|
91
|
+
end
|
92
|
+
|
93
|
+
def new_record?
|
94
|
+
id.to_s.length < 1
|
95
|
+
end
|
96
|
+
alias new? new_record?
|
97
|
+
|
98
|
+
protected
|
99
|
+
|
100
|
+
def to_params
|
101
|
+
attributes
|
102
|
+
end
|
103
|
+
|
104
|
+
def create_request(method = :post)
|
105
|
+
Typhoid::RequestBuilder.new(self.class, request_uri, :body => to_params.to_json, :method => method, :headers => {"Content-Type" => 'application/json'})
|
106
|
+
end
|
107
|
+
|
108
|
+
def update_request(method = :put)
|
109
|
+
Typhoid::RequestBuilder.new(self.class, request_uri, :body => to_params.to_json, :method => method, :headers => {"Content-Type" => 'application/json'})
|
110
|
+
end
|
111
|
+
|
112
|
+
def delete_request(method = :delete)
|
113
|
+
Typhoid::RequestBuilder.new(self.class, request_uri, :method => method)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/lib/typhoid/uri.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'uri'
|
2
|
+
module Typhoid
|
3
|
+
class Uri
|
4
|
+
private
|
5
|
+
attr_writer :base
|
6
|
+
attr_writer :paths
|
7
|
+
|
8
|
+
public
|
9
|
+
attr_reader :base
|
10
|
+
attr_reader :paths
|
11
|
+
|
12
|
+
def initialize(*paths)
|
13
|
+
self.base = URI.parse paths.shift.to_s
|
14
|
+
self.paths = sanitize(base.path) + sanitize(paths)
|
15
|
+
base.path = ""
|
16
|
+
raise "Invalid Base on #uri_join: #{base}" unless base.scheme || base.host
|
17
|
+
end
|
18
|
+
|
19
|
+
def join(*more_paths)
|
20
|
+
full_path = (paths + sanitize(more_paths)).join "/"
|
21
|
+
base.clone.merge(full_path).to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
join
|
26
|
+
end
|
27
|
+
|
28
|
+
def sanitize(*need_sanitizing)
|
29
|
+
need_sanitizing.
|
30
|
+
flatten.
|
31
|
+
compact.
|
32
|
+
map { |p| p.to_s.split("/").compact.delete_if(&:empty?) }.
|
33
|
+
flatten
|
34
|
+
end
|
35
|
+
private :sanitize
|
36
|
+
end
|
37
|
+
end
|
data/lib/typhoid.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require "typhoid/version"
|
2
|
+
require 'typhoid/uri'
|
3
|
+
require 'typhoid/parser'
|
4
|
+
require 'typhoid/builder'
|
5
|
+
require "typhoid/request_queue"
|
6
|
+
require "typhoid/queued_request"
|
7
|
+
require "typhoid/multi"
|
8
|
+
require 'typhoid/attributes'
|
9
|
+
require 'typhoid/resource'
|
10
|
+
require 'typhoid/request_builder'
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper.rb"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
|
8
|
+
require 'json'
|
9
|
+
|
10
|
+
Dir["spec/support/**/*.rb"].each {|f| require "./#{f}"}
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
14
|
+
config.run_all_when_everything_filtered = true
|
15
|
+
config.filter_run :focus
|
16
|
+
config.color = true
|
17
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'typhoid'
|
2
|
+
|
3
|
+
class Game < Typhoid::Resource
|
4
|
+
field :id
|
5
|
+
field :team_1_name
|
6
|
+
field :team_2_name
|
7
|
+
field :start_time
|
8
|
+
|
9
|
+
self.site = 'http://localhost:3000/'
|
10
|
+
self.path = 'games/'
|
11
|
+
|
12
|
+
def self.get_game
|
13
|
+
build_request("http://localhost:3000/games/1")
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Typhoid
|
3
|
+
describe Builder do
|
4
|
+
let(:example_json) { <<-JSON
|
5
|
+
{
|
6
|
+
"metadata": {
|
7
|
+
"current_user": {
|
8
|
+
"first_name": "Jon",
|
9
|
+
"id": 1,
|
10
|
+
"last_name": "Gilmore",
|
11
|
+
"uri": "http://user-service.dev/users/1",
|
12
|
+
"user_name": "admin"
|
13
|
+
}
|
14
|
+
},
|
15
|
+
"result": {
|
16
|
+
"first_name": "Jon",
|
17
|
+
"id": 15,
|
18
|
+
"last_name": "Phenow",
|
19
|
+
"type": "orphan",
|
20
|
+
"uri": "http://user-service.dev/personas/15",
|
21
|
+
"user": null
|
22
|
+
}
|
23
|
+
}
|
24
|
+
JSON
|
25
|
+
}
|
26
|
+
|
27
|
+
let(:example_array) { <<-JSON
|
28
|
+
[{"metadata": null }, {"metadata": null }]
|
29
|
+
JSON
|
30
|
+
}
|
31
|
+
let(:klass) { Resource }
|
32
|
+
let(:response) { double body: mocked_body, success?: mocked_success }
|
33
|
+
let(:mocked_body) { example_json }
|
34
|
+
let(:mocked_success) { true }
|
35
|
+
describe "class" do
|
36
|
+
subject { Builder }
|
37
|
+
describe "call" do
|
38
|
+
subject { Builder.call klass, response }
|
39
|
+
it { should be_a Resource }
|
40
|
+
its(:attributes) { should have_key "metadata" }
|
41
|
+
its(:attributes) { should have_key "result" }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "instance" do
|
46
|
+
subject { Builder.new klass, response }
|
47
|
+
describe "successful" do
|
48
|
+
describe "singular" do
|
49
|
+
it "calls expected building methods" do
|
50
|
+
klass.any_instance.should_receive(:after_build).once
|
51
|
+
subject.build.should be_a Resource
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "array" do
|
56
|
+
let(:mocked_body) { example_array }
|
57
|
+
it "calls expected building methods" do
|
58
|
+
subject.build.should be_an Array
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "unsuccessful" do
|
64
|
+
let(:mocked_success) { false }
|
65
|
+
subject { Builder.new(klass, response).build }
|
66
|
+
describe "singular" do
|
67
|
+
it { should be_a Resource }
|
68
|
+
its(:resource_exception) { should_not be_nil }
|
69
|
+
its(:attributes) { should have_key "metadata" }
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "array" do
|
73
|
+
let(:mocked_body) { example_array }
|
74
|
+
subject { Builder.new(klass, response).build.first }
|
75
|
+
|
76
|
+
it { should be_a Resource }
|
77
|
+
its(:resource_exception) { should_not be_nil }
|
78
|
+
its(:attributes) { should have_key "metadata" }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
|
5
|
+
describe Typhoid::Multi do
|
6
|
+
context "making multiple requests" do
|
7
|
+
before(:each) do
|
8
|
+
@fake_hydra = Typhoeus::Hydra.new
|
9
|
+
game = Typhoeus::Response.new(:code => 200, :headers => "", :body => {"team_1_name" => "Bears"}.to_json, :time => 0.03)
|
10
|
+
@fake_hydra.stub(:get, "http://localhost:3000/games/1").and_return(game)
|
11
|
+
|
12
|
+
stats = Typhoeus::Response.new(:code => 200, :headers => "",
|
13
|
+
:body => [{'player_name' => 'Bob', 'goals' => 1}, {'player_name' => 'Mike', 'goals' => 1}].to_json, :time => 0.02)
|
14
|
+
@fake_hydra.stub(:get, "http://localhost:3000/stats/2").and_return(stats)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should assign the response to instance variables" do
|
18
|
+
controller = Controller.new
|
19
|
+
controller.remote_resources(@fake_hydra) do |req|
|
20
|
+
req.resource(:game, Game.get_game)
|
21
|
+
req.resource(:stats, PlayerStat.get_stats)
|
22
|
+
end
|
23
|
+
#games returns a single object
|
24
|
+
controller.instance_variable_get("@game").class.should eql Game
|
25
|
+
controller.instance_variable_get("@game").team_1_name.should eql "Bears"
|
26
|
+
|
27
|
+
#stats returns an array
|
28
|
+
controller.instance_variable_get("@stats").class.should eql Array
|
29
|
+
controller.instance_variable_get("@stats")[0].class.should eql PlayerStat
|
30
|
+
controller.instance_variable_get("@stats")[0].player_name.should eql 'Bob'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Typhoid
|
4
|
+
describe Parser do
|
5
|
+
let(:example_json) { <<-JSON
|
6
|
+
{
|
7
|
+
"metadata": {
|
8
|
+
"current_user": {
|
9
|
+
"first_name": "Jon",
|
10
|
+
"id": 1,
|
11
|
+
"last_name": "Gilmore",
|
12
|
+
"uri": "http://user-service.dev/users/1",
|
13
|
+
"user_name": "admin"
|
14
|
+
}
|
15
|
+
},
|
16
|
+
"result": {
|
17
|
+
"first_name": "Jon",
|
18
|
+
"id": 15,
|
19
|
+
"last_name": "Phenow",
|
20
|
+
"type": "orphan",
|
21
|
+
"uri": "http://user-service.dev/personas/15",
|
22
|
+
"user": null
|
23
|
+
}
|
24
|
+
}
|
25
|
+
JSON
|
26
|
+
}
|
27
|
+
|
28
|
+
let(:example_array) { <<-JSON
|
29
|
+
[{"metadata": null }, {"metadata": null }]
|
30
|
+
JSON
|
31
|
+
}
|
32
|
+
|
33
|
+
describe "class" do
|
34
|
+
subject { Parser }
|
35
|
+
|
36
|
+
describe "call" do
|
37
|
+
subject { Parser.call(example_json) }
|
38
|
+
|
39
|
+
it { should be_a Hash }
|
40
|
+
it "has an expected element" do
|
41
|
+
subject["result"]["first_name"].should == "Jon"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "instance" do
|
47
|
+
subject { Parser.new example_json }
|
48
|
+
describe "parse" do
|
49
|
+
it "looks like a hash" do
|
50
|
+
subject.parse.should be_a Hash
|
51
|
+
end
|
52
|
+
|
53
|
+
it "has an expected element" do
|
54
|
+
subject.parse["result"]["type"].should == "orphan"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Typhoid::RequestBuilder do
|
4
|
+
context "a request builder object" do
|
5
|
+
it "should provide an http method by default" do
|
6
|
+
req = Typhoid::RequestBuilder.new(Game, 'http://localhost/')
|
7
|
+
req.http_method.should eql :get
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should set http method from options" do
|
11
|
+
req = Typhoid::RequestBuilder.new(Game, 'http://localhost', :method => :post)
|
12
|
+
req.http_method.should eql :post
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Typhoid::Resource do
|
4
|
+
it "synchronizes field with attribute" do
|
5
|
+
response_data = {"team_1_name" => 'Bears', "team_2_name" => 'Lions'}
|
6
|
+
game = Game.new(response_data)
|
7
|
+
game.team_1_name.should == 'Bears'
|
8
|
+
game.attributes["team_1_name"].should == 'Bears'
|
9
|
+
game.attributes["team_1_name"] = 'Da Bears'
|
10
|
+
game.attributes["team_1_name"].should == 'Da Bears'
|
11
|
+
game.team_1_name.should == 'Da Bears'
|
12
|
+
|
13
|
+
game.team_1_name = 'Orange'
|
14
|
+
game.team_1_name.should == 'Orange'
|
15
|
+
game.attributes["team_1_name"].should == 'Orange'
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should have fields defined" do
|
19
|
+
game = Game.new
|
20
|
+
game.should respond_to(:team_1_name)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should populate defined attributes" do
|
24
|
+
response_data = {"team_1_name" => 'Bears', "team_2_name" => 'Lions'}
|
25
|
+
game = Game.new(response_data)
|
26
|
+
game.team_1_name.should eql 'Bears'
|
27
|
+
game.start_time.should be_nil
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should populate attributes" do
|
31
|
+
game = Game.new({"team_1_name" => 'Bears', "team_2_name" => 'Lions'})
|
32
|
+
game.read_attribute(:team_1_name).should eql 'Bears'
|
33
|
+
game[:team_2_name].should eql 'Lions'
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should return the request path" do
|
37
|
+
game = Game.new
|
38
|
+
game.request_uri.should eql "http://localhost:3000/games"
|
39
|
+
end
|
40
|
+
|
41
|
+
context "making a standalone request" do
|
42
|
+
after { hydra.clear_stubs }
|
43
|
+
let(:hydra) { Typhoeus::Hydra.hydra }
|
44
|
+
let(:game_response) { Typhoeus::Response.new(:code => 200, :headers => "", :body => {"team_1_name" => "Bears", "id" => "1"}.to_json) }
|
45
|
+
let(:failed_game_response) { Typhoeus::Response.new(:code => 404, :headers => "", :body => {}.to_json) }
|
46
|
+
it "should retrieve an object" do
|
47
|
+
hydra.stub(:get, "http://localhost:3000/games/1").and_return(game_response)
|
48
|
+
|
49
|
+
game = Game.get_game.run
|
50
|
+
game.class.should eql Game
|
51
|
+
game.team_1_name.should eql 'Bears'
|
52
|
+
end
|
53
|
+
|
54
|
+
it "raises error on save!" do
|
55
|
+
hydra.stub(:post, "http://localhost:3000/games").and_return(failed_game_response)
|
56
|
+
|
57
|
+
game = Game.new
|
58
|
+
expect { game.save! }.to raise_error
|
59
|
+
end
|
60
|
+
|
61
|
+
it "raises error on destroy!" do
|
62
|
+
hydra.stub(:delete, "http://localhost:3000/games/1").and_return(failed_game_response)
|
63
|
+
|
64
|
+
game = Game.new("id" => 1, "team_1_name" => 'Tigers')
|
65
|
+
expect { game.destroy! }.to raise_error
|
66
|
+
end
|
67
|
+
|
68
|
+
it "raises error on save!" do
|
69
|
+
hydra.stub(:post, "http://localhost:3000/games").and_return(game_response)
|
70
|
+
|
71
|
+
game = Game.new
|
72
|
+
expect { game.save! }.to_not raise_error
|
73
|
+
end
|
74
|
+
|
75
|
+
it "raises error on save!" do
|
76
|
+
hydra.stub(:delete, "http://localhost:3000/games/1").and_return(game_response)
|
77
|
+
|
78
|
+
game = Game.new("id" => 1, "team_1_name" => 'Tigers')
|
79
|
+
expect { game.destroy! }.to_not raise_error
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should create an object" do
|
83
|
+
hydra.stub(:post, "http://localhost:3000/games").and_return(game_response)
|
84
|
+
|
85
|
+
game = Game.new
|
86
|
+
game.save
|
87
|
+
|
88
|
+
game.id.should == "1"
|
89
|
+
game.team_1_name.should == "Bears"
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should update an object" do
|
93
|
+
update_response = Typhoeus::Response.new(:code => 200, :headers => "", :body => {"team_1_name" => "Bears", "id" => "1"}.to_json)
|
94
|
+
hydra.stub(:put, "http://localhost:3000/games/1").and_return(update_response)
|
95
|
+
|
96
|
+
game = Game.new("id" => 1, "team_1_name" => 'Tigers')
|
97
|
+
game.save
|
98
|
+
|
99
|
+
game.resource_exception.should be_nil
|
100
|
+
game.team_1_name.should == "Bears"
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should delete an object" do
|
104
|
+
hydra.stub(:delete, "http://localhost:3000/games/1").and_return(game_response)
|
105
|
+
|
106
|
+
game = Game.new("id" => 1, "team_1_name" => 'Tigers')
|
107
|
+
game.destroy
|
108
|
+
|
109
|
+
game.resource_exception.should be nil
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should be able to specify save http verb" do
|
114
|
+
update_response = Typhoeus::Response.new(:code => 200, :headers => "", :body => {"team_1_name" => "Bears", "id" => "1"}.to_json)
|
115
|
+
hydra.stub(:post, "http://localhost:3000/games/1").and_return(update_response)
|
116
|
+
|
117
|
+
game = Game.new("id" => 1, "team_1_name" => 'Tigers')
|
118
|
+
game.save(:post)
|
119
|
+
|
120
|
+
game.resource_exception.should be nil
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context "handling bad requests" do
|
126
|
+
let(:fake_hydra) { Typhoeus::Hydra.new }
|
127
|
+
before do
|
128
|
+
bad_game = Typhoeus::Response.new(:code => 500, :headers => "", :body => "<htmlasdfasdfasdf")
|
129
|
+
fake_hydra.stub(:get, "http://localhost:3000/games/1").and_return(bad_game)
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should assign an exception object on a bad request" do
|
133
|
+
controller = Controller.new
|
134
|
+
controller.remote_resources(fake_hydra) do |req|
|
135
|
+
req.resource(:game, Game.get_game)
|
136
|
+
end
|
137
|
+
|
138
|
+
bad_game = controller.instance_variable_get("@game")
|
139
|
+
bad_game.team_1_name.should be_nil
|
140
|
+
bad_game.resource_exception.class.should be_true
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module Typhoid
|
3
|
+
describe "Uri" do
|
4
|
+
subject { Uri.new *uris }
|
5
|
+
let(:uris) { ["http://localhost/", "users"] }
|
6
|
+
|
7
|
+
its(:to_s) { should == "http://localhost/users" }
|
8
|
+
it "sets base" do
|
9
|
+
subject.base.to_s.should == "http://localhost"
|
10
|
+
end
|
11
|
+
|
12
|
+
it "sets path" do
|
13
|
+
subject.paths.should == ["users"]
|
14
|
+
end
|
15
|
+
|
16
|
+
it "appends paths" do
|
17
|
+
subject.join("/","/a/","b","/c","d/").should == "http://localhost/users/a/b/c/d"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "when joining it doesn't change itself" do
|
21
|
+
expect {
|
22
|
+
subject.join("/","/a/","b","/c","d/").should == "http://localhost/users/a/b/c/d"
|
23
|
+
}.
|
24
|
+
to_not change { subject.to_s }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/typhoid.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/typhoid/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Doug Rohde"]
|
6
|
+
gem.email = ["doug.rohde@tstmedia.com"]
|
7
|
+
gem.description = %q{A lightweight ORM-like wrapper around Typhoeus}
|
8
|
+
gem.summary = %q{A lightweight ORM-like wrapper around Typhoeus}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "typhoid"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Typhoid::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency 'typhoeus'
|
19
|
+
|
20
|
+
gem.add_development_dependency 'rspec'
|
21
|
+
gem.add_development_dependency 'json_pure', [">= 1.4.1"]
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: typhoid
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Doug Rohde
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-28 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: typhoeus
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: json_pure
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.4.1
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.4.1
|
62
|
+
description: A lightweight ORM-like wrapper around Typhoeus
|
63
|
+
email:
|
64
|
+
- doug.rohde@tstmedia.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- .rvmrc
|
71
|
+
- Gemfile
|
72
|
+
- Gemfile.lock
|
73
|
+
- LICENSE
|
74
|
+
- README.md
|
75
|
+
- Rakefile
|
76
|
+
- lib/typhoid.rb
|
77
|
+
- lib/typhoid/attributes.rb
|
78
|
+
- lib/typhoid/builder.rb
|
79
|
+
- lib/typhoid/multi.rb
|
80
|
+
- lib/typhoid/parser.rb
|
81
|
+
- lib/typhoid/queued_request.rb
|
82
|
+
- lib/typhoid/request_builder.rb
|
83
|
+
- lib/typhoid/request_queue.rb
|
84
|
+
- lib/typhoid/resource.rb
|
85
|
+
- lib/typhoid/uri.rb
|
86
|
+
- lib/typhoid/version.rb
|
87
|
+
- spec/spec_helper.rb
|
88
|
+
- spec/support/controller.rb
|
89
|
+
- spec/support/game.rb
|
90
|
+
- spec/support/player_stat.rb
|
91
|
+
- spec/typhoid/builder_spec.rb
|
92
|
+
- spec/typhoid/multi_spec.rb
|
93
|
+
- spec/typhoid/parser_spec.rb
|
94
|
+
- spec/typhoid/request_builder_spec.rb
|
95
|
+
- spec/typhoid/resource_spec.rb
|
96
|
+
- spec/typhoid/typhoid_spec.rb
|
97
|
+
- spec/typhoid/uri_spec.rb
|
98
|
+
- typhoid.gemspec
|
99
|
+
homepage: ''
|
100
|
+
licenses: []
|
101
|
+
post_install_message:
|
102
|
+
rdoc_options: []
|
103
|
+
require_paths:
|
104
|
+
- lib
|
105
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
segments:
|
112
|
+
- 0
|
113
|
+
hash: -3289300841199421232
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
none: false
|
116
|
+
requirements:
|
117
|
+
- - ! '>='
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
segments:
|
121
|
+
- 0
|
122
|
+
hash: -3289300841199421232
|
123
|
+
requirements: []
|
124
|
+
rubyforge_project:
|
125
|
+
rubygems_version: 1.8.24
|
126
|
+
signing_key:
|
127
|
+
specification_version: 3
|
128
|
+
summary: A lightweight ORM-like wrapper around Typhoeus
|
129
|
+
test_files:
|
130
|
+
- spec/spec_helper.rb
|
131
|
+
- spec/support/controller.rb
|
132
|
+
- spec/support/game.rb
|
133
|
+
- spec/support/player_stat.rb
|
134
|
+
- spec/typhoid/builder_spec.rb
|
135
|
+
- spec/typhoid/multi_spec.rb
|
136
|
+
- spec/typhoid/parser_spec.rb
|
137
|
+
- spec/typhoid/request_builder_spec.rb
|
138
|
+
- spec/typhoid/resource_spec.rb
|
139
|
+
- spec/typhoid/typhoid_spec.rb
|
140
|
+
- spec/typhoid/uri_spec.rb
|