jeckle 0.0.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2874609cdcb236683601a9e04386dd1af3e6b3a3
4
- data.tar.gz: fb456e8f4ff54cd39541841f816ee92721c92f05
3
+ metadata.gz: be467f303f461477c44a2ff12a334918e42cf833
4
+ data.tar.gz: 73c1eca8358f96530eabf65c5398898c3f516e18
5
5
  SHA512:
6
- metadata.gz: ac05f0824c301b8d843d5430084f5edec19d24143bff31cfacb96764f19702b6cc9307e92c6907f522acd47406686f82e399c556e01881033e17a4e82999a498
7
- data.tar.gz: 46639dc59b51171ef4a8fe63aa8c91b0d659a8d10f219b4e769ce42e9445118de076eb4229fb1ab1cdc5823497f3200f9e17657bcac0efe748542209a36d0249
6
+ metadata.gz: 69e64c0210fde8e8b577ad46b00f0bbca449fb234940bb5236f40838d7fbd9912fca85776328a9d126b1d495f736b191d04468b62a9871dd02ba8613de9d1ac2
7
+ data.tar.gz: 945e10b3420ba0b94f59358eeab47ac976bcf07eae78d1c6c699f5e634ecbfc2856b92f2c74bdef58f8c1b3c26c00280319124be0f95d631b8261f5fb1820392
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+ script:
3
+ - bundle exec rspec spec
4
+ rvm:
5
+ - rbx-2
6
+ - jruby-head
7
+ - ruby-head
8
+ - ruby
9
+ - jruby
10
+ - 2.1.0
11
+ - 2.0.0
12
+ - 1.9.3
13
+ env:
14
+ - CODECLIMATE_REPO_TOKEN=71bc27cbd607a9f50ee1d314ad3f74e7d670e42afe3e01d99d7bc1cd6b65524b
data/Gemfile CHANGED
@@ -2,3 +2,5 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in jeckle.gemspec
4
4
  gemspec
5
+
6
+ gem 'codeclimate-test-reporter', group: :test, require: nil
data/README.md CHANGED
@@ -1,9 +1,21 @@
1
1
  # Jeckle
2
2
 
3
- TODO: Write a gem description
3
+ [![Build Status](https://travis-ci.org/tomas-stefano/jeckle.svg?branch=master)](https://travis-ci.org/tomas-stefano/jeckle)
4
+ [![Code Climate](https://codeclimate.com/github/tomas-stefano/jeckle.png)](https://codeclimate.com/github/tomas-stefano/jeckle)
5
+ [![Test Coverage](https://codeclimate.com/github/tomas-stefano/jeckle/coverage.png)](https://codeclimate.com/github/tomas-stefano/jeckle)
6
+
7
+ Wrap APIs with easiness and flexibility.
4
8
 
5
9
  <img src="http://www.toonopedia.com/hekljekl.jpg" alt="Jeckle" />
6
10
 
11
+ > Heckle usually refers to Jeckle familiarly, as "chum" or "pal", while Jeckle
12
+ often calls Heckle "old chap", "old thing", "old boy" or "old featherhead",
13
+ indicating a close friendship between them.
14
+
15
+ <small>*[Extracted from Wikipedia](http://en.wikipedia.org/wiki/Heckle_and_Jeckle)*</small>
16
+
17
+ Let third party APIs be Heckle for your app's Jeckle.
18
+
7
19
  ## Installation
8
20
 
9
21
  Add this line to your application's Gemfile:
@@ -14,6 +26,47 @@ And then execute:
14
26
 
15
27
  $ bundle
16
28
 
17
- Or install it yourself as:
29
+ ### For Rails applications
30
+
31
+ We recommend to create a initializer:
32
+
33
+ ```ruby
34
+ # config/initializers/jeckle.rb
35
+
36
+ Jeckle.configure do |config|
37
+ config.register :some_service do |api|
38
+ api.base_uri = 'http://api.someservice.com'
39
+ api.headers = {
40
+ 'Accept' => 'application/json'
41
+ }
42
+ api.namespaces = { prefix: 'api', version: 'v1' }
43
+ api.logger = Rails.logger
44
+ api.read_timeout = 5
45
+ end
46
+ end
47
+ ```
48
+
49
+ And then put your API stuff scoped inside a `services` folder:
50
+
51
+ ```ruby
52
+ # app/services/some_service/models/my_resource.rb
53
+
54
+ module SomeService
55
+ module Models
56
+ class MyResource
57
+ include Jeckle::Resource
58
+
59
+ default_api :some_service
60
+
61
+ attribute :id
62
+ end
63
+ end
64
+ end
65
+ ```
66
+
67
+ ## Roadmap
18
68
 
19
- $ gem install jeckle
69
+ - Faraday middleware abstraction
70
+ - Per action API
71
+ - Comprehensive restful actions
72
+ - Testability
data/Rakefile CHANGED
@@ -1 +1 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,29 @@
1
+ require 'jeckle'
2
+
3
+ Jeckle.configure do |config|
4
+ config.register :dribbble do |api|
5
+ api.base_uri = 'http://api.dribbble.com'
6
+ end
7
+ end
8
+
9
+ class Shot
10
+ include Jeckle::Resource
11
+
12
+ default_api :dribbble
13
+
14
+ attribute :id, Integer
15
+ attribute :name, String
16
+ attribute :url, String
17
+ attribute :image_url, String
18
+ end
19
+
20
+ shot = Shot.find 1600459
21
+
22
+ shot.id
23
+ # => 1600459
24
+
25
+ shot.name
26
+ # => Daryl Heckle And Jeckle Oates
27
+
28
+ shot.image_url
29
+ # => https://d13yacurqjgara.cloudfront.net/users/85699/screenshots/1600459/daryl_heckle_and_jeckle_oates-dribble.jpg
data/jeckle.gemspec CHANGED
@@ -1,29 +1,39 @@
1
- # coding: utf-8
2
1
  lib = File.expand_path('../lib', __FILE__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
4
  require 'jeckle/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "jeckle"
7
+ spec.name = 'jeckle'
8
8
  spec.version = Jeckle::VERSION
9
- spec.authors = ["Tomas D'Stefano"]
10
- spec.email = ["tomas_stefano@successoft.com"]
11
- spec.description = %q{Simple module for build client apis}
12
- spec.summary = %q{Simple module for build client apis}
13
- spec.homepage = "https://github.com/tomas-stefano/jeckle"
14
- spec.license = "MIT"
9
+ spec.authors = ['Tomas D\'Stefano', 'Brenno Costa']
10
+ spec.email = ['tomas_stefano@successoft.com', 'brennolncosta@gmail.com']
11
+ spec.description = %q{Simple module for building client APIs}
12
+ spec.summary = %q{Simple module for building client APIs}
13
+ spec.homepage = 'https://github.com/tomas-stefano/jeckle'
14
+ spec.license = 'MIT'
15
15
 
16
16
  spec.files = `git ls-files`.split($/)
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
19
+ spec.require_paths = ['lib']
20
20
 
21
- spec.add_dependency 'httparty'
22
- spec.add_dependency 'virtus'
23
21
  spec.add_dependency 'activemodel', '>= 4.0'
24
- spec.add_dependency 'activesupport', '>= 4.0'
22
+ spec.add_dependency 'faraday', '~> 0.9'
23
+ spec.add_dependency 'faraday_middleware'
24
+ spec.add_dependency 'virtus', '~> 1.0'
25
25
 
26
- spec.add_development_dependency 'bundler', '~> 1.3'
26
+ spec.add_development_dependency 'bundler', '~> 1.6'
27
27
  spec.add_development_dependency 'rake'
28
- spec.add_development_dependency 'rspec'
28
+ spec.add_development_dependency 'rspec', '~> 3.1'
29
+
30
+ if RUBY_ENGINE == 'rbx'
31
+ spec.add_development_dependency 'rubinius-compiler'
32
+ spec.add_development_dependency 'rubinius-debugger'
33
+ elsif RUBY_ENGINE == 'jruby'
34
+ spec.add_development_dependency 'pry'
35
+ else
36
+ spec.add_development_dependency 'pry-nav' if RUBY_VERSION < '2.0.0'
37
+ spec.add_development_dependency 'pry-byebug' if RUBY_VERSION >= '2.0.0'
38
+ end
29
39
  end
data/lib/jeckle/api.rb ADDED
@@ -0,0 +1,55 @@
1
+ module Jeckle
2
+ class API
3
+ attr_accessor :logger
4
+ attr_writer :base_uri, :namespaces, :params, :headers, :open_timeout, :read_timeout
5
+ attr_reader :basic_auth, :request_timeout
6
+
7
+ def connection
8
+ @connection ||= Faraday.new(url: base_uri, request: timeout).tap do |conn|
9
+ conn.headers = headers
10
+ conn.params = params
11
+ conn.response :logger, logger
12
+
13
+ conn.basic_auth basic_auth[:username], basic_auth[:password] if basic_auth
14
+ conn.instance_exec &@middlewares_block if @middlewares_block
15
+ end
16
+ end
17
+
18
+ def basic_auth=(credential_params)
19
+ [:username, :password].all? do |key|
20
+ credential_params.has_key? key
21
+ end or raise Jeckle::NoUsernameOrPasswordError, credential_params
22
+
23
+ @basic_auth = credential_params
24
+ end
25
+
26
+ def base_uri
27
+ [@base_uri, *namespaces.values].join '/'
28
+ end
29
+
30
+ def params
31
+ @params || {}
32
+ end
33
+
34
+ def headers
35
+ @headers || {}
36
+ end
37
+
38
+ def namespaces
39
+ @namespaces || {}
40
+ end
41
+
42
+ def middlewares(&block)
43
+ raise Jeckle::ArgumentError, 'A block is required when configuring API middlewares' unless block_given?
44
+
45
+ @middlewares_block = block
46
+ end
47
+
48
+ def timeout
49
+ {}.tap do |t|
50
+ t[:open_timeout] = @open_timeout if @open_timeout
51
+ t[:timeout] = @read_timeout if @read_timeout
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,29 @@
1
+ module Jeckle
2
+ class ArgumentError < ::ArgumentError; end
3
+
4
+ class NoSuchAPIError < ArgumentError
5
+ def initialize(api)
6
+ message = %{The API name '#{api}' doesn't exist in Jeckle definitions.
7
+
8
+ Heckle: - Hey chum, what we can do now?
9
+ Jeckle: - Old chap, you need to put the right API name!
10
+ Heckle: - Hey pal, tell me the APIs then!
11
+ Jeckle: - Deal the trays, old thing: #{Jeckle::Setup.registered_apis.keys}.
12
+ }
13
+
14
+ super message
15
+ end
16
+ end
17
+
18
+ class NoUsernameOrPasswordError < ArgumentError
19
+ def initialize(credentials)
20
+ message = %{No such keys "username" and "password" on `basic_auth` definition"
21
+
22
+ Heckle: - Hey chum, what we can do now?
23
+ Jeckle: - Old chap, you need to define a username and a password for basic auth!
24
+ }
25
+
26
+ super message
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,8 @@
1
+ module Jeckle
2
+ module Model
3
+ def self.included(base)
4
+ base.send :include, ActiveModel::Validations
5
+ base.send :include, Virtus.model
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,33 @@
1
+ module Jeckle
2
+ class Request
3
+ attr_reader :api, :body, :headers, :method, :params, :response, :endpoint
4
+
5
+ def initialize(api, endpoint, options = {})
6
+ @api = api
7
+
8
+ @method = options.delete(:method) || :get
9
+ @body = options.delete(:body) if %w(post put).include?(method.to_s)
10
+ @headers = options.delete(:headers)
11
+
12
+ @endpoint = endpoint
13
+ @params = options
14
+
15
+ @response = perform_api_request
16
+ end
17
+
18
+ def self.run(*args)
19
+ new *args
20
+ end
21
+
22
+ private
23
+
24
+ def perform_api_request
25
+ api.connection.public_send method do |api_request|
26
+ api_request.url endpoint
27
+ api_request.params = params
28
+ api_request.body = body
29
+ api_request.headers = api_request.headers.merge(headers) if headers
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,52 +1,49 @@
1
1
  module Jeckle
2
2
  module Resource
3
- extend ActiveSupport::Concern
3
+ def self.included(base)
4
+ base.send :include, Jeckle::Model
5
+ base.send :include, ActiveModel::Naming
4
6
 
5
- included do
6
- include HTTParty
7
- include Virtus.model
8
- include ActiveModel::Validations
9
-
10
- attribute :response
7
+ base.send :extend, Jeckle::Resource::ClassMethods
11
8
  end
12
9
 
13
10
  module ClassMethods
14
- def resource_name
15
- self.class.name.underscore
11
+ def inherited(base)
12
+ base.class_eval do
13
+ @api_mapping = superclass.api_mapping.dup
14
+ end
16
15
  end
17
16
 
18
- def find(id)
19
- get(resource_action)
17
+ def resource_name
18
+ model_name.element.pluralize
20
19
  end
21
20
 
22
- def resource_action(id)
23
- "#{resource_name}/#{id}"
21
+ def default_api(registered_api_name)
22
+ api_mapping[:default_api] = Jeckle::Setup.registered_apis.fetch(registered_api_name)
23
+ rescue KeyError => e
24
+ raise Jeckle::NoSuchAPIError, registered_api_name
24
25
  end
25
- end
26
-
27
- def save
28
- request(:post)
29
- end
30
26
 
31
- def update
32
- request(:put)
33
- end
27
+ def api_mapping
28
+ @api_mapping ||= {}
29
+ end
34
30
 
35
- def headers
36
- {
37
- 'Content-Type' => 'application/json; charset="UTF-8"',
38
- 'Accept-Encoding' => 'application/json'
39
- }
40
- end
31
+ def find(id)
32
+ endpoint = "#{resource_name}/#{id}"
33
+ attributes = run_request(endpoint).response.body
41
34
 
42
- private
35
+ new attributes
36
+ end
43
37
 
44
- def request(http_method)
45
- return false if invalid?
38
+ def search(params = {})
39
+ collection = run_request(resource_name, params).response.body || []
46
40
 
47
- @response = self.class.send(http_method, resource_name, headers: headers, body: params)
41
+ collection.collect { |attrs| new attrs }
42
+ end
48
43
 
49
- @response.success?
44
+ def run_request(endpoint, options = {})
45
+ Jeckle::Request.run api_mapping[:default_api], endpoint, options
46
+ end
50
47
  end
51
48
  end
52
- end
49
+ end
@@ -0,0 +1,28 @@
1
+ module Jeckle
2
+ class Setup
3
+ # Register apis, providing all the configurations to it.
4
+ #
5
+ # @example
6
+ #
7
+ # Jeckle.configure do |config|
8
+ # config.register :my_api_restful do |api|
9
+ # api.basic_auth = { username: 'chucknorris', password: 'nowThatYouKnowYouMustDie' }
10
+ # api.namespaces = { version: 'v2' }
11
+ # api.base_uri = 'myapi.com'
12
+ # api.headers = { 'Content-Type' => 'application/whatever.complex.header.v2+json;charset=UTF-8' }
13
+ # api.logger = Rails.logger # or any other logger
14
+ # end
15
+ # end
16
+ #
17
+ def self.register(name)
18
+ Jeckle::API.new.tap do |user_api|
19
+ yield user_api
20
+ registered_apis[name] = user_api
21
+ end
22
+ end
23
+
24
+ def self.registered_apis
25
+ @registered_apis ||= {}
26
+ end
27
+ end
28
+ end
@@ -1,3 +1,3 @@
1
1
  module Jeckle
2
- VERSION = '0.0.1'
2
+ VERSION = '0.2.0'
3
3
  end
data/lib/jeckle.rb CHANGED
@@ -1,11 +1,19 @@
1
- require 'jeckle/version'
2
- require 'active_support/dependencies/autoload'
3
- require 'active_support/concern'
4
- require 'virtus'
5
1
  require 'active_model'
2
+ require 'faraday'
3
+ require 'faraday_middleware'
4
+ require 'virtus'
6
5
 
7
- module Jeckle
8
- extend ActiveSupport::Autoload
6
+ require 'jeckle/version'
7
+
8
+ %w(setup api model request resource errors).each do |file_name|
9
+ require "jeckle/#{file_name}"
10
+ end
9
11
 
10
- autoload :Resource
12
+ module Jeckle
13
+ # Configure APIs to be used on Jeckle::Resources.
14
+ # See Jeckle::Setup for more information.
15
+ #
16
+ def self.configure
17
+ yield Jeckle::Setup
18
+ end
11
19
  end
@@ -0,0 +1,31 @@
1
+ Jeckle::Setup.register(:my_super_api) do |api|
2
+ api.base_uri = 'http://my-super-api.com.br'
3
+ api.headers = { 'Content-Type' => 'application/json' }
4
+ api.logger = Logger.new(STDOUT)
5
+ api.basic_auth = { username: 'steven_seagal', password: 'youAlwaysLose' }
6
+ api.namespaces = { prefix: 'api', version: 'v1' }
7
+ api.params = { hello: 'world' }
8
+ api.open_timeout = 2
9
+ api.read_timeout = 5
10
+
11
+ api.middlewares do
12
+ request :json
13
+ response :json
14
+ response :raise_error
15
+ end
16
+ end
17
+
18
+ Jeckle::Setup.register(:another_api) do |api|
19
+ api.base_uri = 'http://another-api.com.br'
20
+ api.headers = { 'Content-Type' => 'application/json' }
21
+ api.logger = Logger.new(STDOUT)
22
+ api.basic_auth = { username: 'heisenberg', password: 'metaAfetaAMina' }
23
+ api.namespaces = { prefix: 'api', version: 'v5' }
24
+ api.params = { hi: 'there' }
25
+
26
+ api.middlewares do
27
+ request :json
28
+ response :json
29
+ response :raise_error
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ class FakeModel
2
+ include Jeckle::Model
3
+ end
@@ -0,0 +1,7 @@
1
+ class FakeResource
2
+ include Jeckle::Resource
3
+
4
+ default_api :my_super_api
5
+
6
+ attribute :id, Integer
7
+ end
@@ -0,0 +1,180 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Jeckle::API do
4
+ subject(:jeckle_api) { Jeckle::Setup.registered_apis[:my_super_api] }
5
+
6
+ describe '#connection' do
7
+ before { jeckle_api.instance_variable_set(:@connection, nil) }
8
+
9
+ let(:fake_faraday_connection) { Faraday::Connection.new }
10
+
11
+ it 'returns a Faraday connection' do
12
+ expect(jeckle_api.connection).to be_kind_of Faraday::Connection
13
+ end
14
+
15
+ it 'caches the connection' do
16
+ expect(Faraday).to receive(:new).once.and_return(fake_faraday_connection)
17
+ .with(url: jeckle_api.base_uri, request: jeckle_api.timeout)
18
+
19
+ expect(fake_faraday_connection).to receive(:tap).once.and_call_original
20
+
21
+ 10.times { jeckle_api.connection }
22
+ end
23
+
24
+ it 'assigns api_headers' do
25
+ expect(jeckle_api.connection.headers).to include 'Content-Type' => 'application/json'
26
+ end
27
+
28
+ it 'assigns basic auth headers' do
29
+ expect(jeckle_api.connection.headers.keys).to include 'Authorization'
30
+ end
31
+
32
+ it 'assigns params there will be used on all requests' do
33
+ expect(jeckle_api.connection.params).to eq 'hello' => 'world'
34
+ end
35
+
36
+ it 'assigns timeout options' do
37
+ expect(jeckle_api.connection.options.open_timeout).to eq 2
38
+ expect(jeckle_api.connection.options.timeout).to eq 5
39
+ end
40
+
41
+ context 'when middlewares_block is set' do
42
+ before do
43
+ jeckle_api.middlewares do
44
+ request :retry, max: 2, interval: 0.05
45
+ request :instrumentation
46
+ end
47
+ end
48
+
49
+ it 'adds middlewares on connection middleware stack' do
50
+ expect(jeckle_api.connection.builder.handlers.last(2)).to eq [
51
+ Faraday::Request::Retry,
52
+ Faraday::Request::Instrumentation
53
+ ]
54
+ end
55
+ end
56
+ end
57
+
58
+ describe '#base_uri' do
59
+ context 'when namespaces are defined' do
60
+ it 'appends namespaces' do
61
+ expect(jeckle_api.base_uri).to eq 'http://my-super-api.com.br/api/v1'
62
+ end
63
+ end
64
+
65
+ context 'when no namespaces are defined' do
66
+ subject(:jeckle_api) { described_class.new }
67
+
68
+ before { jeckle_api.base_uri = 'http://my-super-api.com.br' }
69
+
70
+ it 'keeps base_uri without adding namespaces or futher slashes' do
71
+ expect(jeckle_api.base_uri).to eq 'http://my-super-api.com.br'
72
+ end
73
+ end
74
+ end
75
+
76
+ describe '#basic_auth=' do
77
+ subject(:jeckle_api) { described_class.new }
78
+
79
+ context 'when there is the required credentials' do
80
+ let(:credentials) { { username: 'sly', password: 'IAmTheLaw'} }
81
+
82
+ before { jeckle_api.basic_auth = credentials }
83
+
84
+ it 'assigns creditals hash' do
85
+ expect(jeckle_api.basic_auth).to eq credentials
86
+ end
87
+ end
88
+
89
+ context 'when required creadentials is missing' do
90
+ let(:credentials) { {} }
91
+
92
+ it 'raises a argument error NoUsernameOrPasswordError' do
93
+ expect { jeckle_api.basic_auth = credentials }.to raise_error Jeckle::NoUsernameOrPasswordError
94
+ end
95
+ end
96
+ end
97
+
98
+ describe '#params' do
99
+ context 'when there are params' do
100
+ it 'assigns params hash' do
101
+ expect(jeckle_api.params).to eq hello: 'world'
102
+ end
103
+ end
104
+
105
+ context 'when there are no params' do
106
+ subject(:jeckle_api) { described_class.new }
107
+
108
+ it 'assigns an empty hash' do
109
+ expect(jeckle_api.params).to eq({})
110
+ end
111
+ end
112
+ end
113
+
114
+ describe '#headers' do
115
+ context 'when there are headers' do
116
+ it 'assigns headers hash' do
117
+ expect(jeckle_api.headers).to eq 'Content-Type' => 'application/json'
118
+ end
119
+ end
120
+
121
+ context 'when there are no headers' do
122
+ subject(:jeckle_api) { described_class.new }
123
+
124
+ it 'assigns an empty hash' do
125
+ expect(jeckle_api.headers).to eq({})
126
+ end
127
+ end
128
+ end
129
+
130
+ describe '#middlewares' do
131
+ context 'when a block is given' do
132
+ it 'assigns middleware_block' do
133
+ jeckle_api.middlewares { 123 }
134
+
135
+ expect(jeckle_api.instance_variable_get('@middlewares_block')).not_to be_nil
136
+ end
137
+ end
138
+
139
+ context 'when no block is given' do
140
+ it 'raises ArgumentError' do
141
+ expect {
142
+ jeckle_api.middlewares
143
+ }.to raise_error Jeckle::ArgumentError, /A block is required when configuring API middlewares/
144
+ end
145
+ end
146
+ end
147
+
148
+ describe '#timeout' do
149
+ let(:timeout) { nil }
150
+ let(:open_timeout) { nil }
151
+
152
+ before do
153
+ jeckle_api.open_timeout = open_timeout
154
+ jeckle_api.read_timeout = timeout
155
+ end
156
+
157
+ context 'when no timeout is defined' do
158
+ it 'returns empty hash' do
159
+ expect(jeckle_api.timeout).to eq({})
160
+ end
161
+ end
162
+
163
+ context 'when a read_timeout is defined' do
164
+ let(:timeout) { 5 }
165
+
166
+ it 'returns a hash with assgned option' do
167
+ expect(jeckle_api.timeout).to eq timeout: timeout
168
+ end
169
+ end
170
+
171
+ context 'when two timeouts are defined for both open and read' do
172
+ let(:timeout) { 5 }
173
+ let(:open_timeout) { 2 }
174
+
175
+ it 'returns a hash correctly assigned open and read timeouts' do
176
+ expect(jeckle_api.timeout).to eq open_timeout: open_timeout, timeout: timeout
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Jeckle::Model do
4
+ it 'includes active_model/validations' do
5
+ expect(FakeModel.ancestors).to include ActiveModel::Validations
6
+ end
7
+
8
+ it 'includes virtus' do
9
+ expect(FakeModel.ancestors).to include Virtus::Model::Core
10
+ end
11
+ end
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Jeckle::Request do
4
+ let(:api) { Jeckle::Setup.registered_apis[:my_super_api] }
5
+
6
+ before do
7
+ # Manipulate Faraday internals to avoid real requests
8
+ class FakeRackBuilder; def build_response(conn, req); end; end
9
+ fake_builder = FakeRackBuilder.new
10
+
11
+ allow(api.connection).to receive(:builder).and_return(fake_builder)
12
+ end
13
+
14
+ describe '.run' do
15
+ before { allow(api.connection).to receive(http_method).and_call_original }
16
+
17
+ context 'GET' do
18
+ let(:options) { {} }
19
+ let(:endpoint) { 'fake_resource/4' }
20
+ let(:http_method) { :get }
21
+
22
+ it_behaves_like Jeckle::Request
23
+
24
+ it 'calls GET request on API\'s connection' do
25
+ expect(api.connection).to receive(http_method).and_call_original
26
+
27
+ described_class.run api, endpoint, options
28
+ end
29
+ end
30
+
31
+ context 'POST' do
32
+ let(:endpoint) { 'fake_resources' }
33
+ let(:options) { { body: { value: 1000 }, method: http_method } }
34
+ let(:http_method) { :post }
35
+
36
+ it_behaves_like Jeckle::Request
37
+
38
+ it 'calls POST request on API\'s connection' do
39
+ expect(api.connection).to receive(:post).and_call_original
40
+
41
+ described_class.run api, endpoint, options
42
+ end
43
+ end
44
+
45
+ context 'PUT' do
46
+ let(:endpoint) { 'fake_resources' }
47
+ let(:options) { { body: { id: 1001, value: 1000 }, method: http_method } }
48
+ let(:http_method) { :put }
49
+
50
+ it_behaves_like Jeckle::Request
51
+
52
+ it 'calls PUT request on API\'s connection' do
53
+ expect(api.connection).to receive(:put).and_call_original
54
+
55
+ described_class.run api, endpoint, options
56
+ end
57
+ end
58
+
59
+ context 'DELETE' do
60
+ let(:options) { { method: http_method } }
61
+ let(:endpoint) { 'fake_resource/4' }
62
+ let(:http_method) { :delete }
63
+
64
+ it_behaves_like Jeckle::Request
65
+
66
+ it 'calls DELETE request on API\'s connection' do
67
+ expect(api.connection).to receive(:delete).and_call_original
68
+
69
+ described_class.run api, endpoint, options
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,126 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Jeckle::Resource do
4
+ subject(:fake_resource) { FakeResource.new }
5
+
6
+ let(:api) { FakeResource.api_mapping[:default_api] }
7
+
8
+ it 'includes jeckle/model' do
9
+ expect(FakeResource.ancestors).to include Jeckle::Model
10
+ end
11
+
12
+ it 'includes active_model/naming' do
13
+ expect(FakeResource.ancestors).to include ActiveModel::Naming
14
+ end
15
+
16
+ describe '.resource_name' do
17
+ it 'returns resource name based on class name' do
18
+ expect(FakeResource.resource_name).to eq 'fake_resources'
19
+ end
20
+
21
+ context 'when resource class is namespaced' do
22
+ before do
23
+ MySuperApi = Module.new
24
+ MySuperApi::FakeResource = Class.new(::FakeResource)
25
+ end
26
+
27
+ it 'ignores namespace' do
28
+ expect(MySuperApi::FakeResource.resource_name).to eq 'fake_resources'
29
+ end
30
+ end
31
+ end
32
+
33
+ describe '.api_mapping' do
34
+ it 'returns a hash containing default api' do
35
+ expect(FakeResource.api_mapping).to match(
36
+ default_api: an_instance_of(Jeckle::API)
37
+ )
38
+ end
39
+
40
+ context 'when resource is inherited' do
41
+ let(:inherited_class) { Class.new(FakeResource) }
42
+
43
+ it "contains the parent's api_mapping" do
44
+ expect(inherited_class.api_mapping).to eq FakeResource.api_mapping
45
+ end
46
+
47
+ context 'when api_mapping is changed' do
48
+ it "does not affect the parent" do
49
+ inherited_class.default_api :another_api
50
+
51
+ expect(FakeResource.api_mapping).not_to eq inherited_class.api_mapping
52
+ expect(FakeResource.api_mapping[:default_api]).to eq Jeckle::Setup.registered_apis[:my_super_api]
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ describe '.default_api' do
59
+ context 'when defining a registered API via Jeckle::Setup' do
60
+ it 'returns the assigned API' do
61
+ expect(FakeResource.default_api :my_super_api).to be_kind_of Jeckle::API
62
+ end
63
+
64
+ it 'assigns API do api_mapping' do
65
+ expect(FakeResource.api_mapping).to have_key :default_api
66
+ end
67
+ end
68
+
69
+ context 'when defining an inexistent API' do
70
+ it 'raises NoSuchAPIError' do
71
+ expect { FakeResource.default_api :unknown_api }.to raise_error Jeckle::NoSuchAPIError
72
+ end
73
+ end
74
+ end
75
+
76
+ describe '.find' do
77
+ let(:fake_request) { OpenStruct.new response: OpenStruct.new(body: { id: 1001 }) }
78
+
79
+ it 'calls default API connection with GET' do
80
+ expect(Jeckle::Request).to receive(:run)
81
+ .with(api, 'fake_resources/1001', {}).and_return(fake_request)
82
+
83
+ FakeResource.find 1001
84
+ end
85
+
86
+ it 'returns an instance of resource' do
87
+ allow(Jeckle::Request).to receive(:run).and_return(fake_request)
88
+
89
+ expect(FakeResource.find 1001).to be_an_instance_of(FakeResource)
90
+ end
91
+ end
92
+
93
+ describe '.search' do
94
+ let(:query) { { name: 'cocada' } }
95
+
96
+ context 'when there are results' do
97
+ let(:fake_request) { OpenStruct.new response: OpenStruct.new(body: [{ id: 1001 }, { id: 1002 }]) }
98
+
99
+ it 'calls default API connection with GET and search params' do
100
+ expect(Jeckle::Request).to receive(:run)
101
+ .with(api, 'fake_resources', query).and_return(fake_request)
102
+
103
+ FakeResource.search query
104
+ end
105
+
106
+ it 'returns an Array of resources' do
107
+ allow(Jeckle::Request).to receive(:run).and_return(fake_request)
108
+
109
+ expect(FakeResource.search query).to match [
110
+ an_instance_of(FakeResource),
111
+ an_instance_of(FakeResource)
112
+ ]
113
+ end
114
+ end
115
+
116
+ context 'when there are no results' do
117
+ let(:fake_request) { OpenStruct.new response: OpenStruct.new(body: nil) }
118
+
119
+ it 'returns an empty Array' do
120
+ allow(Jeckle::Request).to receive(:run).and_return(fake_request)
121
+
122
+ expect(FakeResource.search query).to match []
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Jeckle::Setup do
4
+ describe '.register' do
5
+ context 'when block is given' do
6
+ let(:registered_apis) { Jeckle::Setup.registered_apis }
7
+
8
+ it 'returns a new registered API' do
9
+ expect(registered_apis).to have_key(:my_super_api)
10
+ end
11
+
12
+ describe 'base uri' do
13
+ it 'assigns to the api instance' do
14
+ expect(registered_apis[:my_super_api].base_uri).to eq 'http://my-super-api.com.br/api/v1'
15
+ end
16
+ end
17
+
18
+ describe 'headers' do
19
+ it 'assigns to the api instance' do
20
+ expect(registered_apis[:my_super_api].headers).to eq 'Content-Type' => 'application/json'
21
+ end
22
+ end
23
+
24
+ describe 'logger' do
25
+ it 'assigns to the api instance' do
26
+ expect(registered_apis[:my_super_api].logger).to be_kind_of Logger
27
+ end
28
+ end
29
+
30
+ describe 'basic auth' do
31
+ it 'assigns to the api instance' do
32
+ expect(registered_apis[:my_super_api].basic_auth).to eq username: 'steven_seagal', password: 'youAlwaysLose'
33
+ end
34
+ end
35
+
36
+ describe 'query string' do
37
+ it 'assigns to the api instance' do
38
+ expect(registered_apis[:my_super_api].params).to eq(hello: 'world')
39
+ end
40
+ end
41
+
42
+ describe 'namespaces' do
43
+ it 'assigns to the api instance' do
44
+ expect(registered_apis[:my_super_api].namespaces).to eq prefix: 'api', version: 'v1'
45
+ end
46
+ end
47
+ end
48
+
49
+ context 'when block is not given' do
50
+ it 'raises no block given exception' do
51
+ expect {
52
+ Jeckle::Setup.register(:my_api)
53
+ }.to raise_exception(LocalJumpError)
54
+ end
55
+ end
56
+ end
57
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,17 @@
1
+ if ENV['CODECLIMATE_REPO_TOKEN']
2
+ require 'codeclimate-test-reporter'
3
+ CodeClimate::TestReporter.start
4
+ end
5
+
1
6
  require 'bundler/setup'
2
- Bundler.require
3
- require 'jeckle'
7
+ require 'jeckle'
8
+
9
+ Dir['spec/support/**/*.rb'].each { |file| require File.expand_path(file) }
10
+
11
+ require 'fixtures/jeckle_config'
12
+
13
+ Dir['spec/fixtures/**/*.rb'].each { |file| require File.expand_path(file) }
14
+
15
+ RSpec.configure do |config|
16
+ config.disable_monkey_patching!
17
+ end
@@ -0,0 +1,47 @@
1
+ RSpec.shared_examples_for Jeckle::Request do
2
+ let(:api_params) { api.params.stringify_keys }
3
+
4
+ before do
5
+ allow_any_instance_of(Faraday::Request).to receive(:params=).with(any_args).and_call_original
6
+ allow_any_instance_of(Faraday::Request).to receive(:headers=).with(any_args).and_call_original
7
+ end
8
+
9
+ it 'sets request URL' do
10
+ expect_any_instance_of(Faraday::Request).to receive(:url).with endpoint
11
+
12
+ described_class.run api, endpoint, options
13
+ end
14
+
15
+ it 'sets global api params defined via register block' do
16
+ expect_any_instance_of(Faraday::Request).to receive(:params=).with(api_params).
17
+ at_least(:once).and_call_original
18
+
19
+ described_class.run api, endpoint, options
20
+ end
21
+
22
+ it 'sets request params' do
23
+ expect_any_instance_of(Faraday::Request).to receive(:params=).with(options).
24
+ at_least(:once).and_call_original
25
+
26
+ described_class.run api, endpoint, options
27
+ end
28
+
29
+ it 'sets global api headers defined via register block' do
30
+ expect_any_instance_of(Faraday::Request).to receive(:headers=).with(api.connection.headers).
31
+ at_least(:once).and_call_original
32
+
33
+ described_class.run api, endpoint, options
34
+ end
35
+
36
+ context 'when has headers option' do
37
+ let(:options) { { headers: { 'Accept' => 'application/json' } } }
38
+ let(:expected_headers) { api.connection.headers.merge options[:headers] }
39
+
40
+ it 'merges request headers with global api headers' do
41
+ expect_any_instance_of(Faraday::Request).to receive(:headers=).with(expected_headers).
42
+ at_least(:once).and_call_original
43
+
44
+ described_class.run api, endpoint, options
45
+ end
46
+ end
47
+ end
metadata CHANGED
@@ -1,85 +1,86 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jeckle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tomas D'Stefano
8
+ - Brenno Costa
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2014-07-18 00:00:00.000000000 Z
12
+ date: 2014-10-09 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
- name: httparty
15
+ name: activemodel
15
16
  requirement: !ruby/object:Gem::Requirement
16
17
  requirements:
17
18
  - - '>='
18
19
  - !ruby/object:Gem::Version
19
- version: '0'
20
+ version: '4.0'
20
21
  type: :runtime
21
22
  prerelease: false
22
23
  version_requirements: !ruby/object:Gem::Requirement
23
24
  requirements:
24
25
  - - '>='
25
26
  - !ruby/object:Gem::Version
26
- version: '0'
27
+ version: '4.0'
27
28
  - !ruby/object:Gem::Dependency
28
- name: virtus
29
+ name: faraday
29
30
  requirement: !ruby/object:Gem::Requirement
30
31
  requirements:
31
- - - '>='
32
+ - - ~>
32
33
  - !ruby/object:Gem::Version
33
- version: '0'
34
+ version: '0.9'
34
35
  type: :runtime
35
36
  prerelease: false
36
37
  version_requirements: !ruby/object:Gem::Requirement
37
38
  requirements:
38
- - - '>='
39
+ - - ~>
39
40
  - !ruby/object:Gem::Version
40
- version: '0'
41
+ version: '0.9'
41
42
  - !ruby/object:Gem::Dependency
42
- name: activemodel
43
+ name: faraday_middleware
43
44
  requirement: !ruby/object:Gem::Requirement
44
45
  requirements:
45
46
  - - '>='
46
47
  - !ruby/object:Gem::Version
47
- version: '4.0'
48
+ version: '0'
48
49
  type: :runtime
49
50
  prerelease: false
50
51
  version_requirements: !ruby/object:Gem::Requirement
51
52
  requirements:
52
53
  - - '>='
53
54
  - !ruby/object:Gem::Version
54
- version: '4.0'
55
+ version: '0'
55
56
  - !ruby/object:Gem::Dependency
56
- name: activesupport
57
+ name: virtus
57
58
  requirement: !ruby/object:Gem::Requirement
58
59
  requirements:
59
- - - '>='
60
+ - - ~>
60
61
  - !ruby/object:Gem::Version
61
- version: '4.0'
62
+ version: '1.0'
62
63
  type: :runtime
63
64
  prerelease: false
64
65
  version_requirements: !ruby/object:Gem::Requirement
65
66
  requirements:
66
- - - '>='
67
+ - - ~>
67
68
  - !ruby/object:Gem::Version
68
- version: '4.0'
69
+ version: '1.0'
69
70
  - !ruby/object:Gem::Dependency
70
71
  name: bundler
71
72
  requirement: !ruby/object:Gem::Requirement
72
73
  requirements:
73
74
  - - ~>
74
75
  - !ruby/object:Gem::Version
75
- version: '1.3'
76
+ version: '1.6'
76
77
  type: :development
77
78
  prerelease: false
78
79
  version_requirements: !ruby/object:Gem::Requirement
79
80
  requirements:
80
81
  - - ~>
81
82
  - !ruby/object:Gem::Version
82
- version: '1.3'
83
+ version: '1.6'
83
84
  - !ruby/object:Gem::Dependency
84
85
  name: rake
85
86
  requirement: !ruby/object:Gem::Requirement
@@ -96,6 +97,20 @@ dependencies:
96
97
  version: '0'
97
98
  - !ruby/object:Gem::Dependency
98
99
  name: rspec
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ~>
103
+ - !ruby/object:Gem::Version
104
+ version: '3.1'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ~>
110
+ - !ruby/object:Gem::Version
111
+ version: '3.1'
112
+ - !ruby/object:Gem::Dependency
113
+ name: pry-byebug
99
114
  requirement: !ruby/object:Gem::Requirement
100
115
  requirements:
101
116
  - - '>='
@@ -108,23 +123,41 @@ dependencies:
108
123
  - - '>='
109
124
  - !ruby/object:Gem::Version
110
125
  version: '0'
111
- description: Simple module for build client apis
126
+ description: Simple module for building client APIs
112
127
  email:
113
128
  - tomas_stefano@successoft.com
129
+ - brennolncosta@gmail.com
114
130
  executables: []
115
131
  extensions: []
116
132
  extra_rdoc_files: []
117
133
  files:
118
134
  - .gitignore
135
+ - .rspec
136
+ - .travis.yml
119
137
  - Gemfile
120
138
  - LICENSE.txt
121
139
  - README.md
122
140
  - Rakefile
141
+ - examples/dribbble.rb
123
142
  - jeckle.gemspec
124
143
  - lib/jeckle.rb
144
+ - lib/jeckle/api.rb
145
+ - lib/jeckle/errors.rb
146
+ - lib/jeckle/model.rb
147
+ - lib/jeckle/request.rb
125
148
  - lib/jeckle/resource.rb
149
+ - lib/jeckle/setup.rb
126
150
  - lib/jeckle/version.rb
151
+ - spec/fixtures/jeckle_config.rb
152
+ - spec/fixtures/models/fake_model.rb
153
+ - spec/fixtures/resources/fake_resource.rb
154
+ - spec/jeckle/api_spec.rb
155
+ - spec/jeckle/model_spec.rb
156
+ - spec/jeckle/request_spec.rb
157
+ - spec/jeckle/resource_spec.rb
158
+ - spec/jeckle/setup_spec.rb
127
159
  - spec/spec_helper.rb
160
+ - spec/support/shared_examples.rb
128
161
  homepage: https://github.com/tomas-stefano/jeckle
129
162
  licenses:
130
163
  - MIT
@@ -148,6 +181,15 @@ rubyforge_project:
148
181
  rubygems_version: 2.0.5
149
182
  signing_key:
150
183
  specification_version: 4
151
- summary: Simple module for build client apis
184
+ summary: Simple module for building client APIs
152
185
  test_files:
186
+ - spec/fixtures/jeckle_config.rb
187
+ - spec/fixtures/models/fake_model.rb
188
+ - spec/fixtures/resources/fake_resource.rb
189
+ - spec/jeckle/api_spec.rb
190
+ - spec/jeckle/model_spec.rb
191
+ - spec/jeckle/request_spec.rb
192
+ - spec/jeckle/resource_spec.rb
193
+ - spec/jeckle/setup_spec.rb
153
194
  - spec/spec_helper.rb
195
+ - spec/support/shared_examples.rb