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 +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
|