restrack 0.0.6 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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