From cdea23487f46fdbdae70855ed9bc3cfc68537a9a Mon Sep 17 00:00:00 2001 From: Robert Ross Date: Sun, 2 Nov 2014 17:29:22 -0500 Subject: [PATCH 1/5] README default connections. --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index da693b6..3f73b23 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,28 @@ RSpec.describe MyResourceClass, resource_kit: true do end ``` +## Default Connection Factory + +ResourceKit also allows you to specify a default connection factory. This is useful for when you want to just initialize resources, or enable class methods. + +```ruby +class BaseResource < ResourceKit::Resource + resources do + default_handler { |resp| raise "Unexpected status code: #{resp.code}" } + connection { Faraday.new(url: 'http://example.com') } + end +end + +class UsersResource < BaseResource + resources do + get "/users/info" => :info + end +end + +resource = UsersResource.new +user_info = resource.info +``` + ### Nice to have's Things we've thought about but just haven't implemented are: From 21a426dc4f61e3a15cb4489d6a3abc89d3628915 Mon Sep 17 00:00:00 2001 From: Robert Ross Date: Sun, 2 Nov 2014 18:30:47 -0500 Subject: [PATCH 2/5] Ensure that we inherit our resources. --- lib/resource_kit/method_factory.rb | 4 --- spec/integration/inheritence_spec.rb | 26 ++++++++++++++++++++ spec/lib/resource_kit/method_factory_spec.rb | 8 ------ 3 files changed, 26 insertions(+), 12 deletions(-) create mode 100644 spec/integration/inheritence_spec.rb diff --git a/lib/resource_kit/method_factory.rb b/lib/resource_kit/method_factory.rb index 79bd275..43bddae 100644 --- a/lib/resource_kit/method_factory.rb +++ b/lib/resource_kit/method_factory.rb @@ -2,11 +2,7 @@ module ResourceKit class MethodFactory def self.construct(object, resource_collection, invoker = ActionInvoker) resource_collection.each do |action| - if object.method_defined?(action.name) - raise ArgumentError, "Action '#{action.name}' is already defined on `#{object}`" - end method_block = method_for_action(action, invoker) - object.send(:define_method, action.name, &method_block) end end diff --git a/spec/integration/inheritence_spec.rb b/spec/integration/inheritence_spec.rb new file mode 100644 index 0000000..065612b --- /dev/null +++ b/spec/integration/inheritence_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +class DummyInheritenceResource < ResourceKit::Resource + resources do + default_handler(:ok) { |resp| "Hello" } + get "/dummy" => :dummy + end +end + +class DummyChildResource < DummyInheritenceResource + resources do + get "/inherited" => :inherited + end +end + +RSpec.describe 'Resource Inheritence' do + it 'inherits the superclasses actions' do + action = DummyChildResource.resources.find_action(:dummy) + expect(action).to_not be_nil + expect(action.name).to eq(:dummy) + end + + it 'inherits default handlers' do + expect(DummyChildResource.resources.default_handlers[200]).to_not be_nil + end +end \ No newline at end of file diff --git a/spec/lib/resource_kit/method_factory_spec.rb b/spec/lib/resource_kit/method_factory_spec.rb index 3778139..d729fe5 100644 --- a/spec/lib/resource_kit/method_factory_spec.rb +++ b/spec/lib/resource_kit/method_factory_spec.rb @@ -16,14 +16,6 @@ expect(instance).to respond_to(:find, :all) end - it 'bails when the method is already defined' do - collection.action :all - - expect { - ResourceKit::MethodFactory.construct(klass, collection) - }.to raise_exception(ArgumentError).with_message("Action 'all' is already defined on `#{klass}`") - end - it 'adds the correct interface for the action' do ResourceKit::MethodFactory.construct(klass, collection) method_sig = klass.instance_method(:all).parameters From 0fe6394275fd8ab4a99fb1b684fb198ed945c6ee Mon Sep 17 00:00:00 2001 From: Robert Ross Date: Sun, 2 Nov 2014 21:46:18 -0500 Subject: [PATCH 3/5] Default connection option. --- lib/resource_kit/resource.rb | 2 +- lib/resource_kit/resource_collection.rb | 5 +++++ spec/lib/resource_kit/resource_collection_spec.rb | 13 +++++++++++++ spec/lib/resource_kit/resource_spec.rb | 13 +++++++++++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/lib/resource_kit/resource.rb b/lib/resource_kit/resource.rb index d532e8e..9c66c61 100644 --- a/lib/resource_kit/resource.rb +++ b/lib/resource_kit/resource.rb @@ -8,7 +8,7 @@ class Resource attr_reader :connection, :scope def initialize(connection: nil, scope: nil) - @connection = connection + @connection = connection || _resources.default_connection.call @scope = scope end diff --git a/lib/resource_kit/resource_collection.rb b/lib/resource_kit/resource_collection.rb index e66f40c..791acf3 100644 --- a/lib/resource_kit/resource_collection.rb +++ b/lib/resource_kit/resource_collection.rb @@ -42,6 +42,11 @@ def find_action(name) end end + def default_connection(&connection_block) + @default_connection = connection_block if block_given? + @default_connection || Proc.new { } + end + private def parse_verb_and_path(verb_and_path) diff --git a/spec/lib/resource_kit/resource_collection_spec.rb b/spec/lib/resource_kit/resource_collection_spec.rb index 72b003a..1397d92 100644 --- a/spec/lib/resource_kit/resource_collection_spec.rb +++ b/spec/lib/resource_kit/resource_collection_spec.rb @@ -64,4 +64,17 @@ expect(retrieved_action.name).to eq(:all) end end + + describe '#default_connection' do + it 'sets the default connection when passed a block' do + proc = Proc.new { } + collection.default_connection(&proc) + + expect(collection.default_connection).to eq(proc) + end + + it 'returns a proc that returns nil when nothing has been set' do + expect(collection.default_connection.call).to be_nil + end + end end diff --git a/spec/lib/resource_kit/resource_spec.rb b/spec/lib/resource_kit/resource_spec.rb index 02671b3..63cef30 100644 --- a/spec/lib/resource_kit/resource_spec.rb +++ b/spec/lib/resource_kit/resource_spec.rb @@ -46,6 +46,19 @@ class DropletResource < described_class expect(instance.connection).to be(connection) expect(instance.scope).to be(scope) end + + context 'with a defaulted connection' do + it 'uses the default connection' do + klass = Class.new(ResourceKit::Resource) do + resources do + default_connection { :default_connection } + end + end + + instance = klass.new + expect(instance.connection).to be(:default_connection) + end + end end describe '#action' do From f67ce754df8d817c2d3bec064200aea4f8621cd3 Mon Sep 17 00:00:00 2001 From: Robert Ross Date: Sun, 2 Nov 2014 22:30:15 -0500 Subject: [PATCH 4/5] Allow including singleton requests that will use the default connection. --- lib/resource_kit.rb | 1 + lib/resource_kit/singleton_requests.rb | 29 +++++++++++++++ .../resource_kit/singleton_requests_spec.rb | 36 +++++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 lib/resource_kit/singleton_requests.rb create mode 100644 spec/lib/resource_kit/singleton_requests_spec.rb diff --git a/lib/resource_kit.rb b/lib/resource_kit.rb index b0e1fc7..432f7f2 100644 --- a/lib/resource_kit.rb +++ b/lib/resource_kit.rb @@ -15,4 +15,5 @@ module ResourceKit autoload :StatusCodeMapper, 'resource_kit/status_code_mapper' autoload :EndpointResolver, 'resource_kit/endpoint_resolver' + autoload :SingletonRequests, 'resource_kit/singleton_requests' end diff --git a/lib/resource_kit/singleton_requests.rb b/lib/resource_kit/singleton_requests.rb new file mode 100644 index 0000000..4f885f4 --- /dev/null +++ b/lib/resource_kit/singleton_requests.rb @@ -0,0 +1,29 @@ +module ResourceKit + module SingletonRequests + def self.included(base) + base.extend ClassMethods + end + + module ClassMethods + def method_missing(action_name, *args, &block) + if action = resources.find_action(action_name) + define_method_for_action(action_name) + send(action_name, *args, &block) + else + super + end + end + + private + + def define_method_for_action(action_name) + class_eval <<-RUBY + def self.#{action_name}(*args, &block) + resource_instance = new + resource_instance.send(:#{action_name}, *args, &block) + end + RUBY + end + end + end +end \ No newline at end of file diff --git a/spec/lib/resource_kit/singleton_requests_spec.rb b/spec/lib/resource_kit/singleton_requests_spec.rb new file mode 100644 index 0000000..231c751 --- /dev/null +++ b/spec/lib/resource_kit/singleton_requests_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +RSpec.describe ResourceKit::SingletonRequests do + let(:klass) do + Class.new(ResourceKit::Resource) do + include ResourceKit::SingletonRequests + + resources do + get '/hello' => :hello + end + end + end + + describe 'Inclusions' do + let(:connection) { Faraday.new { |b| b.adapter :test, stubs } } + let(:stubs) do + Faraday::Adapter::Test::Stubs.new do |stub| + stub.get('/hello') { |env| [200, {}, 'world'] } + end + end + + before do + # Set the default connection for the resource to our stubbed one + klass.resources.default_connection { connection } + end + + it 'allows calling actions as class methods' do + value = klass.hello + expect(value).to eq('world') + end + + it 'defines the method on the class when called' do + expect { klass.hello }.to change { klass.methods.include?(:hello) }.to(true).from(false) + end + end +end \ No newline at end of file From f56a03f0676d24b990402ef73b63888d61afde2b Mon Sep 17 00:00:00 2001 From: Robert Ross Date: Mon, 3 Nov 2014 09:07:00 -0500 Subject: [PATCH 5/5] I don't like this but it's a start. --- README.md | 21 +++++++++++++++++++++ lib/resource_kit/resource.rb | 4 ++++ lib/resource_kit/resource_collection.rb | 14 ++++++++++++++ spec/integration/inheritence_spec.rb | 4 ++++ 4 files changed, 43 insertions(+) diff --git a/README.md b/README.md index 3f73b23..f72a44f 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,27 @@ resource = UsersResource.new user_info = resource.info ``` +## Singleton Methods + +In most cases, you don't care about instantiating a class to use it. You just want the class to respond to a singleton method. + +ResourceKit allows you to do this by mixing in `ResourceKit::SingletonRequests` into your resource classes. This makes calling methods a little simpler, but requires you setup a default connection in order to work. + +```ruby +class UsersResource < ResourceKit::Resource + include ResourceKit::SingletonRequests + + resources do + default_handler { |resp| raise "Unexpected status code: #{resp.code}" } + connection { Faraday.new(url: 'http://example.com') } + + get '/api/users/:id' => :find + end +end + +users_info = UsersResource.find(id: 123) +``` + ### Nice to have's Things we've thought about but just haven't implemented are: diff --git a/lib/resource_kit/resource.rb b/lib/resource_kit/resource.rb index 9c66c61..b7dcd53 100644 --- a/lib/resource_kit/resource.rb +++ b/lib/resource_kit/resource.rb @@ -23,6 +23,10 @@ def self.resources(&block) self._resources end + def self.inherited(base) + base._resources = resources.dup + end + def action(name) _resources.find_action(name) end diff --git a/lib/resource_kit/resource_collection.rb b/lib/resource_kit/resource_collection.rb index 791acf3..4fc0701 100644 --- a/lib/resource_kit/resource_collection.rb +++ b/lib/resource_kit/resource_collection.rb @@ -47,6 +47,20 @@ def default_connection(&connection_block) @default_connection || Proc.new { } end + def dup + collection = ResourceCollection.new + + # Copy all actions into the collection + each do |action| + collection << action + end + + # Copy all of the default handlers in + collection.default_handlers.merge!(default_handlers) + + collection + end + private def parse_verb_and_path(verb_and_path) diff --git a/spec/integration/inheritence_spec.rb b/spec/integration/inheritence_spec.rb index 065612b..cb8eda0 100644 --- a/spec/integration/inheritence_spec.rb +++ b/spec/integration/inheritence_spec.rb @@ -23,4 +23,8 @@ class DummyChildResource < DummyInheritenceResource it 'inherits default handlers' do expect(DummyChildResource.resources.default_handlers[200]).to_not be_nil end + + it 'does not modify the parent resource class' do + expect(DummyInheritenceResource.resources.find_action(:inherited)).to be_nil + end end \ No newline at end of file