resource_kit 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6271c0e0f56abd845d9067c8bc2e55597a532fcd
4
+ data.tar.gz: 4898d65f961b2be6121c782efd92fbfd2f153660
5
+ SHA512:
6
+ metadata.gz: 1bb8f7fea234fabc6d235eb4a9d3f1829d685b72dad2fe737986fee2dd2a432f9b59d91563fbcde239dbcfe802ec2df4614543b22fd9838ab241163d49b102ea
7
+ data.tar.gz: e78e93b61f9301dea5c86db6c3aa219794ff33fab4ad97da122a393d3ee3ecde093ddc34f751c80c8fd3f79405b860a7254b8026dff4571473ee6307889b0d0f
data/.gitignore ADDED
@@ -0,0 +1,22 @@
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
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in resource_kit.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Robert Ross
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # Resource Kit
2
+
3
+ Resource Kit provides tools to aid in making API Clients. Such as URL resolving, Request / Response layer, and more.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'resource_kit'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install resource_kit
18
+
19
+ ## Usage
20
+
21
+ This library recommends using [Kartograph](http://github.com/digitaloceancloud/kartograph) for representing and deserializing response bodies.
22
+ You'll see it in the examples provided below.
23
+
24
+ ### Resource classes
25
+
26
+ Resource Kit provides a comprehensive but intuitive DSL where you describe the remote resources capabilities.
27
+ For example, where can I get a list of users? Where do I get a single user? How do I create a user?
28
+
29
+ When you're able to answer these questions, you can describe them in your resource class like this:
30
+
31
+ ```ruby
32
+ class DropletResource < ResourceKit::Resource
33
+ resources do
34
+ default_handler(422) {|response| ErrorMapping.extract_single(response.body, :read) }
35
+ default_handler(:ok, :created) {|response| DropletMapping.extract_single(response.body, :read) }
36
+
37
+ # Defining actions will create instance methods on the resource class to call them.
38
+ action :find do
39
+ verb :get # get is assumed if this is omitted
40
+ path '/droplets/:id'
41
+ handler(200) {|response| DropletMapping.extract_single(response.body, :read) }
42
+ end
43
+
44
+ action :all do
45
+ path '/droplets'
46
+ handler(200) {|body| DropletMapping.extract_collection(body, :read) }
47
+ end
48
+
49
+ action :create do
50
+ path '/droplets'
51
+ verb :post
52
+ body {|object| DropletMapping.representation_for(:create, object) } # Generate a response body from a passed object
53
+ handler(202) {|response| DropletMapping.extract_single(response.body, :read) }
54
+ end
55
+ end
56
+ end
57
+ ```
58
+
59
+ Now that we've described our resources. We can instantiate our class with a connection object. ResourceKit relies on the interface that Faraday provides. For example:
60
+
61
+ ```ruby
62
+ connection = Faraday.new(url: 'http://api.digitalocean.com') do |req|
63
+ req.adapter :net_http
64
+ end
65
+
66
+ resource = DropletResource.new(connection)
67
+ ```
68
+
69
+ Now that we've instantiated a resource with our class, we can call the actions we've defined on it.
70
+
71
+ ```
72
+ all_droplets = resource.all
73
+ single_droplet = resource.find(id: 123)
74
+ create = resource.create(Droplet.new)
75
+ ```
76
+
77
+ ### Nice to have's
78
+
79
+ Things we've thought about but just haven't implemented are:
80
+
81
+ * `action :find, 'PUT droplets/:id/restart'`
82
+ * Pagination capabilities
83
+
84
+
85
+ ## Contributing
86
+
87
+ 1. Fork it ( https://github.com/[my-github-username]/resource_kit/fork )
88
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
89
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
90
+ 4. Push to the branch (`git push origin my-new-feature`)
91
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,53 @@
1
+ require 'kartograph'
2
+
3
+ class Droplet
4
+ attr_accessor :id, :name, :region, :size, :image
5
+ end
6
+
7
+ class DropletMapping
8
+ include Kartograph::DSL
9
+
10
+ kartograph do
11
+ mapping Droplet
12
+ root_key plural: 'droplets', singular: 'droplet', scopes: [:read]
13
+
14
+ property :id, scopes: [:read]
15
+ property :name, scopes: [:read, :create]
16
+ property :size, scopes: [:read, :create]
17
+ property :image, scopes: [:read, :create]
18
+ property :region, scopes: [:read, :create]
19
+ end
20
+ end
21
+
22
+ class DropletResource < ResourceKit::Resource
23
+ resources do
24
+ action :all do
25
+ verb :get
26
+ path '/v2/droplets'
27
+ handler(200) { |response| DropletMapping.extract_collection(response.body, :read) }
28
+ end
29
+
30
+ action :find do
31
+ verb :get
32
+ path '/v2/droplets/:id'
33
+ handler(200) { |response| DropletMapping.extract_single(response.body, :read) }
34
+ end
35
+
36
+ action :create do
37
+ verb :post
38
+ path '/v2/droplets'
39
+ body {|object| DropletMapping.representation_for(:create, object) }
40
+ handler(202) { |response| DropletMapping.extract_single(response.body, :read) }
41
+ end
42
+ end
43
+ end
44
+
45
+ token = 'YOUR_ACCESS_TOKEN'
46
+ connection = Faraday.new(url: 'https://api.digitalocean.com', headers: { content_type: 'application/json', authorization: "Bearer #{token}" }) do |req|
47
+ req.adapter :net_http
48
+ end
49
+
50
+ resource = DropletResource.new(connection)
51
+
52
+ # Retrieve all droplets
53
+ puts resource.all
@@ -0,0 +1,37 @@
1
+ module ResourceKit
2
+ class Action
3
+ attr_reader :name
4
+
5
+ def initialize(name)
6
+ @name = name
7
+ end
8
+
9
+ def verb(v = nil)
10
+ @verb = v if v
11
+ @verb
12
+ end
13
+
14
+ def path(path = nil)
15
+ @path = path if path
16
+ @path
17
+ end
18
+
19
+ def handlers
20
+ @handlers ||= {}
21
+ end
22
+
23
+ def handler(*response_codes, &block)
24
+ response_codes.each do |code|
25
+ unless code.is_a?(Fixnum)
26
+ code = StatusCodeMapper.code_for(code)
27
+ end
28
+ handlers[code] = block
29
+ end
30
+ end
31
+
32
+ def body(&block)
33
+ @body_handler = block if block_given?
34
+ @body_handler
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,38 @@
1
+ module ResourceKit
2
+ class ActionInvoker
3
+ ALLOWED_VERBS = [:get, :post, :put, :delete, :head, :patch, :options]
4
+
5
+ def self.call(action, connection, *args)
6
+ raise ArgumentError, "Verb '#{action.verb}' is not allowed" unless action.verb.in?(ALLOWED_VERBS)
7
+ options = args.extract_options!
8
+ resolver = EndpointResolver.new(path: action.path)
9
+
10
+ if args.size > 0 and action.verb.in?([:post, :put, :patch])
11
+ # This request is going to have a response body. Handle it.
12
+ response = connection.send(action.verb, resolver.resolve(options)) do |request|
13
+ request.body = construct_body(args.first, action)
14
+ end
15
+ else
16
+ response = connection.send(action.verb, resolver.resolve(options))
17
+ end
18
+
19
+ handle_response(response, action)
20
+ end
21
+
22
+ def self.handle_response(response, action)
23
+ if handler = action.handlers[response.status]
24
+ handler.call(response)
25
+ else
26
+ response.body
27
+ end
28
+ end
29
+
30
+ def self.construct_body(object, action)
31
+ if action.body
32
+ action.body.call(object)
33
+ else
34
+ object.to_s
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,42 @@
1
+ require 'addressable/uri'
2
+
3
+ module ResourceKit
4
+ class EndpointResolver
5
+ attr_reader :path, :query_param_keys
6
+
7
+ def initialize(options = {})
8
+ @path = options[:path]
9
+ @query_param_keys = options[:query_param_keys] || {}
10
+ end
11
+
12
+ def resolve(values = {})
13
+ uri = Addressable::URI.new
14
+ new_path = generated_path(values)
15
+ uri.path = normalized_path_components(new_path)
16
+ uri.query = append_query_values(uri, values) unless query_param_keys.empty?
17
+
18
+ uri.to_s
19
+ end
20
+
21
+ private
22
+
23
+ def generated_path(values)
24
+ values.inject(path) do |np, (key, value)|
25
+ np.gsub(":#{key}", value.to_s)
26
+ end
27
+ end
28
+
29
+ def normalized_path_components(*components)
30
+ components.reject(&:blank?).map do |piece|
31
+ # Remove leading and trailing forward slashes
32
+ piece.gsub(/(^\/)|(\/$)/, '')
33
+ end.join('/').insert(0, '/')
34
+ end
35
+
36
+ def append_query_values(uri, values)
37
+ query_param_keys.each_with_object({}) do |key, query_values|
38
+ query_values[key] = values[key] if values.has_key?(key)
39
+ end.to_query
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,20 @@
1
+ module ResourceKit
2
+ class MethodFactory
3
+ def self.construct(object, resource_collection, invoker = ActionInvoker)
4
+ resource_collection.each do |action|
5
+ if object.method_defined?(action.name)
6
+ raise ArgumentError, "Action '#{action.name}' is already defined on `#{object}`"
7
+ end
8
+ method_block = method_for_action(action, invoker)
9
+
10
+ object.send(:define_method, action.name, &method_block)
11
+ end
12
+ end
13
+
14
+ def self.method_for_action(action, invoker)
15
+ Proc.new do |*args|
16
+ invoker.call(action, connection, *args)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ module ResourceKit
2
+ class Resource
3
+ class_attribute :namespace
4
+
5
+ attr_reader :connection
6
+
7
+ def initialize(connection)
8
+ @connection = connection
9
+ end
10
+
11
+ def self.resources(&block)
12
+ @resources ||= ResourceCollection.new
13
+ @resources.instance_eval(&block) if block_given?
14
+
15
+ MethodFactory.construct(self, @resources)
16
+ @resources
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ module ResourceKit
2
+ class ResourceCollection
3
+ extend Forwardable
4
+ def_delegators :@collection, :find, :<<, :each, :include?
5
+
6
+ def initialize
7
+ @collection = []
8
+ end
9
+
10
+ def action(name, &block)
11
+ action = Action.new(name)
12
+ action.instance_eval(&block) if block_given?
13
+ action.tap {|a| self << a }
14
+ end
15
+
16
+ def find_action(name)
17
+ find do |action|
18
+ action.name == name
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,59 @@
1
+ module ResourceKit
2
+ class StatusCodeMapper
3
+ MAP = {
4
+ continue: 100,
5
+ switching_protocols: 101,
6
+ processing: 102,
7
+ ok: 200,
8
+ created: 201,
9
+ accepted: 202,
10
+ non_authoritative_information: 203,
11
+ no_content: 204,
12
+ reset_content: 205,
13
+ partial_content: 206,
14
+ multi_status: 207,
15
+ im_used: 226,
16
+ multiple_choices: 300,
17
+ moved_permanently: 301,
18
+ found: 302,
19
+ see_other: 303,
20
+ not_modified: 304,
21
+ use_proxy: 305,
22
+ temporary_redirect: 307,
23
+ bad_request: 400,
24
+ unauthorized: 401,
25
+ payment_required: 402,
26
+ forbidden: 403,
27
+ not_found: 404,
28
+ method_not_allowed: 405,
29
+ not_acceptable: 406,
30
+ proxy_authentication_required: 407,
31
+ request_timeout: 408,
32
+ conflict: 409,
33
+ gone: 410,
34
+ length_required: 411,
35
+ precondition_failed: 412,
36
+ request_entity_too_large: 413,
37
+ request_uri_too_long: 414,
38
+ unsupported_media_type: 415,
39
+ requested_range_not_satisfiable: 416,
40
+ expectation_failed: 417,
41
+ unprocessable_entity: 422,
42
+ locked: 423,
43
+ failed_dependency: 424,
44
+ upgrade_required: 426,
45
+ internal_server_error: 500,
46
+ not_implemented: 501,
47
+ bad_gateway: 502,
48
+ service_unavailable: 503,
49
+ gateway_timeout: 504,
50
+ http_version_not_supported: 505,
51
+ insufficient_storage: 507,
52
+ not_extended: 510
53
+ }
54
+
55
+ def self.code_for(symbol)
56
+ MAP[symbol]
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,3 @@
1
+ module ResourceKit
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,16 @@
1
+ require "resource_kit/version"
2
+ require 'active_support/core_ext'
3
+ require 'faraday'
4
+
5
+ module ResourceKit
6
+ autoload :Resource, 'resource_kit/resource'
7
+ autoload :ResourceCollection, 'resource_kit/resource_collection'
8
+
9
+ autoload :Action, 'resource_kit/action'
10
+ autoload :ActionInvoker, 'resource_kit/action_invoker'
11
+ autoload :MethodFactory, 'resource_kit/method_factory'
12
+
13
+ autoload :StatusCodeMapper, 'resource_kit/status_code_mapper'
14
+ autoload :EndpointResolver, 'resource_kit/endpoint_resolver'
15
+
16
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'resource_kit/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "resource_kit"
8
+ spec.version = ResourceKit::VERSION
9
+ spec.authors = ["Robert Ross"]
10
+ spec.email = ["rross@digitalocean.com"]
11
+ spec.summary = %q{Resource Kit provides tools to aid in making API Clients. Such as URL resolving, Request / Response layer, and more.}
12
+ spec.description = ''
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
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_dependency 'faraday'
22
+ spec.add_dependency 'activesupport', '>= 3.0'
23
+ spec.add_dependency 'addressable', '~> 2.3.6'
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.6"
26
+ spec.add_development_dependency "rake"
27
+ spec.add_development_dependency "rspec", "~> 3.0"
28
+ spec.add_development_dependency "webmock", "~> 1.18.0"
29
+ spec.add_development_dependency "kartograph", "~> 0"
30
+ end
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ResourceKit::ActionInvoker do
4
+ let(:connection) { Faraday.new {|b| b.adapter :test, stubs } }
5
+ let(:stubs) do
6
+ Faraday::Adapter::Test::Stubs.new do |stub|
7
+ stub.get('/users') { |env| [200, {}, 'all users'] }
8
+ stub.get('/users/bad_page') { |env| [404, {}, 'not found'] }
9
+ stub.get('/users/12') { |env| [200, {}, 'user 12'] }
10
+ stub.post('/users') { |env| [200, {}, env[:body]] }
11
+ end
12
+ end
13
+ let(:action) { ResourceKit::Action.new(:find) }
14
+
15
+ describe '.call' do
16
+ it 'performs a request to the specfied URL' do
17
+ action.verb :get
18
+ action.path '/users'
19
+
20
+ result = ResourceKit::ActionInvoker.call(action, connection)
21
+
22
+ expect(result).to eq('all users')
23
+ end
24
+
25
+ it 'substitues params on call' do
26
+ action.verb :get
27
+ action.path '/users/:id'
28
+
29
+ result = ResourceKit::ActionInvoker.call(action, connection, id: 12)
30
+ expect(result).to eq('user 12')
31
+ end
32
+
33
+ context 'when an action has a handler' do
34
+ it 'returns the handler block' do
35
+ action.verb :get
36
+ action.path '/users'
37
+ action.handler(200) {|response| 'changed' }
38
+
39
+ result = ResourceKit::ActionInvoker.call(action, connection)
40
+
41
+ expect(result).to eq('changed')
42
+ end
43
+
44
+ it 'uses the correct handler on status codes' do
45
+ action.verb :get
46
+ action.path '/users/bad_page'
47
+ action.handler(404) {|response| '404ed' }
48
+
49
+ result = ResourceKit::ActionInvoker.call(action, connection)
50
+ expect(result).to eq('404ed')
51
+ end
52
+ end
53
+
54
+ context 'for requests with bodies' do
55
+ it 'includes the contents of the body in the request' do
56
+ action.verb :post
57
+ action.path '/users'
58
+
59
+ result = ResourceKit::ActionInvoker.call(action, connection, 'echo me')
60
+ expect(result).to eq('echo me')
61
+ end
62
+
63
+ it 'uses the body handler when present' do
64
+ action.verb :post
65
+ action.path '/users'
66
+ action.body {|object| 'i am a banana' }
67
+
68
+ result = ResourceKit::ActionInvoker.call(action, connection, 'echo me')
69
+ expect(result).to eq('i am a banana')
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ResourceKit::Action do
4
+ subject(:action) { ResourceKit::Action.new(:bunk) }
5
+
6
+ describe '#initialize' do
7
+ it 'initializes with a name' do
8
+ instance = ResourceKit::Action.new(:all)
9
+ expect(instance.name).to eq(:all)
10
+ end
11
+ end
12
+
13
+ describe '#verb' do
14
+ it 'sets the verb' do
15
+ action = described_class.new(:find)
16
+ action.verb :get
17
+ expect(action.verb).to be(:get)
18
+ end
19
+ end
20
+
21
+ describe '#path' do
22
+ it 'sets the path' do
23
+ action = described_class.new(:find)
24
+ action.path '/something/sammy'
25
+ expect(action.path).to eq('/something/sammy')
26
+ end
27
+ end
28
+
29
+ describe '#handler' do
30
+ it 'adds a handler to the handlers' do
31
+ expect { action.handler(202) { } }.to change(action.handlers, :size).from(0).to(1)
32
+ end
33
+
34
+ it 'adds the correct status code when using a symbol' do
35
+ action.handler(:ok) {}
36
+ expect(action.handlers).to have_key(200)
37
+ end
38
+ end
39
+
40
+ describe '#handlers' do
41
+ it 'returns a handler collection object' do
42
+ collection = action.handlers
43
+ expect(collection).to be_kind_of(Hash)
44
+ end
45
+ end
46
+
47
+ describe '#body' do
48
+ it 'stores a proc for handling requests with bodies' do
49
+ handler = Proc.new {|object| 'whut whut' }
50
+ action.body(&handler)
51
+
52
+ expect(action.body).to be(handler)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ResourceKit::EndpointResolver do
4
+ describe '#initialize' do
5
+ it 'initializes with pieces of a URL' do
6
+ options = { path: '/users', namespace: '/v2', query_param_keys: ['per_page', 'page'] }
7
+ instance = ResourceKit::EndpointResolver.new(options)
8
+
9
+ expect(instance.path).to eq(options[:path])
10
+ expect(instance.query_param_keys).to eq(options[:query_param_keys])
11
+ end
12
+ end
13
+
14
+ describe '#resolve' do
15
+ let(:options) { { path: '/users' } }
16
+ subject(:resolver) { ResourceKit::EndpointResolver.new(options) }
17
+
18
+ context 'simple resolve' do
19
+ it 'creates a populated URL from passed values' do
20
+ endpoint = resolver.resolve()
21
+ expect(endpoint).to eq('/users')
22
+ end
23
+ end
24
+
25
+ context 'substituted paths' do
26
+ let(:options) { super().merge(path: '/users/:id') }
27
+
28
+ it 'creates a populated URL from passed values' do
29
+ endpoint = resolver.resolve(id: 1066)
30
+ expect(endpoint).to eq('/users/1066')
31
+ end
32
+ end
33
+
34
+ context 'with query parameters' do
35
+ let(:options) { super().merge(path: '/users', query_param_keys: [:per_page, :page]) }
36
+
37
+ it 'generates a URL with query parameters set correctly' do
38
+ endpoint = resolver.resolve(per_page: 2, page: 3)
39
+
40
+ uri = Addressable::URI.parse(endpoint)
41
+ expect(uri.query_values).to eq("per_page" => '2', "page" => '3')
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ResourceKit::MethodFactory do
4
+ let(:collection) { ResourceKit::ResourceCollection.new }
5
+ let(:klass) { Class.new { attr_accessor :connection } }
6
+
7
+ describe '.construct' do
8
+ before do
9
+ collection.action :find
10
+ collection.action :all
11
+ end
12
+
13
+ it 'adds the methods from the resource collection to the passed object' do
14
+ ResourceKit::MethodFactory.construct(klass, collection)
15
+ instance = klass.new
16
+ expect(instance).to respond_to(:find, :all)
17
+ end
18
+
19
+ it 'bails when the method is already defined' do
20
+ collection.action :all
21
+
22
+ expect {
23
+ ResourceKit::MethodFactory.construct(klass, collection)
24
+ }.to raise_exception(ArgumentError).with_message("Action 'all' is already defined on `#{klass}`")
25
+ end
26
+
27
+ it 'adds the correct interface for the action' do
28
+ ResourceKit::MethodFactory.construct(klass, collection)
29
+ method_sig = klass.instance_method(:all).parameters
30
+
31
+ expect(method_sig).to eq([[:rest, :args]])
32
+ end
33
+
34
+ end
35
+
36
+ describe 'Calling the method' do
37
+ it 'calls the invoker passed with the arguments and action' do
38
+ action = ResourceKit::Action.new(:bunk)
39
+ collection << action
40
+ invoker = double('invoker', call: true)
41
+
42
+ ResourceKit::MethodFactory.construct(klass, collection, invoker)
43
+
44
+ instance = klass.new
45
+ instance.connection = double('connection')
46
+ instance.bunk('something', 'something else')
47
+ expect(invoker).to have_received(:call).with(action, instance.connection, 'something', 'something else')
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ResourceKit::ResourceCollection do
4
+ subject(:collection) { ResourceKit::ResourceCollection.new }
5
+
6
+ describe '#action' do
7
+ it 'yields an action to the block' do
8
+ expect {|b| collection.action(:all, &b) }.to yield_with_args(instance_of(ResourceKit::Action))
9
+ end
10
+
11
+ it 'adds the action to the collection' do
12
+ action = collection.action :all
13
+ expect(collection).to include(action)
14
+ end
15
+ end
16
+
17
+ describe '#find_action' do
18
+ it 'returns the action with the name passed' do
19
+ collection.action(:all)
20
+
21
+ retrieved_action = collection.find_action(:all)
22
+
23
+ expect(retrieved_action).to be_kind_of(ResourceKit::Action)
24
+ expect(retrieved_action.name).to eq(:all)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ResourceKit::Resource do
4
+ describe '.resources' do
5
+ subject(:resource) { Class.new(described_class) }
6
+
7
+ it 'returns a resource collection' do
8
+ expect(resource.resources).to be_kind_of(ResourceKit::ResourceCollection)
9
+ end
10
+
11
+ it 'yields a resource collection' do
12
+ expect { |b| resource.resources(&b) }.to yield_with_args(instance_of(ResourceKit::ResourceCollection))
13
+ end
14
+
15
+ context 'action methods' do
16
+ class DropletResource < described_class
17
+ resources do
18
+ action :find
19
+ action :all
20
+ end
21
+ end
22
+
23
+ subject(:droplet_resource) { DropletResource.new(double) }
24
+
25
+ it "defines the action method" do
26
+ expect(droplet_resource).to respond_to(:find)
27
+ end
28
+ end
29
+ end
30
+
31
+ describe '#initialize' do
32
+ it 'initializes with a connection' do
33
+ faraday = Faraday.new(url: 'http://lol.com')
34
+ instance = ResourceKit::Resource.new(faraday)
35
+
36
+ expect(instance.connection).to be(faraday)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe ResourceKit::StatusCodeMapper do
4
+ describe '.code_for' do
5
+ it 'returns the status code for a symbol' do
6
+ expect(described_class.code_for(:ok)).to eq(200)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ require 'resource_kit'
2
+
3
+ RSpec.configure do |config|
4
+ config.order = :random
5
+ end
metadata ADDED
@@ -0,0 +1,190 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: resource_kit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Robert Ross
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: addressable
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 2.3.6
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 2.3.6
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.6'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '1.6'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '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.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
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.18.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: 1.18.0
111
+ - !ruby/object:Gem::Dependency
112
+ name: kartograph
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ~>
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: ''
126
+ email:
127
+ - rross@digitalocean.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - .gitignore
133
+ - .rspec
134
+ - Gemfile
135
+ - LICENSE.txt
136
+ - README.md
137
+ - Rakefile
138
+ - examples/digitalocean_droplets.rb
139
+ - lib/resource_kit.rb
140
+ - lib/resource_kit/action.rb
141
+ - lib/resource_kit/action_invoker.rb
142
+ - lib/resource_kit/endpoint_resolver.rb
143
+ - lib/resource_kit/method_factory.rb
144
+ - lib/resource_kit/resource.rb
145
+ - lib/resource_kit/resource_collection.rb
146
+ - lib/resource_kit/status_code_mapper.rb
147
+ - lib/resource_kit/version.rb
148
+ - resource_kit.gemspec
149
+ - spec/lib/resource_kit/action_invoker_spec.rb
150
+ - spec/lib/resource_kit/action_spec.rb
151
+ - spec/lib/resource_kit/endpoint_resolver_spec.rb
152
+ - spec/lib/resource_kit/method_factory_spec.rb
153
+ - spec/lib/resource_kit/resource_collection_spec.rb
154
+ - spec/lib/resource_kit/resource_spec.rb
155
+ - spec/lib/resource_kit/status_code_mapper_spec.rb
156
+ - spec/spec_helper.rb
157
+ homepage: ''
158
+ licenses:
159
+ - MIT
160
+ metadata: {}
161
+ post_install_message:
162
+ rdoc_options: []
163
+ require_paths:
164
+ - lib
165
+ required_ruby_version: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - '>='
168
+ - !ruby/object:Gem::Version
169
+ version: '0'
170
+ required_rubygems_version: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - '>='
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
175
+ requirements: []
176
+ rubyforge_project:
177
+ rubygems_version: 2.2.2
178
+ signing_key:
179
+ specification_version: 4
180
+ summary: Resource Kit provides tools to aid in making API Clients. Such as URL resolving,
181
+ Request / Response layer, and more.
182
+ test_files:
183
+ - spec/lib/resource_kit/action_invoker_spec.rb
184
+ - spec/lib/resource_kit/action_spec.rb
185
+ - spec/lib/resource_kit/endpoint_resolver_spec.rb
186
+ - spec/lib/resource_kit/method_factory_spec.rb
187
+ - spec/lib/resource_kit/resource_collection_spec.rb
188
+ - spec/lib/resource_kit/resource_spec.rb
189
+ - spec/lib/resource_kit/status_code_mapper_spec.rb
190
+ - spec/spec_helper.rb