restrack 0.0.5 → 0.0.6

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