restrack 0.0.5 → 0.0.6

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
@@ -1,20 +1,15 @@
1
1
  = RESTRack
2
2
 
3
3
  == Description:
4
- RESTRack is a Rack based MVC framework that makes it extremely easy to
5
- develop RESTful data services. It is inspired by Rails, and follows a few of
6
- its conventions. But it has no routes file, routing relationships are done
7
- through supplying custom code blocks to class methods such as
8
- 'has_relationship_to', 'has_mapped_relationships_to',
4
+ RESTRack is a Rack based MVC framework that makes it extremely easy to develop RESTful data services. It is inspired
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',
9
7
  'has_direct_relationship_to', and 'has_direct_relationships_to'.
10
- RESTRack aims at being lightweight and easy to use. It will
11
- automatically render JSON and XML for the data structures you return in your
12
- actions (any structure parsable by the 'json' and 'xml-simple' gems,
13
- respectively).
14
- If you supply a view for a controller action, you do that using a builder
15
- file (view/<controller>/<action>.xml.builder). XML format requests will then
16
- render the view template with the builder gem, rather than generating XML with
17
- XmlSimple.
8
+ RESTRack aims at being lightweight and easy to use. It will automatically render JSON and XML for the data
9
+ structures you return in your actions (any structure parsable by the 'json' and 'xml-simple' gems, respectively).
10
+ If you supply a view for a controller action, you do that using a builder file. Builder files are stored in the
11
+ view directory grouped by controller name subdirectories (view/<controller>/<action>.xml.builder). XML format
12
+ requests will then render the view template with the builder gem, rather than generating XML with XmlSimple.
18
13
 
19
14
 
20
15
  == Installation:
@@ -22,63 +17,138 @@
22
17
 
23
18
 
24
19
  == Why RESTRack when there is Rails?
25
- Rails is a powerful tool for full web applications. RESTRack is targeted at
26
- making development of lightweight data services as easy as possible, while
27
- still giving you a performant and extensible framework. The primary goal of
28
- the framework was to add as little as possible to the framework to give the
29
- web developer a good application space for developing JSON or XML services.
20
+ Rails is a powerful tool for full web applications. RESTRack is targeted at making development of lightweight data
21
+ services as easy as possible, while still giving you a performant and extensible framework. The primary goal of
22
+ of the development of RESTRack was to add as little as possible to the framework to give the web developer a good
23
+ application space for developing JSON and XML services.
30
24
 
25
+ Rails 3 instantiates approximately 80K more objects than RESTRack to do a hello world or nothing type response with
26
+ the default setup. Trimming Rails down to just ActionController, by eliminating ActiveRecord, ActionMailer, and
27
+ ActiveResource, it still instantiates over 47K more objects than RESTRack.
31
28
 
32
- == Usage:
29
+
30
+ == CLI Usage:
33
31
  - restrack generate service foo_bar
32
+ - restrack gen serv foo_bar
33
+ - restrack g s foo_bar
34
34
  - restrack generate controller baz
35
+ - restrack gen cont baz
36
+ - restrack g c baz
37
+ - restrack server # default rackup port 9292
35
38
  - restrack server 3456
39
+ - restrack s 3456
36
40
 
37
41
 
38
42
  == REST action method names
39
- All default RESTful controller method names align with their Rails
40
- counterparts, with two additional actions being supported(*).
43
+ All default RESTful controller method names align with their Rails counterparts, with two additional actions being
44
+ supported(*).
41
45
 
42
46
  # HTTP Verb: | GET | PUT | POST | DELETE
43
47
  # Collection URI (/widgets/): | index | replace | create | *drop
44
48
  # Element URI (/widgets/42): | show | update | *add | destroy
45
49
 
46
50
 
47
- == How-Tos
51
+ == URLs and Controller relationships
52
+ RESTRack enforces a strict URL pattern through the contruct of controller relationships, rather than a routing file.
53
+ Defining a controller for a resource means that you plan to expose that resource to requests to your service.
54
+ Defining a controller relationship means that you plan to expose a path from this resource to another.
55
+
56
+ An open, or pass-through, path can be defined via the 'pass_through_to' class method for resource controllers. This
57
+ 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
60
+
61
+ A direct path to a single related resource's controller can be defined with the 'has_relationship_to' method. This
62
+ 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:
66
+ has_relationship_to( :people, :as spouse ) do |id|
67
+ People.find(id).spouse.id
68
+ end
69
+ This exposes URL patterns like
70
+ 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
74
+
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|
82
+ People.find(id).children.collect {|child| child.id}
83
+ 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
86
+
87
+ Multiple named one-to-many relationships can be exposed with the 'has_mapped_relationships_to' method. This allows
88
+ you to define many named or keyword paths to related resources. The method's code block should accepts the parent id
89
+ and return a hash where the keys are your relationship names and the values are the child resource ids. For example,
90
+ within a PeopleController the following definition:
91
+ has_mapped_relationships_to( :people ) do |id|
92
+ {
93
+ 'father' => People.find(id).father.id,
94
+ 'mother' => People.find(id).mother.id,
95
+ 'boss' => People.find(id).boss.id,
96
+ 'assistant' => People.find(id).assistant.id
97
+ }
98
+ end
99
+ 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
+
105
+
106
+ Resource id data types can be defined with the keyed_with_type class method within resource controllers. The
107
+ default data type of String is used if a different type is not specified.
48
108
 
49
- ...yet to be done...
50
- === Authentication Suggestions
109
+
110
+ == How-Tos
51
111
 
52
112
  === Logging/Logging Level
113
+ RESTRack logs to two logs, the standard log (or error log) and the request log. Paths and logging levels for these
114
+ can be configured in config/constants.yaml. RESTRack uses Logger from Ruby-stdlib.
53
115
 
54
116
  === XML Serialization
55
- ==== With Builder
117
+ RESTRack will convert the data structures that your actions return to JSON by default. You can change the default
118
+ by setting :DEFAULT_FORMAT to :XML in config/constants.yml.
119
+
56
120
  ==== With XmlSimple
57
- @output = XmlSimple.xml_out(data, 'AttrPrefix' => true)
121
+ RESTRack will attempt to serialize the data structures that your action methods return automatically using the
122
+ xml-simple gem.
123
+
124
+ ==== With Builder
125
+ Custom XML serialization can be done by providing Builder gem templates in views/<controller>/<action>.xml.builder
126
+
58
127
  === Inputs
59
128
  ==== GET params
60
129
  ==== POST data
61
130
 
62
-
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...
137
+
63
138
 
64
139
  == License:
65
140
 
66
141
  Copyright (c) 2010 Chris St. John
67
142
 
68
- Permission is hereby granted, free of charge, to any person obtaining a copy
69
- of this software and associated documentation files (the "Software"), to deal
70
- in the Software without restriction, including without limitation the rights
71
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
72
- copies of the Software, and to permit persons to whom the Software is
73
- furnished to do so, subject to the following conditions:
74
-
75
- The above copyright notice and this permission notice shall be included in
76
- all copies or substantial portions of the Software.
77
-
78
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
79
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
80
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
81
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
82
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
83
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
84
- THE SOFTWARE.
143
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
144
+ documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
145
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
146
+ persons to whom the Software is furnished to do so, subject to the following conditions:
147
+
148
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
149
+ Software.
150
+
151
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
152
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
153
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
154
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/bin/restrack CHANGED
@@ -2,22 +2,31 @@
2
2
  require 'rubygems'
3
3
  require 'restrack'
4
4
 
5
- verb = ARGV[0]
5
+ verb = ARGV[0].to_sym
6
6
  noun = ARGV[1]
7
7
 
8
- case verb.to_sym
9
- when :generate
8
+
9
+ case verb
10
+ when :generate, :gen, :g
10
11
  name = ARGV[2]
11
12
  case noun.to_sym
12
- when :service
13
+ when :service, :serv, :s
13
14
  puts "Generating new RESTRack service #{name}..."
14
15
  RESTRack::Generator.generate_service( name )
15
- when :controller
16
- puts "Generating new controller #{name}..."
17
- RESTRack::Generator.generate_controller( name )
16
+ when :controller, :cont, :c
17
+ predicate = ARGV[3].to_sym
18
+ case predicate
19
+ when :descendant_from, :parent
20
+ parent = ARGV[4]
21
+ puts "Generating new controller #{name} which is descendant from #{parent}..."
22
+ RESTRack::Generator.generate_descendant_controller( name, parent )
23
+ else
24
+ puts "Generating new controller #{name}..."
25
+ RESTRack::Generator.generate_controller( name )
26
+ end
18
27
  end
19
28
  puts 'Creation is complete.'
20
- when :server
29
+ when :server, :s
21
30
  options = { :Port => noun || 9292, :config => 'config.ru' }
22
31
  Rack::Server.start( options )
23
32
  end
@@ -0,0 +1,35 @@
1
+ class <%= @service_name.camelize %>::<%= @name.camelize %>Controller < <%= @service_name.camelize %>::<%= @parent.camelize %>Controller
2
+
3
+ def index
4
+
5
+ end
6
+
7
+ def create
8
+
9
+ end
10
+
11
+ def replace
12
+
13
+ end
14
+
15
+ def destroy
16
+
17
+ end
18
+
19
+ def show(id)
20
+
21
+ end
22
+
23
+ def update(id)
24
+
25
+ end
26
+
27
+ def delete(id)
28
+
29
+ end
30
+
31
+ def add(id)
32
+
33
+ end
34
+
35
+ end
@@ -5,11 +5,13 @@ require 'active_support/inflector'
5
5
 
6
6
  module RESTRack
7
7
  class Generator
8
+
8
9
  TEMPLATE = {
9
- :service => 'loader.rb.erb',
10
- :rackup => 'config.ru.erb',
11
- :constants => 'constants.yaml.erb',
12
- :controller => 'controller.rb.erb'
10
+ :service => 'loader.rb.erb',
11
+ :rackup => 'config.ru.erb',
12
+ :constants => 'constants.yaml.erb',
13
+ :controller => 'controller.rb.erb',
14
+ :descendant_controller => 'descendant_controller.rb.erb'
13
15
  }
14
16
 
15
17
  class << self
@@ -20,7 +22,17 @@ module RESTRack
20
22
  resultant_string = template.result( get_binding_for_controller( name ) )
21
23
  File.open("#{base_dir}/controllers/#{name}_controller.rb", 'w') {|f| f.puts resultant_string }
22
24
  # Generate view folder for controller
23
- FileUtils.makedirs("#{name}/views")
25
+ FileUtils.makedirs("#{base_dir}/views/#{name}")
26
+ end
27
+
28
+ # Generate controller file the descends from specified parent, to enable
29
+ # grouping of controller types and/or overarching functionality.
30
+ def generate_descendant_controller(name, parent)
31
+ template = get_template_for( :descendant_controller )
32
+ resultant_string = template.result( get_binding_for_descendant_controller( name, parent ) )
33
+ File.open("#{base_dir}/controllers/#{name}_controller.rb", 'w') {|f| f.puts resultant_string }
34
+ # Generate view folder for controller
35
+ FileUtils.makedirs("#{base_dir}/views/#{name}")
24
36
  end
25
37
 
26
38
  # Generate a new RESTRack service
@@ -56,6 +68,13 @@ module RESTRack
56
68
  @service_name = get_service_name
57
69
  binding
58
70
  end
71
+
72
+ def get_binding_for_descendant_controller(name, parent)
73
+ @name = name
74
+ @parent = parent
75
+ @service_name = get_service_name
76
+ binding
77
+ end
59
78
 
60
79
  def get_binding_for_service(name)
61
80
  @service_name = name
@@ -1,21 +1,16 @@
1
1
  module RESTRack
2
- # All RESTRack controllers descend from ResourceController.
3
2
 
3
+ # All RESTRack controllers should descend from ResourceController. This class
4
+ # provides the methods for your controllers.
5
+ #
4
6
  # HTTP Verb: | GET | PUT | POST | DELETE
5
7
  # Collection URI (/widgets/): | index | replace | create | drop
6
8
  # 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
9
+ #
16
10
 
17
11
  class ResourceController
18
- attr_reader :input, :output
12
+ attr_reader :action, :id
13
+ class << self; attr_accessor :key_type; end
19
14
 
20
15
  # Base initialization method for resources and storage of request input
21
16
  # This method should not be overriden in decendent classes.
@@ -25,35 +20,39 @@ module RESTRack
25
20
  end
26
21
  def __init(resource_request)
27
22
  @resource_request = resource_request
28
- setup_action
29
23
  self
30
24
  end
31
25
 
32
- # Call the controller's action and return it in the proper format.
26
+ # Call the controller's action and return output in the proper format.
33
27
  def call
28
+ self.determine_action
34
29
  args = []
35
- args << @resource_request.id unless @resource_request.id.blank?
36
- package( self.send(@resource_request.action.to_sym, *args) )
30
+ args << @id unless @id.blank?
31
+ self.send(@action.to_sym, *args)
37
32
  end
38
33
 
34
+ #def index; end
35
+ #def replace; end
36
+ #def create; end
37
+ #def drop; end
38
+ #def show(id); end
39
+ #def update(id); end
40
+ #def add(id); end
41
+ #def destroy(id); end
42
+
39
43
  def method_missing(method_sym, *arguments, &block)
40
44
  raise HTTP405MethodNotAllowed, 'Method not provided on controller.'
41
45
  end
42
46
 
43
- protected # all internal methods are protected rather than private so that calling methods *could* be overriden if necessary.
47
+ # all internal methods are protected rather than private so that calling methods *could* be overriden if necessary.
48
+ protected
49
+
44
50
  # This method allows one to access a related resource, without providing a direct link to specific relation(s).
45
- def self.has_relationship_to(entity, opts = {})
51
+ def self.pass_through_to(entity, opts = {})
46
52
  entity_name = opts[:as] || entity
47
53
  define_method( entity_name.to_sym,
48
- Proc.new do
49
- @resource_request.id, @resource_request.action = nil, nil
50
- ( @resource_request.id, @resource_request.action, @resource_request.path_stack ) = @resource_request.path_stack.split('/', 3) unless @resource_request.path_stack.blank?
51
- if [ :index, :replace, :create, :destroy ].include? @resource_request.id
52
- @resource_request.action = @resource_request.id
53
- @resource_request.id = nil
54
- end
55
- format_id
56
- self.call_relation(entity)
54
+ Proc.new do |calling_id| # The calling resource's id will come along for the ride when the new bridging method is called magically from ResourceController#call
55
+ @resource_request.call_controller(entity)
57
56
  end
58
57
  )
59
58
  end
@@ -62,33 +61,54 @@ module RESTRack
62
61
  # The second parameter is an options hash to support setting the local name of the relation via ':as => :foo'.
63
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.
64
63
  # This adds an accessor instance method whose name is the entity's class.
65
- def self.has_direct_relationship_to(entity, opts = {}, &get_entity_id_from_relation_id)
64
+ def self.has_relationship_to(entity, opts = {}, &get_entity_id_from_relation_id)
66
65
  entity_name = opts[:as] || entity
67
66
  define_method( entity_name.to_sym,
68
- Proc.new do
69
- @resource_request.id = get_entity_id_from_relation_id.call(@resource_request.id)
70
- @resource_request.action = nil
71
- ( @resource_request.action, @resource_request.path_stack ) = @resource_request.path_stack.split('/', 3) unless @resource_request.path_stack.blank?
72
- format_id
73
- self.call_relation(entity)
67
+ Proc.new do |calling_id| # The calling resource's id will come along for the ride when the new bridging method is called magically from ResourceController#call
68
+ id = get_entity_id_from_relation_id.call(@id)
69
+ @resource_request.url_chain.unshift(id)
70
+ @resource_request.call_controller(entity)
74
71
  end
75
72
  )
76
73
  end
77
74
 
78
75
  # This method defines that there are multiple links to members from an entity collection (an array of entity identifiers).
79
76
  # This adds an accessor instance method whose name is the entity's class.
80
- def self.has_direct_relationships_to(entity, opts = {}, &get_entity_id_from_relation_id)
77
+ def self.has_relationships_to(entity, opts = {}, &get_entity_id_from_relation_id)
78
+ entity_name = opts[:as] || entity
79
+ define_method( entity_name.to_sym,
80
+ Proc.new do |calling_id| # The parent resource's id will come along for the ride when the new bridging method is called magically from ResourceController#call
81
+ entity_array = get_entity_id_from_relation_id.call(@id)
82
+ begin
83
+ index = @resource_request.url_chain.shift.to_i
84
+ rescue
85
+ raise HTTP400BadRequest, 'You requested an item by index and the index was not a valid number.'
86
+ end
87
+ unless index < entity_array.length
88
+ raise HTTP404ResourceNotFound, 'You requested an item by index and the index was larger than this item\'s list of relations\' length.'
89
+ end
90
+ id = entity_array[index]
91
+ @resource_request.url_chain.unshift(id)
92
+ @resource_request.call_controller(entity)
93
+ end
94
+ )
95
+ end
96
+
97
+ # This method defines that there are multiple links to members from an entity collection (an array of entity identifiers).
98
+ # This adds an accessor instance method whose name is the entity's class.
99
+ def self.has_defined_relationships_to(entity, opts = {}, &get_entity_id_from_relation_id)
81
100
  entity_name = opts[:as] || entity
82
101
  define_method( entity_name.to_sym,
83
- Proc.new do
84
- entity_array = get_entity_id_from_relation_id.call(@resource_request.id)
85
- @resource_request.id, @resource_request.action = nil, nil
86
- ( @resource_request.id, @resource_request.action, @resource_request.path_stack ) = @resource_request.path_stack.split('/', 3) unless @resource_request.path_stack.blank?
87
- format_id
88
- unless entity_array.include?( @resource_request.id )
102
+ Proc.new do |calling_id| # The parent resource's id will come along for the ride when the new bridging method is called magically from ResourceController#call
103
+ entity_array = get_entity_id_from_relation_id.call(@id)
104
+ id = @resource_request.url_chain.shift
105
+ raise HTTP400BadRequest, 'No ID provided for has_defined_relationships_to routing.' if id.nil?
106
+ id = RESTRack.controller_class_for(entity).format_string_id(id) if id.is_a? String
107
+ unless entity_array.include?( id )
89
108
  raise HTTP404ResourceNotFound, 'Relation entity does not belong to referring resource.'
90
109
  end
91
- self.call_relation(entity)
110
+ @resource_request.url_chain.unshift(id)
111
+ @resource_request.call_controller(entity)
92
112
  end
93
113
  )
94
114
  end
@@ -98,98 +118,79 @@ module RESTRack
98
118
  def self.has_mapped_relationships_to(entity, opts = {}, &get_entity_id_from_relation_id)
99
119
  entity_name = opts[:as] || entity
100
120
  define_method( entity_name.to_sym,
101
- Proc.new do
102
- entity_map = get_entity_id_from_relation_id.call(@resource_request.id)
103
- @resource_request.action = nil
104
- ( key, @resource_request.action, @resource_request.path_stack ) = @resource_request.path_stack.split('/', 3) unless @resource_request.path_stack.blank?
105
- format_id
106
- unless @resource_request.id = entity_map[key.to_sym]
107
- raise HTTP404ResourceNotFound, 'Relation entity does not belong to referring resource.'
108
- end
109
- self.call_relation(entity)
121
+ Proc.new do |calling_id| # The parent resource's id will come along for the ride when the new bridging method is called magically from ResourceController#call
122
+ entity_map = get_entity_id_from_relation_id.call(@id)
123
+ key = @resource_request.url_chain.shift
124
+ id = entity_map[key.to_sym]
125
+ @resource_request.url_chain.unshift(id)
126
+ @resource_request.call_controller(entity)
110
127
  end
111
128
  )
112
129
  end
113
130
 
114
- # Call the child relation (next entity in the path stack)
115
- # common logic to all relationship methods
116
- def call_relation(entity)
117
- @resource_request.resource_name = entity.to_s.camelize
118
- setup_action
119
- @resource_request.locate
120
- @resource_request.call
131
+ # Allows decendent controllers to set a data type for the id other than the default.
132
+ def self.keyed_with_type(klass)
133
+ self.key_type = klass
121
134
  end
122
135
 
123
- # If the action is not set with the request URI, determine the action from HTTP Verb.
124
- def setup_action
125
- if @resource_request.action.blank?
126
- if @resource_request.request.get?
127
- @resource_request.action = @resource_request.id.blank? ? :index : :show
128
- elsif @resource_request.request.put?
129
- @resource_request.action = @resource_request.id.blank? ? :replace : :update
130
- elsif @resource_request.request.post?
131
- @resource_request.action = @resource_request.id.blank? ? :create : :add
132
- elsif @resource_request.request.delete?
133
- @resource_request.action = @resource_request.id.blank? ? :drop : :destroy
136
+ # Find the action, and id if relevant, that the controller must call.
137
+ def determine_action
138
+ term = @resource_request.url_chain.shift
139
+ if term.nil?
140
+ @id = nil
141
+ @action = nil
142
+ # id terms can be pushed on the url_stack which are not of type String by relationship handlers
143
+ elsif term.is_a? String and self.methods.include?( term.to_sym )
144
+ @id = nil
145
+ @action = term.to_sym
146
+ else
147
+ @id = term
148
+ term = @resource_request.url_chain.shift
149
+ if term.nil?
150
+ @action = nil
151
+ elsif self.methods.include?( term.to_sym )
152
+ @action = term.to_sym
134
153
  else
135
154
  raise HTTP405MethodNotAllowed, 'Action not provided or found and unknown HTTP request method.'
136
155
  end
137
156
  end
138
- end
139
-
140
- # Allows decendent controllers to set a data type for the id other than the default.
141
- def self.keyed_with_type(klass)
142
- @@key_type = klass
157
+ @id = self.class.format_string_id(@id) if @id.is_a? String
158
+ # If the action is not set with the request URI, determine the action from HTTP Verb.
159
+ get_action_from_context if @action.blank?
143
160
  end
144
161
 
145
162
  # 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.
146
- def format_id
147
- @@key_type ||= nil
148
- unless @@key_type.blank?
149
- if @@key_type == Fixnum
150
- @resource_request.id = @resource_request.id.to_i
151
- elsif @@key_type == Float
152
- @resource_request.id = @resource_request.id.to_f
163
+ def self.format_string_id(id)
164
+ return nil unless id
165
+ # default key type of resources is String
166
+ # TODO: Should this be set by service in config/constants.yaml?
167
+ self.key_type ||= String
168
+ unless self.key_type.blank? or self.key_type.ancestors.include?(String)
169
+ if self.key_type.ancestors.include?(Integer)
170
+ id = id.to_i
171
+ elsif self.key_type.ancestors.include?(Float)
172
+ id = id.to_f
153
173
  else
154
174
  raise HTTP500ServerError, "Invalid key identifier type specified on resource #{self.class.to_s}."
155
175
  end
156
- else
157
- @@key_type = String
158
176
  end
177
+ id
159
178
  end
160
-
161
- # This handles outputing properly formatted content based on the file extension in the URL.
162
- def package(data)
163
- if @resource_request.mime_type.like?( RESTRack.mime_type_for( :JSON ) )
164
- @output = data.to_json
165
- elsif @resource_request.mime_type.like?( RESTRack.mime_type_for( :XML ) )
166
- if File.exists? builder_file
167
- @output = builder_up(data)
168
- else
169
- @output = XmlSimple.xml_out(data, 'AttrPrefix' => true)
170
- end
171
- elsif @resource_request.mime_type.like?(RESTRack.mime_type_for( :YAML ) )
172
- @output = YAML.dump(data)
173
- elsif @resource_request.mime_type.like?(RESTRack.mime_type_for( :TEXT ) )
174
- @output = data.to_s
179
+
180
+ # Get action from HTTP verb
181
+ def get_action_from_context
182
+ if @resource_request.request.get?
183
+ @action = @id.blank? ? :index : :show
184
+ elsif @resource_request.request.put?
185
+ @action = @id.blank? ? :replace : :update
186
+ elsif @resource_request.request.post?
187
+ @action = @id.blank? ? :create : :add
188
+ elsif @resource_request.request.delete?
189
+ @action = @id.blank? ? :drop : :destroy
175
190
  else
176
- @output = data
191
+ raise HTTP405MethodNotAllowed, 'Action not provided or found and unknown HTTP request method.'
177
192
  end
178
193
  end
179
194
 
180
- # Use Builder to generate the XML.
181
- def builder_up(data)
182
- buffer = ''
183
- xml = Builder::XmlMarkup.new(:target => buffer)
184
- xml.instruct!
185
- eval( File.new( builder_file ).read )
186
- return buffer
187
- end
188
-
189
- # Builds the path to the builder file for the current controller action.
190
- def builder_file
191
- "#{RESTRack::CONFIG[:ROOT]}/views/#{@resource_request.resource_name.underscore}/#{@resource_request.action}.xml.builder"
192
- end
193
-
194
195
  end # class ResourceController
195
196
  end # module RESTRack