restrack 0.0.6 → 0.1.0

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.
data/README.rdoc CHANGED
@@ -3,8 +3,7 @@
3
3
  == Description:
4
4
  RESTRack is a Rack based MVC framework that makes it extremely easy to develop RESTful data services. It is inspired
5
5
  by Rails, and follows a few of its conventions. But it has no routes file, routing relationships are done through
6
- supplying custom code blocks to class methods such as 'has_relationship_to', 'has_mapped_relationships_to',
7
- 'has_direct_relationship_to', and 'has_direct_relationships_to'.
6
+ supplying custom code blocks to class methods such as 'has_relationship_to' or 'has_mapped_relationships_to'.
8
7
  RESTRack aims at being lightweight and easy to use. It will automatically render JSON and XML for the data
9
8
  structures you return in your actions (any structure parsable by the 'json' and 'xml-simple' gems, respectively).
10
9
  If you supply a view for a controller action, you do that using a builder file. Builder files are stored in the
@@ -55,34 +54,39 @@
55
54
 
56
55
  An open, or pass-through, path can be defined via the 'pass_through_to' class method for resource controllers. This
57
56
  exposes URL patterns like the following:
58
- GET /foo/123/bar/234 <= simple pass-through from Foo 123 to show Bar 234
59
- GET /foo/123/bar <= simple pass-through from Foo 123 to Bar index
57
+ GET /foo/123/bar/234 <= simple pass-through from Foo 123 to show Bar 234
58
+ GET /foo/123/bar <= simple pass-through from Foo 123 to Bar index
60
59
 
61
60
  A direct path to a single related resource's controller can be defined with the 'has_relationship_to' method. This
62
61
  allows you to define a one-to-one relationship from this resource to a related resource, which means that the id of
63
- the related resource is implied through the id of the caller. The caller has one and only one relation through a
64
- custom code block passed to 'has_relationship_to'. The code block takes the caller resource's id and evaluates to the
65
- relation resource's id, for example a PeopleController might define a one-to-one relationship like so:
62
+ the related resource is implied through the id of the caller. The caller has one relation through a custom code block
63
+ passed to 'has_relationship_to'. The code block takes the caller resource's id and evaluates to the relation
64
+ resource's id, for example a PeopleController might define a one-to-one relationship like so:
66
65
  has_relationship_to( :people, :as spouse ) do |id|
67
66
  People.find(id).spouse.id
68
67
  end
69
68
  This exposes URL patterns like
70
69
  the following:
71
- GET /people/Sally/spouse <= direct route to show Sally's spouse
72
- PUT /people/Henry/spouse <= direct route to update Henry's spouse
73
- POST /people/Jane/spouse <= direct route to add Jane's spouse
70
+ GET /people/Sally/spouse <= direct route to show Sally's spouse
71
+ PUT /people/Henry/spouse <= direct route to update Henry's spouse
72
+ POST /people/Jane/spouse <= direct route to add Jane's spouse
74
73
 
75
- # TODO: need has_relationships_to and has_direct_relationships_to
76
- A direct path to many related resources' controller can be defined with the 'has_relationships_to' method. This
77
- allows you to define one-to-many relationships. It works similar to 'has_relationship_to', except that the code block
78
- passed to it should evaluate to an array of the related child ids. Each resource in the parent's relation list is
79
- then accessed through its array index (zero-based) in the URL. An example of exposing the list of a People resource's
80
- children in this manner follows:
81
- has_direct_relationships_to( :people, :as => children ) do |id|
74
+ A direct path to many related resources' controller can be defined with the 'has_relationships_to' and
75
+ 'has_defined_relationships_to' methods. These allows you to define one-to-many relationships. They work similar to
76
+ 'has_relationship_to', except that they accept code blocks which evaluate to arrays of related child ids. Each
77
+ resource in the parent's relation list is then accessed through its array index (zero-based) in the URL. An example
78
+ of exposing the list of a People resource's children in this manner follows:
79
+ has_relationships_to( :people, :as => children ) do |id|
82
80
  People.find(id).children.collect {|child| child.id}
83
81
  end
84
- GET /people/Nancy/children/0 <= direct route to show child 0
85
- DELETE /people/Robert/children/100 <= direct route to destroy child 100
82
+ GET /people/Nancy/children/0 <= direct route to show child 0
83
+ DELETE /people/Robert/children/100 <= direct route to destroy child 100
84
+
85
+ has_defined_relationships_to( :people, :as => children ) do |id|
86
+ People.find(id).children.collect {|child| child.id}
87
+ end
88
+ GET /people/Nancy/children/George <= direct route to show child 0
89
+ DELETE /people/Robert/children/John <= direct route to destroy child 100
86
90
 
87
91
  Multiple named one-to-many relationships can be exposed with the 'has_mapped_relationships_to' method. This allows
88
92
  you to define many named or keyword paths to related resources. The method's code block should accepts the parent id
@@ -97,17 +101,17 @@
97
101
  }
98
102
  end
99
103
  This would expose the following URL patterns:
100
- GET /people/Fred/people/father => show the father of Fred
101
- PUT /people/Fred/people/assistant => update Fred's assistant
102
- POST /people/Fred/people/boss => add Fred's boss
103
- DELETE /people/Luke/people/mother => destroy Luke's father
104
+ GET /people/Fred/people/father => show the father of Fred
105
+ PUT /people/Fred/people/assistant => update Fred's assistant
106
+ POST /people/Fred/people/boss => add Fred's boss
107
+ DELETE /people/Luke/people/mother => destroy Luke's father
104
108
 
105
109
 
106
110
  Resource id data types can be defined with the keyed_with_type class method within resource controllers. The
107
111
  default data type of String is used if a different type is not specified.
108
112
 
109
113
 
110
- == How-Tos
114
+ == Miscellaneous Details
111
115
 
112
116
  === Logging/Logging Level
113
117
  RESTRack logs to two logs, the standard log (or error log) and the request log. Paths and logging levels for these
@@ -125,16 +129,20 @@
125
129
  Custom XML serialization can be done by providing Builder gem templates in views/<controller>/<action>.xml.builder
126
130
 
127
131
  === Inputs
128
- ==== GET params
132
+ ==== Query string parameters
133
+ Available to controllers in the @params instance variable.
129
134
  ==== POST data
130
-
131
- === TODO: :DEFAULT_RESOURCE
132
- ...yet to be done...
133
- === TODO: :ROOT_RESOURCE_ACCEPT/:ROOT_RESOURCE_DENY
134
- ...yet to be done...
135
- === TODO: Authentication/Authorization Suggestions
136
- ...yet to be done...
135
+ Available to controllers in the @input instance variable.
136
+ === :DEFAULT_RESOURCE
137
+ Set this option in config/constants.yaml to use an implied root resource controller.
138
+ :DEFAULT_RESOURCE: foo # /foo/123 could be accessed with /123, /foo could be accessed with /
137
139
 
140
+ === :ROOT_RESOURCE_ACCEPT / :ROOT_RESOURCE_DENY
141
+ :ROOT_RESOURCE_ACCEPT: [ 'foo', 'bar' ] # OPTIONAL
142
+ defines an array of resources that can be accessed (without being proxied through another relation).
143
+ :ROOT_RESOURCE_DENY: [ 'baz' ] # OPTIONAL
144
+ defines an array of resources that cannot be accessed without proxying though another controller.
145
+
138
146
 
139
147
  == License:
140
148
 
data/bin/restrack CHANGED
@@ -14,7 +14,7 @@ when :generate, :gen, :g
14
14
  puts "Generating new RESTRack service #{name}..."
15
15
  RESTRack::Generator.generate_service( name )
16
16
  when :controller, :cont, :c
17
- predicate = ARGV[3].to_sym
17
+ predicate = ARGV[3] ? ARGV[3].to_sym : nil
18
18
  case predicate
19
19
  when :descendant_from, :parent
20
20
  parent = ARGV[4]
@@ -20,6 +20,9 @@ module RESTRack
20
20
  end
21
21
  def __init(resource_request)
22
22
  @resource_request = resource_request
23
+ @request = @resource_request.request
24
+ @params = @resource_request.params
25
+ @input = @resource_request.input
23
26
  self
24
27
  end
25
28
 
@@ -163,7 +166,6 @@ module RESTRack
163
166
  def self.format_string_id(id)
164
167
  return nil unless id
165
168
  # default key type of resources is String
166
- # TODO: Should this be set by service in config/constants.yaml?
167
169
  self.key_type ||= String
168
170
  unless self.key_type.blank? or self.key_type.ancestors.include?(String)
169
171
  if self.key_type.ancestors.include?(Integer)
@@ -1,7 +1,7 @@
1
1
  module RESTRack
2
2
  # The ResourceRequest class handles all incoming requests.
3
3
  class ResourceRequest
4
- attr_reader :request, :request_id, :input
4
+ attr_reader :request, :request_id, :input, :params
5
5
  attr_accessor :mime_type, :url_chain
6
6
 
7
7
  # Initialize the ResourceRequest by assigning a request_id and determining the path, format, and controller of the resource.
@@ -13,7 +13,8 @@ module RESTRack
13
13
  RESTRack.request_log.info "{#{@request_id}} #{@request.path_info} requested from #{@request.ip}"
14
14
  RESTRack.log.debug "{#{@request_id}} Reading POST Input"
15
15
  # Pull input data from POST body
16
- @input = read( @request )
16
+ @input = parse_body( @request )
17
+ @params = get_params( @request )
17
18
  # Setup up the initial routing.
18
19
  @url_chain = @request.path_info.split('/')
19
20
  @url_chain.shift if @url_chain[0] == ''
@@ -66,25 +67,25 @@ module RESTRack
66
67
  end
67
68
 
68
69
  # Pull input data from POST body
69
- def read(request)
70
- input = ''
71
- if request.content_type.blank?
72
- input = request.body.read
73
- else
70
+ def parse_body(request)
71
+ input = request.body.read
72
+ unless request.content_type.blank?
74
73
  request_mime_type = MIME::Type.new( request.content_type )
75
74
  if request_mime_type.like?( RESTRack.mime_type_for( :JSON ) )
76
- input = JSON.parse( request.body.read )
75
+ input = JSON.parse( input )
77
76
  elsif request_mime_type.like?( RESTRack.mime_type_for( :XML ) )
78
- input = XmlSimple.xml_in( request.body.read )
77
+ input = XmlSimple.xml_in( input )
79
78
  elsif request_mime_type.like?( RESTRack.mime_type_for( :YAML ) )
80
- input = YAML.parse( request.body.read )
81
- else
82
- input = request.body.read
79
+ input = YAML.parse( input )
83
80
  end
84
- RESTRack.request_log.debug "{#{@request_id}} #{request_mime_type.to_s} data in\n" + input.to_json
85
81
  end
82
+ RESTRack.request_log.debug "{#{@request_id}} #{request_mime_type.to_s} data in\n" + input.pretty_inspect
86
83
  input
87
84
  end
85
+
86
+ def get_params(request)
87
+ params = request.GET
88
+ end
88
89
 
89
90
  # Determine the MIME type of the request from the extension provided.
90
91
  def get_mime_type_from(extension)
@@ -119,7 +120,7 @@ module RESTRack
119
120
  if File.exists? builder_file
120
121
  @output = builder_up(data)
121
122
  else
122
- @output = XmlSimple.xml_out(data, 'AttrPrefix' => true, 'XmlDeclaration' => true)
123
+ @output = XmlSimple.xml_out(data, 'AttrPrefix' => true, 'XmlDeclaration' => true, 'NoIndent' => true)
123
124
  end
124
125
  elsif @mime_type.like?(RESTRack.mime_type_for( :YAML ) )
125
126
  @output = YAML.dump(data)
@@ -1,3 +1,3 @@
1
1
  module RESTRack
2
- VERSION = "0.0.6"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -17,9 +17,9 @@
17
17
  :DEFAULT_FORMAT: :JSON
18
18
  # 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.
19
19
  # This setting ('bazu') won't work because of :ROOT_RESOURCE_ACCEPT VALUE (:DEFAULT_RESOURCE should be a member of :ROOT_RESOURCE_ACCEPT).
20
- :DEFAULT_RESOURCE: 'bazu'
20
+ :DEFAULT_RESOURCE: bazu
21
21
 
22
22
  # 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.
23
- :ROOT_RESOURCE_ACCEPT: [ 'foo_bar' ]
23
+ :ROOT_RESOURCE_ACCEPT: [ foo_bar ]
24
24
  # 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).
25
- :ROOT_RESOURCE_DENY: [ 'baz' ]
25
+ :ROOT_RESOURCE_DENY: [ baz ]
@@ -78,8 +78,30 @@ class SampleApp::FooBarController < RESTRack::ResourceController
78
78
  { :success => true }
79
79
  end
80
80
 
81
+ def complex_show_xml_no_builder(id)
82
+ if id == '1234567890'
83
+ return { :foo => 'abc', :bar => '123', 'baz' => 456, :more => { :one => 1, :two => [1,2], :three => :deep_fu } }
84
+ end
85
+ if id == '42'
86
+ return {
87
+ :foo => 'abc',
88
+ :bar => 123,
89
+ :baz => {
90
+ 'one' => [1],
91
+ 'two' => ['1','2'],
92
+ 'three' => ['1', 2, {:three => 3}],
93
+ 4 => :four
94
+ }
95
+ }
96
+ end
97
+ end
98
+
81
99
  def echo
82
- return @resource_request.input
100
+ return @input
101
+ end
102
+
103
+ def echo_get
104
+ return @params.merge({ 'get?' => @resource_request.request.get?.to_s })
83
105
  end
84
106
 
85
107
  def custom_entity(id)
@@ -10,6 +10,30 @@ class SampleApp::TestControllerInputs < Test::Unit::TestCase
10
10
  @ws = SampleApp::WebService.new
11
11
  end
12
12
 
13
+ def test_get_params
14
+ env = Rack::MockRequest.env_for('/foo_bar/echo_get?test=1&hello=world', {
15
+ :method => 'GET'
16
+ })
17
+ output = ''
18
+ assert_nothing_raised do
19
+ output = @ws.call(env)
20
+ end
21
+ test_val = { :test => '1', :hello => 'world', 'get?' => 'true' }.to_json
22
+ assert_equal test_val, output[2]
23
+ end
24
+
25
+ def test_FUBAR_params
26
+ env = Rack::MockRequest.env_for('/foo_bar/echo_get?test=1&hello=world', {
27
+ :method => 'DELETE'
28
+ })
29
+ output = ''
30
+ assert_nothing_raised do
31
+ output = @ws.call(env)
32
+ end
33
+ test_val = { :test => '1', :hello => 'world', 'get?' => 'false' }.to_json
34
+ assert_equal test_val, output[2]
35
+ end
36
+
13
37
  def test_post_no_content_type
14
38
  test_val = "random text" # will be converted to json because of default response type
15
39
  env = Rack::MockRequest.env_for('/foo_bar/echo', {
@@ -36,6 +60,32 @@ class SampleApp::TestControllerInputs < Test::Unit::TestCase
36
60
  end
37
61
  assert_equal test_val, output[2]
38
62
  end
39
- # TODO: Test all input formats
40
-
63
+
64
+ def test_post_xml
65
+ test_val = XmlSimple.xml_out({ :echo => 'niner' }, 'AttrPrefix' => true, 'XmlDeclaration' => true, 'NoIndent' => true)
66
+ env = Rack::MockRequest.env_for('/foo_bar/echo.xml', {
67
+ :method => 'POST',
68
+ :input => test_val,
69
+ 'CONTENT_TYPE' => 'application/xml'
70
+ })
71
+ output = ''
72
+ assert_nothing_raised do
73
+ output = @ws.call(env)
74
+ end
75
+ assert_equal test_val, output[2]
76
+ end
77
+
78
+ def test_post_text
79
+ test_val = 'OPCODE=PEBKAC'
80
+ env = Rack::MockRequest.env_for('/foo_bar/echo.txt', {
81
+ :method => 'POST',
82
+ :input => test_val,
83
+ 'CONTENT_TYPE' => 'text/plain'
84
+ })
85
+ output = ''
86
+ assert_nothing_raised do
87
+ output = @ws.call(env)
88
+ end
89
+ assert_equal test_val, output[2]
90
+ end
41
91
  end
@@ -30,7 +30,7 @@ class SampleApp::TestFormats < Test::Unit::TestCase
30
30
  assert_nothing_raised do
31
31
  output = @ws.call(env)
32
32
  end
33
- test_val = XmlSimple.xml_out([1,2,3,4,5,6,7], 'AttrPrefix' => true, 'XmlDeclaration' => true)
33
+ test_val = XmlSimple.xml_out([1,2,3,4,5,6,7], 'AttrPrefix' => true, 'XmlDeclaration' => true, 'NoIndent' => true)
34
34
  assert_equal test_val, output[2]
35
35
 
36
36
  env = Rack::MockRequest.env_for('/foo_bar.xml', {
@@ -40,7 +40,7 @@ class SampleApp::TestFormats < Test::Unit::TestCase
40
40
  assert_nothing_raised do
41
41
  output = @ws.call(env)
42
42
  end
43
- test_val = XmlSimple.xml_out([1,2,3,4,5,6,7], 'AttrPrefix' => true, 'XmlDeclaration' => true)
43
+ test_val = XmlSimple.xml_out([1,2,3,4,5,6,7], 'AttrPrefix' => true, 'XmlDeclaration' => true, 'NoIndent' => true)
44
44
  assert_equal test_val, output[2]
45
45
  end
46
46
 
@@ -88,25 +88,24 @@ class SampleApp::TestFormats < Test::Unit::TestCase
88
88
  end
89
89
 
90
90
  def test_complex_data_structure_xml
91
- skip
92
- env = Rack::MockRequest.env_for('/foo_bar/1234567890.xml', {
91
+ env = Rack::MockRequest.env_for('/foo_bar/1234567890/complex_show_xml_no_builder.xml', {
93
92
  :method => 'GET'
94
93
  })
95
94
  output = ''
96
95
  assert_nothing_raised do
97
96
  output = @ws.call(env)
98
97
  end
99
- test_val = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><data><foo>abc</foo><baz>456</baz><bar>123</bar><more><two></data>"
98
+ test_val = "<?xml version='1.0' standalone='yes'?>\n<opt><foo>abc</foo><bar>123</bar><baz>456</baz><more><one>1</one><two>1</two><two>2</two><three>deep_fu</three></more></opt>"
100
99
  assert_equal test_val, output[2]
101
100
 
102
- env = Rack::MockRequest.env_for('/foo_bar/42.xml', {
101
+ env = Rack::MockRequest.env_for('/foo_bar/42/complex_show_xml_no_builder.xml', {
103
102
  :method => 'GET'
104
103
  })
105
104
  output = ''
106
105
  assert_nothing_raised do
107
106
  output = @ws.call(env)
108
107
  end
109
- test_val = {
108
+ test_val = XmlSimple.xml_out({
110
109
  :foo => 'abc',
111
110
  :bar => 123,
112
111
  :baz => {
@@ -115,7 +114,7 @@ class SampleApp::TestFormats < Test::Unit::TestCase
115
114
  'three' => ['1', 2, {:three => 3}],
116
115
  4 => :four
117
116
  }
118
- }
117
+ }, 'AttrPrefix' => true, 'XmlDeclaration' => true, 'NoIndent' => true)
119
118
  assert_equal test_val, output[2]
120
119
  end
121
120
 
@@ -16,9 +16,9 @@
16
16
  # Supported formats are :JSON, :XML, :YAML, :BIN, :TEXT
17
17
  :DEFAULT_FORMAT: :JSON
18
18
  # 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.
19
- :DEFAULT_RESOURCE: 'bazu'
19
+ :DEFAULT_RESOURCE: bazu
20
20
 
21
21
  # 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.
22
- :ROOT_RESOURCE_ACCEPT: [ 'baz' ]
22
+ :ROOT_RESOURCE_ACCEPT: [ baz ]
23
23
  # 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).
24
24
  #:ROOT_RESOURCE_DENY: []
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
+ - 1
7
8
  - 0
8
- - 6
9
- version: 0.0.6
9
+ version: 0.1.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Chris St. John