abstract_api_wrapper 1.0.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 41eb4526da6c48255ee04ef0cf6ee56fb0e0af85
4
+ data.tar.gz: d3f6a78965318217ad58c685e0c6e02b5ee975d4
5
+ SHA512:
6
+ metadata.gz: 0ac6d2491828a02e03ddff2668fddd8f98ac97344c87602c9aa4d66d5f2d5d71f98f7bbba15e66052e38f63d584fa90ba42c38432bf41f5a79bb193deba36640
7
+ data.tar.gz: add2a654238708294e2bd500681a585b67849d2fd52bc0e0c67d5ffee6892a56e90a35545dafafcd406f1c1d90c0812afb5d4a5cb2abd402989fdd03ea6de26f
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /*.gem
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in abstract-api-wrapper.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # AbstractApiWrapper
2
+
3
+ TODO: summary
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'abstract_api_wrapper'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install abstract_api_wrapper
20
+
21
+ ## Usage
22
+
23
+ Modify the `endpoint` method appending the `.json` extension to all requests
24
+
25
+ ```ruby
26
+ def endpoint
27
+ query_string = filters.map { |k, v| "#{k}=#{v}" }.join('&')
28
+ "#{@client.base_url}/#{path.join('/')}.json?#{query_string}"
29
+ end
30
+ ```
31
+
32
+ ```ruby
33
+ ACCESS_TOKEN = "YOUR ACCESS TOKEN"
34
+ client = AbstractApiWrapper::Client.new(access_token: ACCESS_TOKEN, base_url: 'https://launchpad.37signals.com')
35
+ authorizations = client.authorization.all.run
36
+ # => <AbstractApiWrapper::Response::Resource accounts=#<Hashie::Array [#<AbstractApiWrapper::Response::Resource app_href="https://basecamp.com/xxxxxxx" href="https://basecamp.com/xxxxxxx/api/v1" id=xxxxxxx name="Your Company" product="bcx">, #<AbstractApiWrapper::Response::Resource app_href="https://basecamp.com/xxxxxxx" href="https://basecamp.com/xxxxxxx/api/v1" id=xxxxxxx name="Another company" product="bcx">]> expires_at="2016-12-19T18:13:52.000Z" identity=#<AbstractApiWrapper::Response::Resource email_address="email@yourcompany.com" first_name="Juan" id=xxxxxx last_name="Puelpan">>
37
+
38
+ account = authorizations.accounts.first
39
+ client = AbstractApiWrapper::Client.new(access_token: ACCESS_TOKEN, base_url: account.href)
40
+ projects = client.projects.all.run
41
+ puts projects
42
+ puts projects.pagination
43
+ ```
44
+
45
+ ## Contributing
46
+
47
+ Bug reports and pull requests are welcome on GitHub at https://github.com/octopull/abstract_api_wrapper.
48
+
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler'
2
+ require 'rspec/core/rake_task'
3
+ require 'bundler/gem_tasks'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'abstract_api_wrapper/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'abstract_api_wrapper'
8
+ spec.version = AbstractApiWrapper::VERSION
9
+ spec.authors = ['Juan Puelpan']
10
+ spec.email = ['juan@octopull.us']
11
+
12
+ spec.summary = %q{An abstract REST API wrapper}
13
+ spec.description = %q{An abstract REST API wrapper based on missing_method magic}
14
+ spec.homepage = "https://github.com/octopull/abstract_api_wrapper"
15
+
16
+ spec.files = `git ls-files`.split("\n")
17
+ spec.bindir = 'exe'
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'json' , '~> 2.0'
22
+ spec.add_dependency 'faraday' , '~> 0.10'
23
+ spec.add_dependency 'hashie' , '~> 3.4'
24
+
25
+ spec.add_development_dependency 'bundler' , '~> 1.11'
26
+ spec.add_development_dependency 'rake' , '~> 10.0'
27
+ spec.add_development_dependency 'rspec' , '~> 3.5'
28
+ spec.add_development_dependency 'webmock' , '~> 1.24'
29
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "abstract_api_wrapper"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,169 @@
1
+ require 'abstract_api_wrapper/version'
2
+ require 'json'
3
+ require 'faraday'
4
+ require 'hashie/mash'
5
+
6
+ class AbstractApiWrapper
7
+ class Client
8
+ attr_reader :access_token, :base_url, :apiver
9
+
10
+ def initialize(**options)
11
+ @base_url = options[:base_url]
12
+ @apiver = options[:apiver]
13
+ @access_token = options[:access_token]
14
+ end
15
+
16
+ def method_missing(name, *params, &block)
17
+ AbstractApiWrapper::Request.new(name.to_s, self)
18
+ end
19
+ end
20
+
21
+ class Request
22
+ attr_accessor :path, :filters, :chain
23
+
24
+ METHODS_MAP = {
25
+ 'all' => 'get',
26
+ 'head' => 'head',
27
+ 'find' => 'get',
28
+ 'create' => 'post',
29
+ 'update' => 'put',
30
+ 'destroy' => 'delete'
31
+ }.freeze
32
+
33
+ def initialize(*options)
34
+ @client = options[1]
35
+ @path = []
36
+ @chain = []
37
+ @filters = Hashie::Mash.new
38
+ @params = Hashie::Mash.new
39
+
40
+ method_name = options[0]
41
+
42
+ unless method_name.nil?
43
+ @path << method_name
44
+ @chain << method_name
45
+ end
46
+ end
47
+
48
+ def method_missing(name, *params, &block)
49
+ method_name = name.to_s
50
+ return self if chain.last == method_name
51
+
52
+ chain.push(method_name)
53
+
54
+ case method_name
55
+ when 'all'
56
+ filters.merge!(params.first || {})
57
+ when 'head'
58
+ filters.merge!(params.first || {})
59
+ when 'find'
60
+ path.push(params.first.to_s)
61
+ when 'create'
62
+ @params = Hashie::Mash.new(params.first || {})
63
+ when 'update'
64
+ if chain[-2] == 'find'
65
+ @params = Hashie::Mash.new(params.first || {})
66
+ end
67
+ when 'destroy'
68
+ if chain[-2] == 'find'
69
+ path.push(params.first.to_s)
70
+ end
71
+ else
72
+ path.push(method_name)
73
+ end
74
+
75
+ self
76
+ end
77
+
78
+ def run
79
+ method = METHODS_MAP[@chain.last] || 'get'
80
+ params = @params.any? ? @params.to_json : nil
81
+
82
+ request = Faraday.send(method, endpoint, params, headers)
83
+ response = AbstractApiWrapper::Response.new(request)
84
+
85
+ # Reset variables
86
+ path = []
87
+ chain = []
88
+ filters = Hashie::Mash.new
89
+ params = Hashie::Mash.new
90
+
91
+ response.body
92
+ end
93
+
94
+ def headers
95
+ {
96
+ 'Authorization' => "token #{@client.access_token}",
97
+ 'Content-Type' => 'application/json'
98
+ }
99
+ end
100
+
101
+ def endpoint
102
+ # Here is a Query String API versioning with the `apiver` param
103
+ # You can change this to whatever your API versioning system is
104
+ filters.apiver = @client.apiver
105
+ query_string = filters.map { |k, v| "#{k}=#{v}" }.join('&')
106
+
107
+ "#{@client.base_url}/#{path.join('/')}?#{query_string}"
108
+ end
109
+ end
110
+
111
+ class Response
112
+
113
+ def initialize(request)
114
+ @request = request
115
+
116
+ @parsed_body = if (request.body.nil? || request.body.empty?)
117
+ []
118
+ else
119
+ JSON.parse(request.body)
120
+ end
121
+ end
122
+
123
+ def body
124
+ if @parsed_body.is_a?(Hash)
125
+ AbstractApiWrapper::Response::Resource.new(@parsed_body, @request)
126
+ elsif @parsed_body.is_a?(Array)
127
+ collection = @parsed_body.map do |item|
128
+ AbstractApiWrapper::Response::Resource.new(item)
129
+ end
130
+
131
+ AbstractApiWrapper::Response::Collection.new(collection, @request)
132
+ end
133
+ end
134
+
135
+ class Resource < Hashie::Mash
136
+ attr_reader :request
137
+
138
+ def initialize(*options)
139
+ @request = options[1] if options[1]
140
+ super(options[0])
141
+ end
142
+ end
143
+
144
+ class Collection < Array
145
+ attr_reader :request
146
+
147
+ def initialize(*options)
148
+ @request = options[1] if options[1]
149
+ super(options[0])
150
+ end
151
+
152
+ def headers
153
+ @headers ||= @request.headers
154
+ end
155
+
156
+ def pagination
157
+ @pagination ||= Hashie::Mash.new(
158
+ next_page: headers['x-next-page'].to_i,
159
+ offset: headers['x-offset'].to_i,
160
+ page: headers['x-page'].to_i,
161
+ per_page: headers['x-per-page'].to_i,
162
+ prev_page: headers['prev_page'].to_i,
163
+ total: headers['x-total'].to_i,
164
+ total_pages: headers['x-total-pages'].to_i
165
+ )
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,3 @@
1
+ class AbstractApiWrapper
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,113 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'webmock/rspec'
3
+ require 'abstract_api_wrapper'
4
+
5
+ include WebMock::API
6
+ WebMock.enable!
7
+
8
+ TEST_OPTIONS = {
9
+ base_url: 'https://mysuperapi.com/api',
10
+ apiver: 'v3',
11
+ access_token: 'mysupersecrettoken'
12
+ }
13
+
14
+ describe 'AbstractApiWrapper' do
15
+ describe 'Client' do
16
+ it 'should create a Client with the given options' do
17
+ client = AbstractApiWrapper::Client.new(TEST_OPTIONS)
18
+ expect(client.access_token).to eq('mysupersecrettoken')
19
+ expect(client.base_url).to eq('https://mysuperapi.com/api')
20
+ expect(client.apiver).to eq('v3')
21
+ end
22
+ end
23
+
24
+ describe 'Request' do
25
+ before(:each) do
26
+ @client = AbstractApiWrapper::Client.new(TEST_OPTIONS)
27
+ end
28
+
29
+ it 'should return a Request instance' do
30
+ request = @client.users.all
31
+ expect(request.client).to be_kind_of(AbstractApiWrapper::Request)
32
+ end
33
+
34
+ it 'should return the same Request instance' do
35
+ user = @client.users.find(1)
36
+ project = user.projects.find(1)
37
+
38
+ expect(user).to be_kind_of(AbstractApiWrapper::Request)
39
+ expect(project).to be_kind_of(AbstractApiWrapper::Request)
40
+
41
+ expect(user.object_id).to eq(project.object_id)
42
+ end
43
+
44
+ it 'should create a path as array' do
45
+ request = @client.users.find(1)
46
+ expect(request.path).to be_kind_of(Array)
47
+ expect(request.path.last).to eq('1')
48
+ expect(request.path).to eq(['users', '1'])
49
+ end
50
+
51
+ it 'should set the filters' do
52
+ request = @client.users.all(active: true)
53
+ expect(request.filters).to be_kind_of(Hashie::Mash)
54
+ expect(request.filters).to eq({ 'active' => true })
55
+ end
56
+
57
+ it 'should set the path chain' do
58
+ request = @client.users.find(1).projects.all(active: true)
59
+ expect(request.chain).to be_kind_of(Array)
60
+ expect(request.chain).to eq(['users', 'find', 'projects', 'all'])
61
+ end
62
+ end
63
+
64
+ describe 'Response' do
65
+ before(:each) do
66
+ @client = AbstractApiWrapper::Client.new(TEST_OPTIONS)
67
+
68
+ stub_request(:get, TEST_OPTIONS[:base_url])
69
+ .to_return(:status => 200, :body => '[]')
70
+
71
+ @request = Faraday.get(TEST_OPTIONS[:base_url])
72
+ end
73
+
74
+ it 'should return a Response instance' do
75
+ response = AbstractApiWrapper::Response.new(@request)
76
+ expect(response).to be_kind_of(AbstractApiWrapper::Response)
77
+ end
78
+
79
+ it 'should parse the response body as JSON' do
80
+ response = AbstractApiWrapper::Response.new(@request)
81
+ expect(response.body).to be_kind_of(AbstractApiWrapper::Response::Collection)
82
+ end
83
+
84
+ describe 'Resource' do
85
+ before(:each) do
86
+ @item = { id: 1, full_name: 'Jon Snow', email: 'jon@winterfell.com' }
87
+ end
88
+
89
+ it 'should create a new Resource from a Hash' do
90
+ resource = AbstractApiWrapper::Response::Resource.new(@item)
91
+
92
+ expect(resource).to be_kind_of(Hashie::Mash)
93
+ expect(resource.id).to eq(1)
94
+ expect(resource.full_name).to eq('Jon Snow')
95
+ expect(resource.email).to eq('jon@winterfell.com')
96
+ expect(resource.request).to be_nil
97
+ end
98
+
99
+ it 'should assign the Request object from params' do
100
+ request = Faraday.get(TEST_OPTIONS[:base_url])
101
+ resource = AbstractApiWrapper::Response::Resource.new(@item, request)
102
+
103
+ expect(resource).to be_kind_of(Hashie::Mash)
104
+ expect(resource.request).to be_kind_of(Faraday::Response)
105
+ end
106
+ end
107
+
108
+ # TODO: Collection tests
109
+ # describe 'Collection' do
110
+ # end
111
+ end
112
+
113
+ end
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: abstract_api_wrapper
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Juan Puelpan
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-12-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json
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: faraday
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.10'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.10'
41
+ - !ruby/object:Gem::Dependency
42
+ name: hashie
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.4'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.4'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.11'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.11'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.5'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.5'
97
+ - !ruby/object:Gem::Dependency
98
+ name: webmock
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.24'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.24'
111
+ description: An abstract REST API wrapper based on missing_method magic
112
+ email:
113
+ - juan@octopull.us
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".rspec"
120
+ - Gemfile
121
+ - README.md
122
+ - Rakefile
123
+ - abstract_api_wrapper.gemspec
124
+ - bin/console
125
+ - bin/setup
126
+ - lib/abstract_api_wrapper.rb
127
+ - lib/abstract_api_wrapper/version.rb
128
+ - spec/abstract_api_wrapper_spec.rb
129
+ homepage: https://github.com/octopull/abstract_api_wrapper
130
+ licenses: []
131
+ metadata: {}
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirements: []
147
+ rubyforge_project:
148
+ rubygems_version: 2.5.1
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: An abstract REST API wrapper
152
+ test_files: []
153
+ has_rdoc: