hollow 0.1.0 → 0.1.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 +4 -4
- data/README.md +49 -38
- data/lib/hollow.rb +2 -8
- data/lib/hollow/application.rb +90 -8
- data/lib/hollow/hollow_exception.rb +7 -0
- data/lib/hollow/resource.rb +5 -1
- data/lib/hollow/resource/chains.rb +21 -5
- data/lib/hollow/resource/stateful.rb +6 -1
- data/lib/hollow/resource/stateless.rb +6 -1
- data/lib/hollow/version.rb +2 -1
- metadata +18 -7
- data/lib/hollow/exceptions.rb +0 -7
- data/lib/hollow/resource/chains/chains.rb +0 -15
- data/lib/hollow/resource/resource.rb +0 -14
- data/lib/hollow/resource/stateful/stateful.rb +0 -18
- data/lib/hollow/resource/stateless/stateless.rb +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 913d652bacfc36cf0de16de872e48834aff704b1
|
4
|
+
data.tar.gz: 779d456eafe4c34533a0435f97ea976b7167bcb7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4cedfa66c2db9f2c03e68efa1021c90d7d24f836d65e9b6183b6f866160ccb147b5c0cd67034c4a4c43d06eb0f1b26e939d31d2f14dcf04a7aa56980c9809f55
|
7
|
+
data.tar.gz: 241ef0f0b0446aa28dd21266d9f42b9fd3da6324293c1346bb70da5f6f5ad65a3690b640b28ded8594348afcabcce36bce81584f1e06d6c47f8f569ab28bd97a
|
data/README.md
CHANGED
@@ -1,38 +1,49 @@
|
|
1
|
-
Hollow
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
The
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
1
|
+
# Hollow
|
2
|
+
|
3
|
+
> Hollow is a drop-in component for building RESTful services that bridges from any routing solution (like Sinatra) to your back-end. You flesh out your service with Resource classes, you pick a Router to forward some traffic to Hollow, and it works. GET /HelloWorld becomes HelloWorld.get().
|
4
|
+
|
5
|
+
With Hollow, any class which includes `Hollow::Resource::Stateless` or `Stateful` is a REST resource capable of servicing certain kinds of requests. Let's say we want to respond to POST requests with a greeting like, "Hello, Tomboyo!":
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
class HelloWorld
|
9
|
+
include Hollow::Resource::Stateless
|
10
|
+
def post(request)
|
11
|
+
if (request['name'] && !request['name'].empty?)
|
12
|
+
"Hello, #{request['name']}!"
|
13
|
+
else
|
14
|
+
"Hello, whoever you are!"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
```
|
19
|
+
|
20
|
+
We save that to `./resources/HelloWorld.rb`. Now we want to expose it to the world, so we make a `./server.rb` script. Using Hollow, we first create an `Application` object:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
my_app = Hollow::Application.new(
|
24
|
+
autorequire: {
|
25
|
+
root: "#{File.dirname __FILE__}",
|
26
|
+
directories: ['resources']
|
27
|
+
},
|
28
|
+
resource_methods: %i(post)
|
29
|
+
)
|
30
|
+
```
|
31
|
+
The application instance is configured to look for resources in `./resources`, which is where `HelloWorld` is defined. We've also decided that we only want to handle POST requests for now. Finally, we simply pipe HTTP traffic to the application’s `handle_request` method. Using Sinatra:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
get '/:resource' do |resource|
|
35
|
+
my_app.handle_request(
|
36
|
+
resource: resource,
|
37
|
+
method: request.request_method,
|
38
|
+
data: request.params
|
39
|
+
)
|
40
|
+
```
|
41
|
+
Start the server and `curl 127.0.0.1:4567/HelloWorld -d "name=Tomboyo"` to be given an enthusiastic greeting (that is, send a post request with your name). If we want to create any more functionality, we just create new classes where Hollow can find them. That's it!
|
42
|
+
|
43
|
+
# What about other classes and methods?
|
44
|
+
|
45
|
+
Only classes, and only classes which include `Hollow::Resource::Stateless` or `Hollow::Resource::Stateful` can service requests. Only resource methods matching the symbols configured via the `resource_methods: %i(post)` parameter during application instantiation can be invoked, as well; other methods are hidden. That means if you only make a `HelloWorld` resource and your application only has `post` configured, your application only invokes `HelloWorld`'s `post`. Nothing else, ever.
|
46
|
+
|
47
|
+
# Is this only for REST?
|
48
|
+
|
49
|
+
Hollow was designed for REST, but it's not limited in that respect. `Application` can be configured with nonstandard request methods (making resource.myRequestMethodHere a legal request handler), so really `Application` is a mapping from binary identifiers (HelloWorld, post) to methods (HelloWorld post(resource)).
|
data/lib/hollow.rb
CHANGED
@@ -1,17 +1,11 @@
|
|
1
|
+
require_relative "hollow/hollow_exception"
|
1
2
|
require_relative "hollow/application"
|
2
|
-
require_relative "hollow/exceptions"
|
3
3
|
require_relative "hollow/resource"
|
4
4
|
require_relative "hollow/version"
|
5
5
|
require_relative "hollow/resource/chains"
|
6
6
|
require_relative "hollow/resource/stateful"
|
7
7
|
require_relative "hollow/resource/stateless"
|
8
8
|
|
9
|
+
# Root namespace for Hollow
|
9
10
|
module Hollow
|
10
|
-
DEFAULT_SETTINGS = {
|
11
|
-
autorequire: {
|
12
|
-
root: "#{File.dirname __FILE__}/../..",
|
13
|
-
directories: []
|
14
|
-
},
|
15
|
-
resource_methods: ["get", "post", "put", "patch", "delete", "options"]
|
16
|
-
}
|
17
11
|
end
|
data/lib/hollow/application.rb
CHANGED
@@ -2,26 +2,111 @@ require 'require_all'
|
|
2
2
|
|
3
3
|
module Hollow
|
4
4
|
|
5
|
+
# A RESTful service provider framework which provides dynamic access to REST
|
6
|
+
# resource objects, allowing routers to service reqeusts without any knowledge
|
7
|
+
# of the types that exist in business code.
|
8
|
+
#
|
9
|
+
# - Resource classes compose the service provider aspect of Hollow and
|
10
|
+
# encapsulate all or part of a system's business logic.
|
11
|
+
# - Any class can be registered as a resource by including one of the
|
12
|
+
# Stateless[Resource/Stateless] or Stateful[Resource/Stateful] modules.
|
13
|
+
# - The service provider API is defined dynamically by application instance
|
14
|
+
# configuration (see settings[Application#settings-instance_method]).
|
15
|
+
# Different application instances can use the same underlying resources, but
|
16
|
+
# they will filter access to those resources differently. Requests through one
|
17
|
+
# application instance may succeed while requests through another may raise
|
18
|
+
# access exceptions.
|
19
|
+
# - Application instances provide service access to resources via the
|
20
|
+
# handle_request[#handle_request] method.
|
5
21
|
class Application
|
6
22
|
|
23
|
+
# Indicates an illegal resource name.
|
24
|
+
class ResourceException < Hollow::HollowException
|
25
|
+
# The illegal resource name.
|
26
|
+
attr_reader :resource_name
|
27
|
+
|
28
|
+
private
|
29
|
+
def initialize(resource_name)
|
30
|
+
super("The resource #{resource_name} does not exist.")
|
31
|
+
@resource_name = resource_name
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Indicates an illegal resource method name.
|
36
|
+
class ResourceMethodException < Hollow::HollowException
|
37
|
+
# The legal resource name provided for the request.
|
38
|
+
attr_reader :resource_name
|
39
|
+
|
40
|
+
# The illegal method name.
|
41
|
+
attr_reader :method_name
|
42
|
+
|
43
|
+
private
|
44
|
+
def initialize(resource_name, method_name)
|
45
|
+
super("The %s resource does not respond to %s requests" % [
|
46
|
+
resource_name,
|
47
|
+
method_name
|
48
|
+
])
|
49
|
+
|
50
|
+
@resource_name = resource_name
|
51
|
+
@method_name = method_name
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Default application settings.
|
56
|
+
DEFAULT_SETTINGS = {
|
57
|
+
autorequire: {
|
58
|
+
root: "#{File.dirname __FILE__}/../..",
|
59
|
+
directories: []
|
60
|
+
},
|
61
|
+
resource_methods: ["get", "post", "put", "patch", "delete", "options"]
|
62
|
+
}
|
63
|
+
|
64
|
+
# @return [Hash] the application settings
|
7
65
|
attr_reader :settings
|
8
66
|
|
67
|
+
# Create a new application instance.
|
68
|
+
# @param [Hash] settings application settings. See {DEFAULT_SETTINGS} for
|
69
|
+
# defaults.
|
70
|
+
# @option settings [Array<Symbol>, Array<String>] :resource_methods
|
71
|
+
# Resource method names which may be invoked on resource instances by
|
72
|
+
# this application. This effectively defines the service provider API.
|
73
|
+
# @option settings [String] :autorequire[:root]
|
74
|
+
# location in the file system relative to which other filesystem locations
|
75
|
+
# are evaluated.
|
76
|
+
# @option settings [Array<String>] :autorequire[:directories]
|
77
|
+
# file system locations where resource classes may be found; all classes
|
78
|
+
# in these directories will be required immediately. These locations are
|
79
|
+
# relative to `:autorequire[:root]`.
|
9
80
|
def initialize(settings = {})
|
10
|
-
@settings =
|
81
|
+
@settings = DEFAULT_SETTINGS.merge(settings)
|
11
82
|
@settings[:resource_methods].map! { |m| m.to_sym }
|
12
83
|
@settings[:autorequire][:directories].each do |dir|
|
13
84
|
require_all "#{@settings[:autorequire][:root]}/#{dir}"
|
14
85
|
end
|
15
86
|
end
|
16
87
|
|
88
|
+
# Attempt to invoke a resource method with the given data.
|
89
|
+
#
|
90
|
+
# @param [String, Symbol] resource
|
91
|
+
# The case-sensitive name of a desired resource.
|
92
|
+
# @param [String, Symbol] method
|
93
|
+
# The case-sensitive resource method name to invoke.
|
94
|
+
# @param [Hash] data
|
95
|
+
# Any data which the resource may or may not use to handle the reqeust.
|
96
|
+
# @raise [ResourceException]
|
97
|
+
# If the indicated resource is not defined, is not a Class, is
|
98
|
+
# {Hollow::Resource} itself, or is not a type of {Hollow::Resource}.
|
99
|
+
# @raise [ResourceMethodException]
|
100
|
+
# If the indicated resource exists and:
|
101
|
+
# 1. The indicated method is not accessible or defined, or
|
102
|
+
# 2. The method name is not included in `settings[:resource_methods]`.
|
17
103
|
def handle_request(resource: nil, method: nil, data: {})
|
18
104
|
begin
|
19
105
|
resource_class = Application::get_resource(resource.to_sym)
|
20
106
|
handler = resource_class.get_instance
|
21
107
|
method = method.to_sym.downcase
|
22
108
|
rescue NoMethodError, NameError
|
23
|
-
fail
|
24
|
-
"The resource #{resource} does not exist."
|
109
|
+
fail ResourceException.new resource
|
25
110
|
end
|
26
111
|
|
27
112
|
if @settings[:resource_methods].include?(method) &&
|
@@ -31,9 +116,7 @@ module Hollow
|
|
31
116
|
invoke_chain(resource_class, data, :after, method)
|
32
117
|
return response
|
33
118
|
else
|
34
|
-
fail
|
35
|
-
"The %s resource does not respond to %s requests" % [ resource,
|
36
|
-
method ]
|
119
|
+
fail ResourceMethodException.new resource, method
|
37
120
|
end
|
38
121
|
end
|
39
122
|
|
@@ -47,8 +130,7 @@ module Hollow
|
|
47
130
|
it.is_a?(Hollow::Resource)
|
48
131
|
return it
|
49
132
|
else
|
50
|
-
fail
|
51
|
-
"The requested resource (\"#{resource}\") does not exist."
|
133
|
+
fail ResourceException.new resource
|
52
134
|
end
|
53
135
|
end
|
54
136
|
end
|
data/lib/hollow/resource.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
module Hollow
|
2
2
|
|
3
|
-
# A mixin that marks a class as a Resource
|
3
|
+
# A mixin that marks a class as a resource. Only instances of Resource can be
|
4
|
+
# accessed by {Hollow::Application#handle_request}. This module should not be
|
5
|
+
# used directly; prefer {Hollow::Resource::Stateless} or
|
6
|
+
# {Hollow::Resource::Stateful} instead.
|
4
7
|
module Resource
|
5
8
|
|
9
|
+
private
|
6
10
|
def self.extended(base)
|
7
11
|
base.class_variable_set(:@@chains, {
|
8
12
|
before: {},
|
@@ -1,13 +1,29 @@
|
|
1
1
|
module Hollow
|
2
2
|
module Resource
|
3
3
|
|
4
|
+
# Indicates that a {Hollow::Resource} class uses chains to perform logic
|
5
|
+
# before or after each request.
|
4
6
|
module Chains
|
5
|
-
def self.included(base)
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
# Register a function to invoke before or after a given resource method
|
9
|
+
# (or any resource method) is invoked.
|
10
|
+
#
|
11
|
+
# Behaviors are invoked in the order in which they are defined within
|
12
|
+
# their respective chains, the exception being that behaviors associated
|
13
|
+
# with `:all` methods are invoked before more specific methods.
|
14
|
+
#
|
15
|
+
# @param [Symbol] chain
|
16
|
+
# Which chain to register the designated behavior with. Must be one of
|
17
|
+
# either `:before` or `:after`.
|
18
|
+
# @param [Symbol] method
|
19
|
+
# Which request method triggers the behavior. If `:all` is provded,
|
20
|
+
# any request method will invoke the behavior.
|
21
|
+
# @param [Proc, Lambda] behavior
|
22
|
+
# Behavior which accepts one argument (the data accompanying the
|
23
|
+
# current request).
|
24
|
+
def chain(chain, method, behavior)
|
25
|
+
(self.class_variable_get(:@@chains)[chain][method] ||= []) <<
|
26
|
+
behavior
|
11
27
|
end
|
12
28
|
end
|
13
29
|
|
@@ -1,8 +1,13 @@
|
|
1
1
|
module Hollow
|
2
2
|
module Resource
|
3
3
|
|
4
|
-
#
|
4
|
+
# Marks a class as a {Hollow::Resource} that may be used by
|
5
|
+
# {Hollow::Application} instances to handle requests. Each time
|
6
|
+
# {Hollow::Application#handle_request} delegates to a Stateful resource, a
|
7
|
+
# new instance of the resource is created to service the request.
|
8
|
+
# @see Hollow::Resource::Stateless
|
5
9
|
module Stateful
|
10
|
+
private
|
6
11
|
def self.included(base)
|
7
12
|
unless base.is_a?(Hollow::Resource)
|
8
13
|
base.extend(Hollow::Resource)
|
@@ -1,8 +1,13 @@
|
|
1
1
|
module Hollow
|
2
2
|
module Resource
|
3
3
|
|
4
|
-
#
|
4
|
+
# Marks a class as a {Hollow::Resource} that may be used by
|
5
|
+
# {Hollow::Application} instances to handle requests. Including this module
|
6
|
+
# creates a singleton instance of the class which will service all requests
|
7
|
+
# from all Application instances.
|
8
|
+
# @see Hollow::Resource::Stateful
|
5
9
|
module Stateless
|
10
|
+
private
|
6
11
|
def self.included(base)
|
7
12
|
unless base.is_a?(Hollow::Resource)
|
8
13
|
base.extend(Hollow::Resource)
|
data/lib/hollow/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hollow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tom Simmons
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-08-
|
11
|
+
date: 2017-08-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: require_all
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0.7'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: yard
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
83
97
|
description: "\n Hollow is a drop-in component for building RESTful services that
|
84
98
|
bridges\n from any routing solution (like Sinatra) to your back-end. You flesh
|
85
99
|
out\n your service with Resource classes, you pick a Router to forward some\n
|
@@ -95,21 +109,18 @@ files:
|
|
95
109
|
- README.md
|
96
110
|
- lib/hollow.rb
|
97
111
|
- lib/hollow/application.rb
|
98
|
-
- lib/hollow/
|
112
|
+
- lib/hollow/hollow_exception.rb
|
99
113
|
- lib/hollow/resource.rb
|
100
114
|
- lib/hollow/resource/chains.rb
|
101
|
-
- lib/hollow/resource/chains/chains.rb
|
102
|
-
- lib/hollow/resource/resource.rb
|
103
115
|
- lib/hollow/resource/stateful.rb
|
104
|
-
- lib/hollow/resource/stateful/stateful.rb
|
105
116
|
- lib/hollow/resource/stateless.rb
|
106
|
-
- lib/hollow/resource/stateless/stateless.rb
|
107
117
|
- lib/hollow/version.rb
|
108
118
|
homepage: https://www.github.com/tomboyo/hollow
|
109
119
|
licenses:
|
110
120
|
- MIT
|
111
121
|
metadata:
|
112
122
|
allowed_push_host: https://rubygems.org
|
123
|
+
yard.run: yard
|
113
124
|
post_install_message:
|
114
125
|
rdoc_options: []
|
115
126
|
require_paths:
|
data/lib/hollow/exceptions.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
module Hollow
|
2
|
-
module Resource
|
3
|
-
|
4
|
-
# A resource which handles each request with a new instance
|
5
|
-
module Stateful
|
6
|
-
def self.included(base)
|
7
|
-
unless base.is_a?(Hollow::Resource)
|
8
|
-
base.extend(Hollow::Resource)
|
9
|
-
end
|
10
|
-
|
11
|
-
def base.get_instance
|
12
|
-
self.new
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
end
|
18
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
module Hollow
|
2
|
-
module Resource
|
3
|
-
|
4
|
-
# A resource that handles all requests with the same instance
|
5
|
-
module Stateless
|
6
|
-
def self.included(base)
|
7
|
-
unless base.is_a?(Hollow::Resource)
|
8
|
-
base.extend(Hollow::Resource)
|
9
|
-
end
|
10
|
-
|
11
|
-
base.class_variable_set(:@@instance, base.new)
|
12
|
-
def base.get_instance
|
13
|
-
self.class_variable_get(:@@instance)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
end
|
19
|
-
end
|