restrack 0.0.3 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +2 -1
- data/Rakefile +9 -2
- data/lib/restrack/generator/config.ru.erb +3 -0
- data/lib/restrack/generator.rb +7 -1
- data/lib/restrack/resource_controller.rb +35 -32
- data/lib/restrack/resource_request.rb +9 -8
- data/lib/restrack/support.rb +4 -1
- data/lib/restrack/version.rb +1 -1
- data/lib/restrack/web_service.rb +4 -4
- data/lib/restrack.rb +0 -1
- data/test/test_support.rb +5 -1
- metadata +4 -4
- data/config/constants.yaml +0 -8
data/README.rdoc
CHANGED
@@ -22,7 +22,7 @@
|
|
22
22
|
|
23
23
|
|
24
24
|
== Why RESTRack when there is Rails?
|
25
|
-
Rails is a powerful tool for full web applications. RESTRack is
|
25
|
+
Rails is a powerful tool for full web applications. RESTRack is targeted at
|
26
26
|
making development of lightweight data services as easy as possible, while
|
27
27
|
still giving you a performant and extensible framework. The primary goal of
|
28
28
|
the framework was to add as little as possible to the framework to give the
|
@@ -47,6 +47,7 @@
|
|
47
47
|
== How-Tos
|
48
48
|
|
49
49
|
...yet to be done...
|
50
|
+
=== Authentication Suggestions
|
50
51
|
|
51
52
|
=== Logging/Logging Level
|
52
53
|
|
data/Rakefile
CHANGED
@@ -7,8 +7,15 @@ Bundler::GemHelper.install_tasks
|
|
7
7
|
task :default => [:test_all]
|
8
8
|
|
9
9
|
desc 'Run all tests.'
|
10
|
-
|
11
|
-
|
10
|
+
task :test_all do
|
11
|
+
for n in 0..4
|
12
|
+
Rake::Task['test'+n.to_s].invoke
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Run base tests.'
|
17
|
+
Rake::TestTask.new('test0') { |t|
|
18
|
+
t.pattern = 'test/test_*.rb'
|
12
19
|
}
|
13
20
|
|
14
21
|
desc 'Run sample_app_1 tests.'
|
data/lib/restrack/generator.rb
CHANGED
@@ -7,14 +7,15 @@ module RESTRack
|
|
7
7
|
class Generator
|
8
8
|
TEMPLATE = {
|
9
9
|
:service => 'loader.rb.erb',
|
10
|
+
:rackup => 'config.ru.erb',
|
10
11
|
:constants => 'constants.yaml.erb',
|
11
12
|
:controller => 'controller.rb.erb'
|
12
13
|
}
|
13
14
|
|
14
15
|
class << self
|
15
16
|
|
17
|
+
# Generate controller file
|
16
18
|
def generate_controller(name)
|
17
|
-
# Generate controller file
|
18
19
|
template = get_template_for( :controller )
|
19
20
|
resultant_string = template.result( get_binding_for_controller( name ) )
|
20
21
|
File.open("#{base_dir}/controllers/#{name}_controller.rb", 'w') {|f| f.puts resultant_string }
|
@@ -22,6 +23,7 @@ module RESTRack
|
|
22
23
|
FileUtils.makedirs("#{name}/views")
|
23
24
|
end
|
24
25
|
|
26
|
+
# Generate a new RESTRack service
|
25
27
|
def generate_service(name)
|
26
28
|
FileUtils.makedirs("#{name}/config")
|
27
29
|
FileUtils.makedirs("#{name}/controllers")
|
@@ -33,6 +35,10 @@ module RESTRack
|
|
33
35
|
resultant_string = template.result( get_binding_for_service( name ) )
|
34
36
|
File.open("#{name}/loader.rb", 'w') {|f| f.puts resultant_string }
|
35
37
|
|
38
|
+
template = get_template_for( :rackup )
|
39
|
+
resultant_string = template.result( get_binding_for_service( name ) )
|
40
|
+
File.open("#{name}/config.ru", 'w') {|f| f.puts resultant_string }
|
41
|
+
|
36
42
|
template = get_template_for( :constants )
|
37
43
|
resultant_string = template.result( get_binding_for_service( name ) )
|
38
44
|
File.open("#{name}/config/constants.yaml", 'w') {|f| f.puts resultant_string }
|
@@ -1,10 +1,25 @@
|
|
1
1
|
module RESTRack
|
2
|
+
# All RESTRack controllers descend from ResourceController.
|
3
|
+
|
4
|
+
# HTTP Verb: | GET | PUT | POST | DELETE
|
5
|
+
# Collection URI (/widgets/): | index | replace | create | drop
|
6
|
+
# Element URI (/widgets/42): | show | update | add | destroy
|
7
|
+
|
8
|
+
#def index; end
|
9
|
+
#def replace; end
|
10
|
+
#def create; end
|
11
|
+
#def drop; end
|
12
|
+
#def show(id); end
|
13
|
+
#def update(id); end
|
14
|
+
#def add(id); end
|
15
|
+
#def destroy(id); end
|
16
|
+
|
2
17
|
class ResourceController
|
3
18
|
attr_reader :input, :output
|
4
19
|
|
20
|
+
# Base initialization method for resources and storage of request input
|
21
|
+
# This method should not be overriden in decendent classes.
|
5
22
|
def self.__init(resource_request)
|
6
|
-
# Base initialization method for resources and storage of request input
|
7
|
-
# This method should not be overriden in decendent classes.
|
8
23
|
__self = self.new
|
9
24
|
return __self.__init(resource_request)
|
10
25
|
end
|
@@ -14,33 +29,20 @@ module RESTRack
|
|
14
29
|
self
|
15
30
|
end
|
16
31
|
|
32
|
+
# Call the controller's action and return it in the proper format.
|
17
33
|
def call
|
18
|
-
# Call the controller's action and return it in the proper format.
|
19
34
|
args = []
|
20
35
|
args << @resource_request.id unless @resource_request.id.blank?
|
21
36
|
package( self.send(@resource_request.action.to_sym, *args) )
|
22
37
|
end
|
23
38
|
|
24
|
-
# HTTP Verb: | GET | PUT | POST | DELETE
|
25
|
-
# Collection URI (/widgets/): | index | replace | create | drop
|
26
|
-
# Element URI (/widgets/42): | show | update | add | destroy
|
27
|
-
|
28
|
-
#def index; end
|
29
|
-
#def replace; end
|
30
|
-
#def create; end
|
31
|
-
#def drop; end
|
32
|
-
#def show(id); end
|
33
|
-
#def update(id); end
|
34
|
-
#def add(id); end
|
35
|
-
#def destroy(id); end
|
36
|
-
|
37
39
|
def method_missing(method_sym, *arguments, &block)
|
38
40
|
raise HTTP405MethodNotAllowed, 'Method not provided on controller.'
|
39
41
|
end
|
40
42
|
|
41
43
|
protected # all internal methods are protected rather than private so that calling methods *could* be overriden if necessary.
|
44
|
+
# This method allows one to access a related resource, without providing a direct link to specific relation(s).
|
42
45
|
def self.has_relationship_to(entity, opts = {})
|
43
|
-
# This method allows one to access a related resource, without providing a direct link to specific relation(s).
|
44
46
|
entity_name = opts[:as] || entity
|
45
47
|
define_method( entity_name.to_sym,
|
46
48
|
Proc.new do
|
@@ -56,11 +58,11 @@ module RESTRack
|
|
56
58
|
)
|
57
59
|
end
|
58
60
|
|
61
|
+
# This method defines that there is a single link to a member from an entity collection.
|
62
|
+
# The second parameter is an options hash to support setting the local name of the relation via ':as => :foo'.
|
63
|
+
# The third parameter to the method is a Proc which accepts the calling entity's id and returns the id of the relation to which we're establishing the link.
|
64
|
+
# This adds an accessor instance method whose name is the entity's class.
|
59
65
|
def self.has_direct_relationship_to(entity, opts = {}, &get_entity_id_from_relation_id)
|
60
|
-
# This method defines that there is a single link to a member from an entity collection.
|
61
|
-
# The second parameter is an options hash to support setting the local name of the relation via ':as => :foo'.
|
62
|
-
# The third parameter to the method is a Proc which accepts the calling entity's id and returns the id of the relation to which we're establishing the link.
|
63
|
-
# This adds an accessor instance method whose name is the entity's class.
|
64
66
|
entity_name = opts[:as] || entity
|
65
67
|
define_method( entity_name.to_sym,
|
66
68
|
Proc.new do
|
@@ -73,9 +75,9 @@ module RESTRack
|
|
73
75
|
)
|
74
76
|
end
|
75
77
|
|
78
|
+
# This method defines that there are multiple links to members from an entity collection (an array of entity identifiers).
|
79
|
+
# This adds an accessor instance method whose name is the entity's class.
|
76
80
|
def self.has_direct_relationships_to(entity, opts = {}, &get_entity_id_from_relation_id)
|
77
|
-
# This method defines that there are multiple links to members from an entity collection (an array of entity identifiers).
|
78
|
-
# This adds an accessor instance method whose name is the entity's class.
|
79
81
|
entity_name = opts[:as] || entity
|
80
82
|
define_method( entity_name.to_sym,
|
81
83
|
Proc.new do
|
@@ -91,9 +93,9 @@ module RESTRack
|
|
91
93
|
)
|
92
94
|
end
|
93
95
|
|
96
|
+
# This method defines that there are mapped links to members from an entity collection (a hash of entity identifiers).
|
97
|
+
# This adds an accessor instance method whose name is the entity's class.
|
94
98
|
def self.has_mapped_relationships_to(entity, opts = {}, &get_entity_id_from_relation_id)
|
95
|
-
# This method defines that there are mapped links to members from an entity collection (a hash of entity identifiers).
|
96
|
-
# This adds an accessor instance method whose name is the entity's class.
|
97
99
|
entity_name = opts[:as] || entity
|
98
100
|
define_method( entity_name.to_sym,
|
99
101
|
Proc.new do
|
@@ -109,17 +111,17 @@ module RESTRack
|
|
109
111
|
)
|
110
112
|
end
|
111
113
|
|
114
|
+
# Call the child relation (next entity in the path stack)
|
115
|
+
# common logic to all relationship methods
|
112
116
|
def call_relation(entity)
|
113
|
-
# Call the child relation (next entity in the path stack)
|
114
|
-
# common logic to all relationship methods
|
115
117
|
@resource_request.resource_name = entity.to_s.camelize
|
116
118
|
setup_action
|
117
119
|
@resource_request.locate
|
118
120
|
@resource_request.call
|
119
121
|
end
|
120
122
|
|
123
|
+
# If the action is not set with the request URI, determine the action from HTTP Verb.
|
121
124
|
def setup_action
|
122
|
-
# If the action is not set with the request URI, determine the action from HTTP Verb.
|
123
125
|
if @resource_request.action.blank?
|
124
126
|
if @resource_request.request.get?
|
125
127
|
@resource_request.action = @resource_request.id.blank? ? :index : :show
|
@@ -135,13 +137,13 @@ module RESTRack
|
|
135
137
|
end
|
136
138
|
end
|
137
139
|
|
140
|
+
# Allows decendent controllers to set a data type for the id other than the default.
|
138
141
|
def self.keyed_with_type(klass)
|
139
|
-
# Allows decendent controllers to set a data type for the id other than the default.
|
140
142
|
@@key_type = klass
|
141
143
|
end
|
142
144
|
|
145
|
+
# This method is used to convert the id coming off of the path stack, which is in string form, into another data type if one has been set.
|
143
146
|
def format_id
|
144
|
-
# This method is used to convert the id coming off of the path stack, which is in string form, into another data type if one has been set.
|
145
147
|
@@key_type ||= nil
|
146
148
|
unless @@key_type.blank?
|
147
149
|
if @@key_type == Fixnum
|
@@ -156,8 +158,8 @@ module RESTRack
|
|
156
158
|
end
|
157
159
|
end
|
158
160
|
|
161
|
+
# This handles outputing properly formatted content based on the file extension in the URL.
|
159
162
|
def package(data)
|
160
|
-
# This handles outputing properly formatted content based on the file extension in the URL.
|
161
163
|
if @resource_request.mime_type.like?( RESTRack.mime_type_for( :JSON ) )
|
162
164
|
@output = data.to_json
|
163
165
|
elsif @resource_request.mime_type.like?( RESTRack.mime_type_for( :XML ) )
|
@@ -175,8 +177,8 @@ module RESTRack
|
|
175
177
|
end
|
176
178
|
end
|
177
179
|
|
180
|
+
# Use Builder to generate the XML.
|
178
181
|
def builder_up(data)
|
179
|
-
# Use Builder to generate the XML
|
180
182
|
buffer = ''
|
181
183
|
xml = Builder::XmlMarkup.new(:target => buffer)
|
182
184
|
xml.instruct!
|
@@ -184,6 +186,7 @@ module RESTRack
|
|
184
186
|
return buffer
|
185
187
|
end
|
186
188
|
|
189
|
+
# Builds the path to the builder file for the current controller action.
|
187
190
|
def builder_file
|
188
191
|
"#{RESTRack::CONFIG[:ROOT]}/views/#{@resource_request.resource_name.underscore}/#{@resource_request.action}.xml.builder"
|
189
192
|
end
|
@@ -1,11 +1,12 @@
|
|
1
1
|
module RESTRack
|
2
|
+
# The ResourceRequest class handles all incoming requests.
|
2
3
|
class ResourceRequest
|
3
4
|
attr_reader :request, :request_id, :input
|
4
5
|
attr_accessor :mime_type, :path_stack, :resource_name, :action, :id
|
5
6
|
|
7
|
+
# Initialize the ResourceRequest by assigning a request_id and determining the path, format, and controller of the resource.
|
8
|
+
# Accepting options to allow us to override request_id for testing.
|
6
9
|
def initialize(opts)
|
7
|
-
# Initialize the ResourceRequest by assigning a request_id and determining the path, format, and controller of the resource.
|
8
|
-
# Accepting options just to allow us to override request_id for testing.
|
9
10
|
@request = opts[:request]
|
10
11
|
@request_id = opts[:request_id] || get_request_id
|
11
12
|
|
@@ -28,21 +29,21 @@ module RESTRack
|
|
28
29
|
raise HTTP403Forbidden if not RESTRack::CONFIG[:ROOT_RESOURCE_DENY].blank? and RESTRack::CONFIG[:ROOT_RESOURCE_DENY].include?(@resource_name)
|
29
30
|
end
|
30
31
|
|
32
|
+
# Locate the correct controller of resource based on the request.
|
33
|
+
# The resource requested must be a member of RESTRack application or a 404 error will be thrown by RESTRack::WebService.
|
31
34
|
def locate
|
32
|
-
# Locate the correct controller of resource based on the request.
|
33
|
-
# The resource requested must be a member of RESTRack application or a 404 error will be thrown by RESTRack::WebService.
|
34
35
|
RESTRack.log.debug "{#{@request_id}} Locating Resource"
|
35
36
|
@resource = instantiate_controller
|
36
37
|
end
|
37
38
|
|
39
|
+
# Pass along the `call` method to the typed resource object, this must occur after a call to locate.
|
38
40
|
def call
|
39
|
-
# Pass along the `call` method to the typed resource object, this must occur after a call to locate.
|
40
41
|
RESTRack.log.debug "{#{@request_id}} Processing Request"
|
41
42
|
@resource.call
|
42
43
|
end
|
43
44
|
|
45
|
+
# Send out the typed resource's output, this must occur after a call to run.
|
44
46
|
def output
|
45
|
-
# Send out the typed resource's output, this must occur after a call to run.
|
46
47
|
RESTRack.log.debug "{#{@request_id}} Retrieving Output"
|
47
48
|
@resource.output
|
48
49
|
end
|
@@ -77,8 +78,8 @@ module RESTRack
|
|
77
78
|
input
|
78
79
|
end
|
79
80
|
|
81
|
+
# Remove the extension from the URL if present, that will be used to determine content-type.
|
80
82
|
def split_extension_from(path_stack)
|
81
|
-
# Remove the extension from the URL if present, that will be used to determine content-type.
|
82
83
|
extension = ''
|
83
84
|
unless path_stack.nil?
|
84
85
|
path_stack = path_stack.sub(/\.([^.]*)$/) do |s|
|
@@ -122,8 +123,8 @@ module RESTRack
|
|
122
123
|
[id, action, path_stack]
|
123
124
|
end
|
124
125
|
|
126
|
+
# Called from the locate method, this method dynamically finds the class based on the URI and instantiates an object of that class via the __init method on RESTRack::ResourceController.
|
125
127
|
def instantiate_controller
|
126
|
-
# Called from the locate method, this method dynamically finds the class based on the URI and instantiates an object of that class via the __init method on RESTRack::ResourceController.
|
127
128
|
begin
|
128
129
|
return RESTRack.controller_class_for( @resource_name ).__init(self)
|
129
130
|
rescue
|
data/lib/restrack/support.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
module RESTRack
|
2
|
+
require 'mime/types'
|
3
|
+
require 'yaml'
|
4
|
+
require 'logger'
|
2
5
|
|
3
6
|
class << self
|
4
7
|
def log; @@log; end
|
@@ -44,8 +47,8 @@ module RESTRack
|
|
44
47
|
|
45
48
|
end
|
46
49
|
|
47
|
-
# Courtesy of Rails' ActiveSupport, thank you DHH et al.
|
48
50
|
class Object
|
51
|
+
# Courtesy of Rails' ActiveSupport, thank you DHH et al.
|
49
52
|
def blank?
|
50
53
|
respond_to?(:empty?) ? empty? : !self
|
51
54
|
end
|
data/lib/restrack/version.rb
CHANGED
data/lib/restrack/web_service.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
module RESTRack
|
2
2
|
class WebService
|
3
3
|
|
4
|
+
# Establish the namespace pointer.
|
4
5
|
def initialize
|
5
|
-
# Establish the namespace pointer.
|
6
6
|
RESTRack::CONFIG[:SERVICE_NAME] = self.class.to_s.split('::')[0].to_sym
|
7
7
|
end
|
8
8
|
|
9
|
+
# Handle requests in the Rack way.
|
9
10
|
def call( env )
|
10
|
-
# Handle requests.
|
11
11
|
request = Rack::Request.new(env)
|
12
12
|
begin
|
13
13
|
resource_request = RESTRack::ResourceRequest.new( :request => request )
|
@@ -22,15 +22,15 @@ module RESTRack
|
|
22
22
|
|
23
23
|
private
|
24
24
|
|
25
|
+
# Return HTTP200OK SUCCESS
|
25
26
|
def valid( resource_request, response )
|
26
|
-
# Return HTTP200OK SUCCESS
|
27
27
|
RESTRack.request_log.debug "'#{resource_request.mime_type.to_s}' response data (Request ID: #{resource_request.request_id})\n" + response.to_s unless not response.respond_to?( :to_s )
|
28
28
|
RESTRack.request_log.info "HTTP200OK - (Request ID: #{resource_request.request_id})"
|
29
29
|
return [200, {'Content-Type' => resource_request.content_type}, response ]
|
30
30
|
end
|
31
31
|
|
32
|
+
# Return appropriate response code and messages per raised exception type.
|
32
33
|
def caught( resource_request, exception )
|
33
|
-
# Return appropriate response code and messages per raised exception type.
|
34
34
|
if resource_request && resource_request.request_id
|
35
35
|
RESTRack.request_log.info exception.message + "(Request ID: #{resource_request.request_id})"
|
36
36
|
else
|
data/lib/restrack.rb
CHANGED
data/test/test_support.rb
CHANGED
@@ -1,14 +1,18 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'test/unit'
|
3
|
-
require File.expand_path(File.join(File.dirname(__FILE__),'../lib/restrack'))
|
3
|
+
require File.expand_path(File.join(File.dirname(__FILE__),'../lib/restrack/support'))
|
4
4
|
|
5
5
|
module RESTRack
|
6
6
|
class TestSupport < Test::Unit::TestCase
|
7
7
|
|
8
|
+
RESTRack::CONFIG = RESTRack.load_config(File.expand_path(File.join(File.dirname(__FILE__),'../test/sample_app_1/config/constants.yaml')))
|
9
|
+
|
8
10
|
def test_constants
|
9
11
|
assert_nothing_raised do
|
10
12
|
RESTRack::CONFIG[:LOG].to_sym.to_s
|
13
|
+
RESTRack::CONFIG[:LOG_LEVEL].to_sym.to_s
|
11
14
|
RESTRack::CONFIG[:REQUEST_LOG].to_sym.to_s
|
15
|
+
RESTRack::CONFIG[:REQUEST_LOG_LEVEL].to_sym.to_s
|
12
16
|
RESTRack::CONFIG[:DEFAULT_FORMAT].to_sym.to_s
|
13
17
|
RESTRack::CONFIG[:DEFAULT_RESOURCE].to_sym.to_s
|
14
18
|
assert RESTRack::CONFIG[:ROOT_RESOURCE_ACCEPT].blank? || RESTRack::CONFIG[:ROOT_RESOURCE_ACCEPT].class == Array
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: restrack
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 21
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 5
|
10
|
+
version: 0.0.5
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Chris St. John
|
@@ -118,9 +118,9 @@ files:
|
|
118
118
|
- README.rdoc
|
119
119
|
- Rakefile
|
120
120
|
- bin/restrack
|
121
|
-
- config/constants.yaml
|
122
121
|
- lib/restrack.rb
|
123
122
|
- lib/restrack/generator.rb
|
123
|
+
- lib/restrack/generator/config.ru.erb
|
124
124
|
- lib/restrack/generator/constants.yaml.erb
|
125
125
|
- lib/restrack/generator/controller.rb.erb
|
126
126
|
- lib/restrack/generator/loader.rb.erb
|
data/config/constants.yaml
DELETED
@@ -1,8 +0,0 @@
|
|
1
|
-
:LOG: '/var/log/restrack/restrack.log'
|
2
|
-
:REQUEST_LOG: '/var/log/restrack/restrack.request.log'
|
3
|
-
:LOG_LEVEL: DEBUG # Logger object level
|
4
|
-
:REQUEST_LOG_LEVEL: DEBUG # Logger object level
|
5
|
-
:DEFAULT_FORMAT: :JSON # Supported formats are :JSON, :XML, :YAML, :BIN, :TEXT
|
6
|
-
:DEFAULT_RESOURCE: '' # The resource which will handle root level requests where the name is not specified. Best for users of this not to implement method_missing in their default controller, unless they are checking for bad URI. :DEFAULT_RESOURCE should be a member of :ROOT_RESOURCE_ACCEPT.
|
7
|
-
:ROOT_RESOURCE_ACCEPT: [] # These are the resources which can be accessed from the root of your web service. If left empty, all resources are available at the root.
|
8
|
-
:ROOT_RESOURCE_DENY: [] # These are the resources which cannot be accessed from the root of your web service. Use either this or ROOT_RESOURCE_ACCEPT as a blacklist or whitelist to establish routing (relationships defined in resource controllers define further routing).
|