resource_kit 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 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