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 +114 -44
- data/bin/restrack +17 -8
- data/lib/restrack/generator/descendant_controller.rb.erb +35 -0
- data/lib/restrack/generator.rb +24 -5
- data/lib/restrack/resource_controller.rb +113 -112
- data/lib/restrack/resource_request.rb +76 -64
- data/lib/restrack/support.rb +12 -7
- data/lib/restrack/version.rb +1 -1
- data/lib/restrack/web_service.rb +1 -3
- data/restrack.gemspec +4 -1
- data/test/sample_app_1/controllers/bata_controller.rb +13 -0
- data/test/sample_app_1/controllers/foo_bar_controller.rb +35 -4
- data/test/sample_app_1/test/test_controller_actions.rb +23 -0
- data/test/sample_app_1/test/test_controller_inputs.rb +41 -0
- data/test/sample_app_1/test/test_controller_modifiers.rb +108 -23
- data/test/sample_app_1/test/test_formats.rb +65 -62
- data/test/sample_app_1/test/test_resource_request.rb +0 -16
- data/test/sample_app_2/controllers/bazu_controller.rb +2 -0
- data/test/sample_app_2/controllers/foo_bar_controller.rb +4 -4
- data/test/sample_app_2/test/test_controller_modifiers.rb +19 -19
- data/test/sample_app_3/controllers/foo_bar_controller.rb +3 -3
- data/test/sample_app_4/controllers/foo_controller.rb +2 -2
- data/test/sample_app_4/test/test_controller_modifiers.rb +1 -1
- data/test/sample_app_4/test/test_formats.rb +4 -2
- metadata +57 -21
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
|
-
|
6
|
-
|
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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
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
|
-
|
40
|
-
|
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
|
-
==
|
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
|
-
|
50
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
70
|
-
|
71
|
-
to
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
9
|
-
|
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
|
-
|
17
|
-
|
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
|
data/lib/restrack/generator.rb
CHANGED
@@ -5,11 +5,13 @@ require 'active_support/inflector'
|
|
5
5
|
|
6
6
|
module RESTRack
|
7
7
|
class Generator
|
8
|
+
|
8
9
|
TEMPLATE = {
|
9
|
-
:service
|
10
|
-
:rackup
|
11
|
-
:constants
|
12
|
-
:controller
|
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("#{
|
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 :
|
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
|
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 << @
|
36
|
-
|
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
|
-
|
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.
|
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.
|
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.
|
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
|
-
|
70
|
-
@resource_request.
|
71
|
-
|
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.
|
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(@
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
unless entity_array.include?(
|
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
|
-
|
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(@
|
103
|
-
@resource_request.
|
104
|
-
|
105
|
-
|
106
|
-
|
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
|
-
#
|
115
|
-
|
116
|
-
|
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
|
-
#
|
124
|
-
def
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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
|
-
|
139
|
-
|
140
|
-
|
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
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
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
|
-
#
|
162
|
-
def
|
163
|
-
if @resource_request.
|
164
|
-
@
|
165
|
-
elsif @resource_request.
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
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
|
-
|
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
|