jeckle 0.0.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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