otis 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in skyscanner.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Thiago Bueno
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.
@@ -0,0 +1,53 @@
1
+ # Otis
2
+
3
+ API Wrapper Framework
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'otis'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install otis
18
+
19
+ ## Usage
20
+
21
+ ### Map
22
+
23
+ It all starts by describing the webservice/API that you would be interacting to by creating Otis::Map of entry points and response objects.
24
+
25
+ Using the example of [US Zip webservice](https://github.com/tbueno/otis_uszip_example), this first step would be like this:
26
+
27
+ Otis::Map.new({
28
+ :get_info_by_zip => UsZip::InfoZipResponse,
29
+ :get_info_by_state => UsZip::InfoStateResponse,
30
+ :get_info_by_city => UsZip::InfoCityResponse,
31
+ :get_info_by_area_code => UsZip::InfoAreaCodeResponse
32
+ })
33
+
34
+ ### Client
35
+ Otis offers a base Client class that handle the response and instantiates the response object accordingly.
36
+
37
+ Otis::Client.new(routes, "http://www.webservicex.net/uszip.asmx?WSDL")
38
+
39
+ ### Model
40
+
41
+ The Client takes care of transforming the API response into a Hash. Having hashes, makes it easy to create response objects, called Otis::Model.
42
+
43
+ The main purpose of a Otis::Model is to wrap a hash into a object with helpful methods for dealing with inconsistence and other useful functionalities.
44
+
45
+
46
+
47
+ ## Contributing
48
+
49
+ 1. Fork it
50
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
51
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
52
+ 4. Push to the branch (`git push origin my-new-feature`)
53
+ 5. Create new Pull Request
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,13 @@
1
+ require "otis/version"
2
+ require "virtus"
3
+ require "savon"
4
+ require 'json'
5
+ module Otis
6
+ require 'otis/hash_content'
7
+ require 'otis/otis_object'
8
+ require 'otis/model'
9
+ require 'otis/client'
10
+ require 'otis/http_client'
11
+ require 'otis/soap_client'
12
+ require 'otis/map'
13
+ end
@@ -0,0 +1,12 @@
1
+ module Otis
2
+ class Client
3
+ protected
4
+ # Tries to find the requested method in the routes map.
5
+ # Send it to Object$method_missing if the desired route is not found
6
+ def method_missing(meth, *args)
7
+ klass = @routes[meth.to_sym]
8
+ super unless klass
9
+ klass.new(call(meth, args))
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,37 @@
1
+ module Otis
2
+ # This module holds a collection of utilitie methods for Hash objects
3
+ #
4
+ module HashContent
5
+ def self.included(base)
6
+ base.extend(ClassExtension)
7
+ end
8
+
9
+ def initialize(attrs = {})
10
+ @response = root(attrs)
11
+ super(@response)
12
+ end
13
+
14
+ private
15
+ def root(attrs)
16
+ return attrs unless self.respond_to?(:path)
17
+ attrs = sub_tree(attrs)
18
+ if self.respond_to?(:new_root)
19
+ Hash[new_root.to_sym => attrs]
20
+ else
21
+ attrs
22
+ end
23
+ end
24
+
25
+ # Navigates in the Hash tree and return a subtree based on the path list
26
+ def sub_tree(attrs)
27
+ path.inject(attrs){ |res, el| res[el]}
28
+ end
29
+
30
+ module ClassExtension
31
+ def root_key(opts = {})
32
+ class_eval "def new_root; '#{opts[:as]}'; end " if opts[:as]
33
+ class_eval %( def path; #{opts[:to]}; end )
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,28 @@
1
+ require 'faraday'
2
+
3
+ module Otis
4
+ class HttpClient < Client
5
+ def initialize(map, url)
6
+ @routes = map.routes
7
+ @client = create_client(url)
8
+ end
9
+
10
+ def operations
11
+ @routes.keys
12
+ end
13
+
14
+ protected
15
+ def call(action, options)
16
+ response = @client.get "#{options.first}/#{action}", options.last
17
+ JSON.parse(response.body)
18
+ end
19
+
20
+ def create_client(url)
21
+ Faraday.new(:url => url) do |faraday|
22
+ faraday.request :url_encoded
23
+ faraday.response :logger
24
+ faraday.adapter Faraday.default_adapter
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,8 @@
1
+ module Otis
2
+ class Map
3
+ attr_reader :routes
4
+ def initialize(routes)
5
+ @routes = routes
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ module Otis
2
+ class Model
3
+ include Otis::Object
4
+ include Virtus.model
5
+ include HashContent
6
+ end
7
+ end
@@ -0,0 +1,39 @@
1
+ module Otis
2
+ module Object
3
+
4
+ def self.included(base)
5
+ base.extend(ClassExtension)
6
+ end
7
+
8
+ def initialize(attrs = {})
9
+ @response = attrs
10
+ attrs.each_pair do |k, v|
11
+ m = underscore(k.to_s)
12
+ self.send("#{m}=", v ) if self.respond_to?("#{m}=")
13
+ end if respond_to?(:decamelize?) and decamelize?
14
+ if respond_to?(:hooks)
15
+ hooks.each do |hook|
16
+ self.send(hook)
17
+ end
18
+ end
19
+ end
20
+
21
+ module ClassExtension
22
+ def attributes(*args)
23
+ args.each do |m|
24
+ class_eval %(attr_accessor :#{m} )
25
+ end
26
+ end
27
+ end
28
+
29
+ private
30
+ def underscore(string)
31
+ string.gsub(/::/, '/').
32
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
33
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
34
+ tr("-", "_").
35
+ downcase
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,18 @@
1
+ module Otis
2
+ class SoapClient < Client
3
+ def initialize(map, wsdl)
4
+ @routes = map.routes
5
+ @client = Savon.client(wsdl: wsdl)
6
+ end
7
+
8
+ def operations
9
+ @client.operations
10
+ end
11
+
12
+ protected
13
+ def call(action, options)
14
+ soap_response = @client.call(action, options.first).body
15
+ soap_response
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module Otis
2
+ VERSION = "0.0.4"
3
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'otis/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "otis"
8
+ spec.version = Otis::VERSION
9
+ spec.authors = ["Thiago Bueno"]
10
+ spec.email = ["tbueno@tbueno.com"]
11
+ spec.description = %q{A Ruby Api wrapper framework}
12
+ spec.summary = %q{A Ruby Api wrapper framework}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "debugger"
24
+
25
+ spec.add_dependency 'savon', '~> 2.2.0'
26
+ spec.add_dependency 'faraday'
27
+ spec.add_dependency 'virtus'
28
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ describe Otis::HttpClient do
4
+
5
+ class MyHttpClient < Otis::HttpClient
6
+ end
7
+ class ResponseClass; def initialize(string); end; end
8
+
9
+ let(:map) do
10
+ Otis::Map.new({
11
+ :op1 => ResponseClass,
12
+ :op2 => double
13
+ })
14
+ end
15
+
16
+ describe 'operations' do
17
+ it 'maps the allowed entrypoints' do
18
+ expect(described_class.new(map, 'url').operations).to eq([:op1, :op2])
19
+ end
20
+ end
21
+
22
+ describe 'create client' do
23
+ let(:url) { 'http://api.site.com' }
24
+
25
+ it 'instantiate a faraday client with endpoint url' do
26
+ Faraday.should_receive(:new).with(url: url)
27
+ described_class.new(map, url)
28
+ end
29
+
30
+ describe 'default options' do
31
+
32
+ after :each do described_class.new(map, url) end
33
+
34
+ it 'sets the request type' do
35
+ Faraday::Connection.any_instance.should_receive(:request).with(:url_encoded)
36
+ end
37
+
38
+ it 'sets response output' do
39
+ Faraday::Connection.any_instance.should_receive(:response).with(:logger)
40
+ end
41
+
42
+ it 'sets adapter' do
43
+ Faraday::Connection.any_instance.should_receive(:adapter).with(Faraday.default_adapter)
44
+ end
45
+ end
46
+ end
47
+
48
+ describe 'call' do
49
+
50
+ let(:faraday) { double(get: response) }
51
+ let(:response) { double(body: "{\"my_call\": \"response\"}")}
52
+ let(:routes) { Otis::Map.new({my_call: ResponseClass}) }
53
+
54
+ before { Otis::HttpClient.any_instance.stub(create_client: faraday)}
55
+
56
+ it 'forwards the call to the client' do
57
+ faraday.should_receive(:get).with("api/v1/my_call", {param1: 'foo', param2: 'bar'})
58
+ MyHttpClient.new(routes, 'url').my_call('api/v1', {param1: 'foo', param2: 'bar'})
59
+ end
60
+
61
+ it 'returns response object' do
62
+ MyHttpClient.new(routes, 'url').my_call('api/v1', {param1: 'foo', param2: 'bar'})
63
+ .should be_a(ResponseClass)
64
+ end
65
+
66
+ it 'passes the parsed response to response object' do
67
+ ResponseClass.should_receive(:new).with({'my_call' => 'response'})
68
+ MyHttpClient.new(routes, 'url').my_call('api/v1', {param1: 'foo', param2: 'bar'})
69
+ end
70
+ end
71
+
72
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe Otis::Model do
4
+
5
+ describe 'initialization' do
6
+ class SimpleClass < Otis::Model
7
+ attribute :my_key, String
8
+ end
9
+
10
+ describe 'with attributes' do
11
+ it 'creates the attribute getter' do
12
+ expect(SimpleClass.new({my_key: 'value'}).my_key).to eql('value')
13
+ end
14
+ end
15
+ end
16
+
17
+ describe 'accessor generation' do
18
+ let(:params) do {a: {b: {foo: 'bar'}}} end
19
+
20
+ class ComplexClass < Otis::Model
21
+ root_key to: [:a, :b]
22
+ attribute :foo, String
23
+ end
24
+
25
+ it 'creates an array to the root key' do
26
+ ComplexClass.new(params).path.should == [:a, :b]
27
+ end
28
+
29
+ it 'sets the root key' do
30
+ ComplexClass.new(params).foo.should == 'bar'
31
+ end
32
+ end
33
+
34
+ describe 'remaping root key name' do
35
+
36
+ let(:params) do {a: {b: {foo: 'bar'}}} end
37
+
38
+ class EvenMoreComplex < Otis::Model
39
+ root_key to: [:a, :b], as: 'baz'
40
+ attribute :baz, String
41
+ end
42
+
43
+ it 'response accordingly' do
44
+ EvenMoreComplex.new(params).baz.should == {foo: 'bar'}
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe Otis::SoapClient do
4
+
5
+ class MySoapClient < Otis::SoapClient
6
+ end
7
+ class ResponseClass; def initialize(response); end; end
8
+
9
+ let(:client) { double(operations: [:op1, :op2])}
10
+ let(:routes) { Otis::Map.new({my_call: ResponseClass}) }
11
+
12
+ before { Savon.stub(client: client) }
13
+
14
+ describe 'operations' do
15
+ it 'maps the allowed from client' do
16
+ expect(described_class.new(routes, double).operations).to eq([:op1, :op2])
17
+ end
18
+ end
19
+
20
+ describe 'call' do
21
+ let(:response) { {my_call: 'response'}}
22
+
23
+ before { client.stub_chain(:call, :body).and_return(response) }
24
+
25
+ it 'delegates the call the the client' do
26
+ client.should_receive(:call).with(:my_call, {params: []})
27
+ MySoapClient.new(routes, double).my_call(params: [])
28
+ end
29
+
30
+ it 'returns response object' do
31
+ MySoapClient.new(routes, double).my_call(params: []).should be_a(ResponseClass)
32
+ end
33
+
34
+ it 'passes the attributes to response object' do
35
+ ResponseClass.should_receive(:new).with(response)
36
+ MySoapClient.new(routes, double).my_call(params: [])
37
+ end
38
+ end
39
+ end
@@ -0,0 +1 @@
1
+ require File.expand_path('../../lib/otis.rb', __FILE__)
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: otis
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Thiago Bueno
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-11-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
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: debugger
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
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: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: savon
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 2.2.0
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 2.2.0
78
+ - !ruby/object:Gem::Dependency
79
+ name: faraday
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: virtus
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :runtime
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ description: A Ruby Api wrapper framework
111
+ email:
112
+ - tbueno@tbueno.com
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files: []
116
+ files:
117
+ - .gitignore
118
+ - .rspec
119
+ - Gemfile
120
+ - LICENSE.txt
121
+ - README.md
122
+ - Rakefile
123
+ - lib/otis.rb
124
+ - lib/otis/client.rb
125
+ - lib/otis/hash_content.rb
126
+ - lib/otis/http_client.rb
127
+ - lib/otis/map.rb
128
+ - lib/otis/model.rb
129
+ - lib/otis/otis_object.rb
130
+ - lib/otis/soap_client.rb
131
+ - lib/otis/version.rb
132
+ - otis.gemspec
133
+ - spec/lib/otis/http_client_spec.rb
134
+ - spec/lib/otis/model_spec.rb
135
+ - spec/lib/otis/soap_client_spec.rb
136
+ - spec/spec_helper.rb
137
+ homepage: ''
138
+ licenses:
139
+ - MIT
140
+ post_install_message:
141
+ rdoc_options: []
142
+ require_paths:
143
+ - lib
144
+ required_ruby_version: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ! '>='
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ required_rubygems_version: !ruby/object:Gem::Requirement
151
+ none: false
152
+ requirements:
153
+ - - ! '>='
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ requirements: []
157
+ rubyforge_project:
158
+ rubygems_version: 1.8.23
159
+ signing_key:
160
+ specification_version: 3
161
+ summary: A Ruby Api wrapper framework
162
+ test_files:
163
+ - spec/lib/otis/http_client_spec.rb
164
+ - spec/lib/otis/model_spec.rb
165
+ - spec/lib/otis/soap_client_spec.rb
166
+ - spec/spec_helper.rb