├── Gemfile ├── Gemfile.lock ├── README.markdown ├── README.markdown.html ├── Rakefile ├── bin └── restrack ├── lib ├── restrack.rb └── restrack │ ├── generator.rb │ ├── generator │ ├── config.ru.erb │ ├── constants.yaml.erb │ ├── controller.rb.erb │ ├── descendant_controller.rb.erb │ ├── hooks.rb.erb │ └── loader.rb.erb │ ├── http_status.rb │ ├── resource_controller.rb │ ├── resource_relations.rb │ ├── resource_request.rb │ ├── response.rb │ ├── support.rb │ ├── version.rb │ └── web_service.rb ├── restrack.gemspec └── test ├── sample_app_1 ├── config.ru ├── config │ └── constants.yaml ├── controllers │ ├── bat_controller.rb │ ├── bata_controller.rb │ ├── baz_controller.rb │ ├── baza_controller.rb │ ├── bazu_controller.rb │ ├── errors_controller.rb │ └── foo_bar_controller.rb ├── loader.rb ├── test │ ├── test_callback.rb │ ├── test_controller_actions.rb │ ├── test_controller_inputs.rb │ ├── test_controller_modifiers.rb │ ├── test_cors_jsonp_support.rb │ ├── test_errors.rb │ ├── test_formats.rb │ ├── test_resource_request.rb │ └── test_web_service.rb └── views │ └── foo_bar │ └── show.xml.builder ├── sample_app_2 ├── config │ └── constants.yaml ├── controllers │ ├── bat_controller.rb │ ├── baz_controller.rb │ ├── baza_controller.rb │ ├── bazu_controller.rb │ └── foo_bar_controller.rb ├── loader.rb ├── test │ ├── test_controller_modifiers.rb │ └── test_resource_request.rb └── views │ └── foo_bar │ └── show.xml.builder ├── sample_app_3 ├── config │ └── constants.yaml ├── controllers │ ├── bat_controller.rb │ ├── baz_controller.rb │ ├── baza_controller.rb │ ├── bazu_controller.rb │ └── foo_bar_controller.rb ├── loader.rb ├── test │ └── test_resource_request.rb └── views │ └── foo_bar │ └── show.xml.builder ├── sample_app_4 ├── config │ └── constants.yaml ├── controllers │ ├── bar_controller.rb │ ├── baz_controller.rb │ └── foo_controller.rb ├── loader.rb ├── test │ ├── test_controller_modifiers.rb │ └── test_formats.rb └── views │ └── alphatest.png ├── sample_app_5 ├── config.ru ├── config │ └── constants.yaml ├── controllers │ └── hook_controller.rb ├── hooks.rb ├── loader.rb └── test │ └── test_hooks.rb ├── test_support.rb └── test_web_service.rb /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # Specifying gem's dependencies in restrack.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | restrack (1.1.2) 5 | activesupport 6 | builder 7 | i18n 8 | json 9 | mime-types 10 | rack 11 | xml-simple (>= 1.0.13) 12 | 13 | GEM 14 | remote: http://rubygems.org/ 15 | specs: 16 | activesupport (3.1.0) 17 | multi_json (~> 1.0) 18 | builder (3.0.0) 19 | i18n (0.6.0) 20 | json (1.5.4) 21 | mime-types (1.16) 22 | multi_json (1.0.3) 23 | rack (1.3.2) 24 | rack-test (0.6.1) 25 | rack (>= 1.0) 26 | xml-simple (1.1.0) 27 | 28 | PLATFORMS 29 | ruby 30 | 31 | DEPENDENCIES 32 | rack-test 33 | restrack! 34 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # RESTRack 2 | - serving JSON and XML with REST and pleasure. 3 | 4 | ## Description: 5 | RESTRack is a [Rack](http://rack.rubyforge.org/)-based [MVC](http://en.wikipedia.org/wiki/Model%E2%80%93View%E2%80%93Controller) 6 | framework that makes it extremely easy to develop [REST](http://en.wikipedia.org/wiki/Representational_State_Transfer)ful 7 | data services. It is inspired by [Rails](http://rubyonrails.org), and follows a few of its conventions. But it has no routes 8 | file, routing relationships are done through supplying custom code blocks to class methods such as "has\_relationship\_to" or 9 | "has\_mapped\_relationships\_to". 10 | 11 | RESTRack aims at being lightweight and easy to use. It will automatically render [JSON](http://www.json.org/) and 12 | [XML](http://www.w3.org/XML/) for the data structures you return in your actions \(any structure parsable by the 13 | "[json](http://flori.github.com/json/)" and "[xml-simple](https://github.com/maik/xml-simple)" gems, respectively\). 14 | 15 | If you supply a view for a controller action, you do that using a builder file. Builder files are stored in the 16 | view directory grouped by controller name subdirectories \(`view//.xml.builder`\). XML format 17 | requests will then render the view template with the builder gem, rather than generating XML with XmlSimple. 18 | 19 | 20 | ## Installation: 21 | ### Using [RubyGems](http://rubygems.org): 22 | gem install restrack 23 | 24 | 25 | ## Why RESTRack when there is Rails? 26 | [Rails](http://rubyonrails.org/) is a powerful tool for full web applications. RESTRack is targeted at making 27 | development of lightweight data services as easy as possible, while still giving you a performant and extensible 28 | framework. The primary goal of of the development of RESTRack was to add as little as possible to the framework to give 29 | the web developer a good application space for developing JSON and XML services. 30 | 31 | Rails 3 instantiates approximately 80K more objects than RESTRack to do a hello world or nothing type response with 32 | the default setup. Trimming Rails down by eliminating ActiveRecord, ActionMailer, and ActiveResource, it still 33 | instantiates over 47K more objects than RESTRack. 34 | 35 | ## OK, so why RESTRack when there is Sinatra? 36 | RESTRack provides a full, albeit small, framework for developing RESTful MVC applications. 37 | 38 | 39 | ## CLI Usage: 40 | ### Generate a new service \(FooBar::WebService\) 41 | - restrack generate service foo\_bar 42 | - restrack gen serv foo\_bar 43 | - restrack g s foo\_bar 44 | 45 | ### Generate a new controller \(FooBar::BazController\) 46 | - restrack generate controller baz 47 | - restrack gen cont baz 48 | - restrack g c baz 49 | 50 | ### Generate a new controller that descends from another \(FooBar::NewController < FooBar::BazController\) 51 | - restrack generate controller new descendant\_from baz 52 | - restrack g controller new parent baz 53 | 54 | ### Start up a server on default rackup port 9292 55 | - restrack server 56 | 57 | ### Start up a server on port 3456 58 | - restrack server 3456 59 | - restrack s 3456 60 | 61 | 62 | ## REST action method names 63 | All default RESTful controller method names align with their Rails counterparts, with two additional actions being 64 | supported\(\*\). 65 | 66 | HTTP Verb: | GET | PUT | POST | DELETE 67 | Collection URI (/widgets/): | index | replace | create | *drop 68 | Element URI (/widgets/42): | show | update | *add | destroy 69 | 70 | 71 | ## Automatic response data serialization 72 | ### JSON 73 | Objects returned from resource controller methods will have the "to\_json" method called to serialize response output. 74 | Controllers should return objects that respond to "to\_json". RESTRack includes the JSON gem, which implements this method 75 | on Ruby's standard lib simple data types (Array, Hash, String etc). 76 | 77 | # GET /widgets/42.json 78 | def show(id) # id will be 42 79 | widget = Widget.find(id) 80 | return widget 81 | # The widget.to_json will be called and the resultant JSON sent as response body. 82 | end 83 | 84 | # GET /widgets/42 85 | def show(id) # id will be 42 86 | widget = Widget.find(id) 87 | return widget 88 | # The widget.to_json will be called unless the default response type is set to :XML in config/constants.yaml, 89 | # in which case the widget.to_xml method will be called. 90 | end 91 | 92 | ### XML 93 | RESTRack will convert the data structures that your actions return to JSON by default. You can change the default 94 | by setting :DEFAULT_FORMAT to :XML in `config/constants.yml`. 95 | 96 | #### With Builder 97 | Custom XML serialization can be done by providing [Builder](http://builder.rubyforge.org/) gem templates in `views//.xml.builder`. 98 | 99 | #### Custom Serialization Method 100 | When XML is requested, objects returned from resource controller methods will have the "to_xml" method called to serialize 101 | response output if an XML builder template file is not provided. If the response object does not respond to "to_xml", then 102 | the object will be sent to XmlSimple for serialization. 103 | 104 | #### With XmlSimple 105 | RESTRack will attempt to serialize the data structures that your action methods return automatically using the 106 | xml-simple gem. Complex objects may not serialize correctly, or you may want to define a particular structure for your 107 | XML, in which case a builder template should be defined. 108 | 109 | # GET /widgets/42.xml 110 | def show(id) # id will be 42 111 | widget = Widget.find(id) 112 | return widget 113 | # Template file views/widgets/show.xml.builder will be used to render the XML if it exists. 114 | # If not, the widget.to_xml method will be called and the resultant XML sent as response body, 115 | # or, if widget does not respond to "to_xml", then XmlSimple will be used to serialize the data object. 116 | end 117 | 118 | 119 | ## Accepting parameters and generating a response 120 | Input parameters are accessible through the @params object. This is a merged hash containing the POST and GET parameters, 121 | which can be accessed separately through @post_params and @get_params. 122 | 123 | # GET /widgets/list.xml?offset=100&limit=50 124 | def list 125 | widget_list = Widget.limit( @params['limit'], @params['offset'] ) 126 | return widget_list 127 | end 128 | 129 | 130 | ## URLs and Controller relationships 131 | RESTRack enforces a strict URL pattern through the construct of controller relationships, rather than a routing file. 132 | Defining a controller for a resource means that you plan to expose that resource to requests to your service. 133 | Defining a controller relationship means that you plan to expose a path from this resource to another. 134 | 135 | ### "pass\_through\_to" 136 | An open, or pass-through, path can be defined via the "pass\_through\_to" class method for resource controllers. This 137 | exposes URL patterns like the following: 138 | 139 | GET /foo/123/bar/234 <= simple pass-through from Foo 123 to show Bar 234 140 | GET /foo/123/bar <= simple pass-through from Foo 123 to Bar index 141 | 142 | ### "has\_relationship\_to" 143 | A direct path to a single related resource's controller can be defined with the "has\_relationship\_to" method. This 144 | allows you to define a one-to-one relationship from this resource to a related resource, which means that the id of 145 | the related resource is implied through the id of the caller. The caller has one relation through a custom code block 146 | passed to "has\_relationship\_to". The code block takes the caller resource's id and evaluates to the relation 147 | resource's id, for example a PeopleController might define a one-to-one relationship like so: 148 | 149 | has_relationship_to( :people, :as spouse ) do |id| 150 | People.find(id).spouse.id 151 | end 152 | 153 | This exposes URL patterns like the following: 154 | 155 | GET /people/Sally/spouse <= direct route to show Sally's spouse 156 | PUT /people/Henry/spouse <= direct route to update Henry's spouse 157 | POST /people/Jane/spouse <= direct route to add Jane's spouse 158 | 159 | ### "has\_relationships\_to" and "has\_defined\_relationships\_to" 160 | A direct path to many related resources' controller can be defined with the "has\_relationships\_to" and 161 | "has\_defined\_relationships\_to" methods. These allows you to define one-to-many relationships. They work similar to 162 | "has\_relationship\_to", except that they accept code blocks which evaluate to arrays of related child ids. Each 163 | resource in the parent's relation list is then accessed through its array index (zero-based) in the URL. An example 164 | of exposing the list of a People resource's children in this manner follows: 165 | 166 | has_relationships_to( :people, :as => children ) do |id| 167 | People.find(id).children.collect {|child| child.id} 168 | end 169 | 170 | Which exposes URLs similar to: 171 | 172 | GET /people/Nancy/children/0 <= direct route to show child 0 173 | DELETE /people/Robert/children/100 <= direct route to destroy child 100 174 | 175 | An example of "has\_defined\_relationships\_to": 176 | 177 | has_defined_relationships_to( :people, :as => children ) do |id| 178 | People.find(id).children.collect {|child| child.id} 179 | end 180 | 181 | exposes URL patterns: 182 | 183 | GET /people/Nancy/children/George <= route to show child George 184 | DELETE /people/Robert/children/Jerry <= route to destroy child Jerry 185 | 186 | ### "has\_mapped\_relationships\_to" 187 | Multiple named one-to-many relationships can be exposed with the "has\_mapped\_relationships\_to" method. This allows 188 | you to define many named or keyword paths to related resources. The method's code block should accepts the parent id 189 | and return a hash where the keys are your relationship names and the values are the child resource ids. For example, 190 | within a PeopleController the following definition: 191 | 192 | has_mapped_relationships_to( :people ) do |id| 193 | { 194 | 'father' => People.find(id).father.id, 195 | 'mother' => People.find(id).mother.id, 196 | 'boss' => People.find(id).boss.id, 197 | 'assistant' => People.find(id).assistant.id 198 | } 199 | end 200 | 201 | This would expose the following URL patterns: 202 | 203 | GET /people/Fred/people/father => show the father of Fred 204 | PUT /people/Fred/people/assistant => update Fred's assistant 205 | POST /people/Fred/people/boss => add Fred's boss 206 | DELETE /people/Luke/people/mother => destroy Luke's father 207 | 208 | ### Setting the data type of the id - "keyed\_with\_type" 209 | Resource id data types can be defined with the "keyed\_with\_type" class method within resource controllers. The 210 | default data type of String is used if a different type is not specified. 211 | 212 | 213 | ## Logging/Logging Level 214 | RESTRack outputs to two logs, the standard log (or error log) and the request log. Paths and logging levels for these 215 | can be configured in `config/constants.yaml`. RESTRack uses Logger from Ruby-stdlib. 216 | 217 | 218 | ## Inputs 219 | 220 | ### Query string parameters 221 | Available to controllers in the `@params` instance variable. 222 | 223 | ### POST data 224 | Available to controllers in the `@input` instance variable. 225 | 226 | 227 | ## Constant Definition \(`config/constants.yaml`\) 228 | 229 | ### Required Configuration Settings 230 | #### :LOG 231 | Sets the location of the error log. 232 | 233 | #### :REQUEST\_LOG 234 | Sets the location of the request log. 235 | 236 | #### :LOG\_LEVEL 237 | Sets the the logging level of the error log, based on the Ruby Logger object. Supply these as a symbol, with valid 238 | values being :DEBUG, :INFO, :WARN, etc. 239 | 240 | #### :REQUEST\_LOG\_LEVEL 241 | Sets the the logging level of the request log, similar to :LOG\_LEVEL. 242 | 243 | ### Optional Configuration Settings 244 | #### :DEFAULT\_FORMAT 245 | Sets the default format for the response. This is the format that the response will take if no extension is appended to 246 | the request string \(i.e. `/foo/123` rather than `/foo/123.xml`\). Services will have a default format of JSON if this 247 | configuration option is not defined. 248 | 249 | #### :DEFAULT\_RESOURCE 250 | Set this option in config/constants.yaml to use an implied root resource controller. To make `/foo/123` also be accessible 251 | at `/123`: 252 | 253 | :DEFAULT_RESOURCE: foo 254 | 255 | 256 | #### :ROOT\_RESOURCE\_ACCEPT 257 | This defines an array of resources that can be accessed as the first resource in the URL chain, without being proxied 258 | through another relation. 259 | 260 | :ROOT_RESOURCE_ACCEPT: [ 'foo', 'bar' ] 261 | 262 | 263 | #### :ROOT\_RESOURCE\_DENY 264 | This defines an array of resources that cannot be accessed without proxying though another controller. 265 | 266 | :ROOT_RESOURCE_DENY: [ 'baz' ] 267 | 268 | 269 | #### :SHOW\_STACK 270 | If defined, server error messages will contain the stack trace. This is not recommended when these errors could possibly 271 | be delivered to the client. 272 | 273 | :SHOW_STACK: true 274 | 275 | 276 | ## License 277 | 278 | Copyright (c) 2010 Chris St. John 279 | 280 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 281 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 282 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 283 | persons to whom the Software is furnished to do so, subject to the following conditions: 284 | 285 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 286 | Software. 287 | 288 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 289 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 290 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 291 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 292 | -------------------------------------------------------------------------------- /README.markdown.html: -------------------------------------------------------------------------------- 1 |

RESTRack

2 | 3 |
    4 |
  • serving JSON and XML with REST and pleasure.
  • 5 |
6 | 7 |

Description:

8 | 9 |

RESTRack is a Rack-based MVC 10 | framework that makes it extremely easy to develop RESTful 11 | data services. It is inspired by Rails, and follows a few of its conventions. But it has no routes 12 | file, routing relationships are done through supplying custom code blocks to class methods such as "has_relationship_to" or 13 | "has_mapped_relationships_to".

14 | 15 |

RESTRack aims at being lightweight and easy to use. It will automatically render JSON and 16 | XML for the data structures you return in your actions (any structure parsable by the 17 | "json" and "xml-simple" gems, respectively).

18 | 19 |

If you supply a view for a controller action, you do that using a builder file. Builder files are stored in the 20 | view directory grouped by controller name subdirectories (view/<controller>/<action>.xml.builder). XML format 21 | requests will then render the view template with the builder gem, rather than generating XML with XmlSimple.

22 | 23 |

Installation:

24 | 25 |

Using RubyGems:

26 | 27 |
<sudo> gem install restrack
 28 | 
29 | 30 |

Why RESTRack when there is Rails?

31 | 32 |

Rails is a powerful tool for full web applications. RESTRack is targeted at making 33 | development of lightweight data services as easy as possible, while still giving you a performant and extensible 34 | framework. The primary goal of of the development of RESTRack was to add as little as possible to the framework to give 35 | the web developer a good application space for developing JSON and XML services.

36 | 37 |

Rails 3 instantiates approximately 80K more objects than RESTRack to do a hello world or nothing type response with 38 | the default setup. Trimming Rails down by eliminating ActiveRecord, ActionMailer, and ActiveResource, it still 39 | instantiates over 47K more objects than RESTRack.

40 | 41 |

OK, so why RESTRack when there is Sinatra?

42 | 43 |

RESTRack provides a full, albeit small, framework for developing RESTful MVC applications.

44 | 45 |

CLI Usage:

46 | 47 |

Generate a new service (FooBar::WebService)

48 | 49 |
    50 |
  • restrack generate service foo_bar
  • 51 |
  • restrack gen serv foo_bar
  • 52 |
  • restrack g s foo_bar
  • 53 |
54 | 55 |

Generate a new controller (FooBar::BazController)

56 | 57 |
    58 |
  • restrack generate controller baz
  • 59 |
  • restrack gen cont baz
  • 60 |
  • restrack g c baz
  • 61 |
62 | 63 |

Generate a new controller that descends from another (FooBar::NewController < FooBar::BazController)

64 | 65 |
    66 |
  • restrack generate controller new descendant_from baz
  • 67 |
  • restrack g controller new parent baz
  • 68 |
69 | 70 |

Start up a server on default rackup port 9292

71 | 72 |
    73 |
  • restrack server
  • 74 |
75 | 76 |

Start up a server on port 3456

77 | 78 |
    79 |
  • restrack server 3456
  • 80 |
  • restrack s 3456
  • 81 |
82 | 83 |

REST action method names

84 | 85 |

All default RESTful controller method names align with their Rails counterparts, with two additional actions being 86 | supported(*).

87 | 88 |
                     HTTP Verb: |   GET   |   PUT    |   POST   |   DELETE
 89 |     Collection URI (/widgets/): |  index  |  replace |  create  |  *drop
 90 |     Element URI  (/widgets/42): |  show   |  update  |  *add    |  destroy
 91 | 
92 | 93 |

Automatic response data serialization

94 | 95 |

JSON

96 | 97 |

Objects returned from resource controller methods will have the "to_json" method called to serialize response output. 98 | Controllers should return objects that respond to "to_json". RESTRack includes the JSON gem, which implements this method 99 | on Ruby's standard lib simple data types (Array, Hash, String etc).

100 | 101 |
# GET /widgets/42.json
102 | def show(id) # id will be 42
103 |     widget = Widget.find(id)
104 |     return widget
105 |     # The widget.to_json will be called and the resultant JSON sent as response body.
106 | end
107 | 
108 | # GET /widgets/42
109 | def show(id) # id will be 42
110 |     widget = Widget.find(id)
111 |     return widget
112 |     # The widget.to_json will be called unless the default response type is set to :XML in config/constants.yaml,
113 |     # in which case the widget.to_xml method will be called.
114 | end
115 | 
116 | 117 |

XML

118 | 119 |

RESTRack will convert the data structures that your actions return to JSON by default. You can change the default 120 | by setting :DEFAULT_FORMAT to :XML in config/constants.yml.

121 | 122 |

With Builder

123 | 124 |

Custom XML serialization can be done by providing Builder gem templates in views/<controller>/<action>.xml.builder.

125 | 126 |

Custom Serialization Method

127 | 128 |

When XML is requested, objects returned from resource controller methods will have the "toxml" method called to serialize 129 | response output if an XML builder template file is not provided. If the response object does not respond to "toxml", then 130 | the object will be sent to XmlSimple for serialization.

131 | 132 |

With XmlSimple

133 | 134 |

RESTRack will attempt to serialize the data structures that your action methods return automatically using the 135 | xml-simple gem. Complex objects may not serialize correctly, or you may want to define a particular structure for your 136 | XML, in which case a builder template should be defined.

137 | 138 |
# GET /widgets/42.xml
139 | def show(id) # id will be 42
140 |     widget = Widget.find(id)
141 |     return widget
142 |     # Template file views/widgets/show.xml.builder will be used to render the XML if it exists.
143 |     # If not, the widget.to_xml method will be called and the resultant XML sent as response body,
144 |     # or, if widget does not respond to "to_xml", then XmlSimple will be used to serialize the data object.
145 | end
146 | 
147 | 148 |

Accepting parameters and generating a response

149 | 150 |

Input parameters are accessible through the @params object. This is a merged hash containing the POST and GET parameters, 151 | which can be accessed separately through @postparams and @getparams.

152 | 153 |
# GET /widgets/list.xml?offset=100&limit=50
154 | def list
155 |     widget_list = Widget.limit( @params['limit'], @params['offset'] )
156 |     return widget_list
157 | end
158 | 
159 | 160 |

URLs and Controller relationships

161 | 162 |

RESTRack enforces a strict URL pattern through the construct of controller relationships, rather than a routing file. 163 | Defining a controller for a resource means that you plan to expose that resource to requests to your service. 164 | Defining a controller relationship means that you plan to expose a path from this resource to another.

165 | 166 |

"pass_through_to"

167 | 168 |

An open, or pass-through, path can be defined via the "pass_through_to" class method for resource controllers. This 169 | exposes URL patterns like the following:

170 | 171 |
GET /foo/123/bar/234        <= simple pass-through from Foo 123 to show Bar 234
172 | GET /foo/123/bar            <= simple pass-through from Foo 123 to Bar index
173 | 
174 | 175 |

"has_relationship_to"

176 | 177 |

A direct path to a single related resource's controller can be defined with the "has_relationship_to" method. This 178 | allows you to define a one-to-one relationship from this resource to a related resource, which means that the id of 179 | the related resource is implied through the id of the caller. The caller has one relation through a custom code block 180 | passed to "has_relationship_to". The code block takes the caller resource's id and evaluates to the relation 181 | resource's id, for example a PeopleController might define a one-to-one relationship like so:

182 | 183 |
    has_relationship_to( :people, :as spouse ) do |id|
184 |       People.find(id).spouse.id
185 |     end
186 | 
187 | 188 |

This exposes URL patterns like the following:

189 | 190 |
GET /people/Sally/spouse    <= direct route to show Sally's spouse
191 | PUT /people/Henry/spouse    <= direct route to update Henry's spouse
192 | POST /people/Jane/spouse    <= direct route to add Jane's spouse
193 | 
194 | 195 |

"has_relationships_to" and "has_defined_relationships_to"

196 | 197 |

A direct path to many related resources' controller can be defined with the "has_relationships_to" and 198 | "has_defined_relationships_to" methods. These allows you to define one-to-many relationships. They work similar to 199 | "has_relationship_to", except that they accept code blocks which evaluate to arrays of related child ids. Each 200 | resource in the parent's relation list is then accessed through its array index (zero-based) in the URL. An example 201 | of exposing the list of a People resource's children in this manner follows:

202 | 203 |
  has_relationships_to( :people, :as => children ) do |id|
204 |     People.find(id).children.collect {|child| child.id}
205 |   end
206 | 
207 | 208 |

Which exposes URLs similar to:

209 | 210 |
GET /people/Nancy/children/0          <= direct route to show child 0
211 | DELETE /people/Robert/children/100    <= direct route to destroy child 100
212 | 
213 | 214 |

An example of "has_defined_relationships_to":

215 | 216 |
    has_defined_relationships_to( :people, :as => children ) do |id|
217 |       People.find(id).children.collect {|child| child.id}
218 |     end
219 | 
220 | 221 |

exposes URL patterns:

222 | 223 |
GET /people/Nancy/children/George     <= route to show child George
224 | DELETE /people/Robert/children/Jerry  <= route to destroy child Jerry
225 | 
226 | 227 |

"has_mapped_relationships_to"

228 | 229 |

Multiple named one-to-many relationships can be exposed with the "has_mapped_relationships_to" method. This allows 230 | you to define many named or keyword paths to related resources. The method's code block should accepts the parent id 231 | and return a hash where the keys are your relationship names and the values are the child resource ids. For example, 232 | within a PeopleController the following definition:

233 | 234 |
    has_mapped_relationships_to( :people ) do |id|
235 |       {
236 |         'father'    => People.find(id).father.id,
237 |         'mother'    => People.find(id).mother.id,
238 |         'boss'      => People.find(id).boss.id,
239 |         'assistant' => People.find(id).assistant.id
240 |       }
241 |     end
242 | 
243 | 244 |

This would expose the following URL patterns:

245 | 246 |
GET /people/Fred/people/father      => show the father of Fred
247 | PUT /people/Fred/people/assistant   => update Fred's assistant
248 | POST /people/Fred/people/boss       => add Fred's boss
249 | DELETE /people/Luke/people/mother   => destroy Luke's father
250 | 
251 | 252 |

Setting the data type of the id - "keyed_with_type"

253 | 254 |

Resource id data types can be defined with the "keyed_with_type" class method within resource controllers. The 255 | default data type of String is used if a different type is not specified.

256 | 257 |

Logging/Logging Level

258 | 259 |

RESTRack outputs to two logs, the standard log (or error log) and the request log. Paths and logging levels for these 260 | can be configured in config/constants.yaml. RESTRack uses Logger from Ruby-stdlib.

261 | 262 |

Inputs

263 | 264 |

Query string parameters

265 | 266 |

Available to controllers in the @params instance variable.

267 | 268 |

POST data

269 | 270 |

Available to controllers in the @input instance variable.

271 | 272 |

Constant Definition (config/constants.yaml)

273 | 274 |

Required Configuration Settings

275 | 276 |

:LOG

277 | 278 |

Sets the location of the error log.

279 | 280 |

:REQUEST_LOG

281 | 282 |

Sets the location of the request log.

283 | 284 |

:LOG_LEVEL

285 | 286 |

Sets the the logging level of the error log, based on the Ruby Logger object. Supply these as a symbol, with valid 287 | values being :DEBUG, :INFO, :WARN, etc.

288 | 289 |

:REQUEST_LOG_LEVEL

290 | 291 |

Sets the the logging level of the request log, similar to :LOG_LEVEL.

292 | 293 |

Optional Configuration Settings

294 | 295 |

:DEFAULT_FORMAT

296 | 297 |

Sets the default format for the response. This is the format that the response will take if no extension is appended to 298 | the request string (i.e. /foo/123 rather than /foo/123.xml). Services will have a default format of JSON if this 299 | configuration option is not defined.

300 | 301 |

:DEFAULT_RESOURCE

302 | 303 |

Set this option in config/constants.yaml to use an implied root resource controller. To make /foo/123 also be accessible 304 | at /123:

305 | 306 |
:DEFAULT_RESOURCE: foo
307 | 
308 | 309 |

:ROOT_RESOURCE_ACCEPT

310 | 311 |

This defines an array of resources that can be accessed as the first resource in the URL chain, without being proxied 312 | through another relation.

313 | 314 |
:ROOT_RESOURCE_ACCEPT: [ 'foo', 'bar' ]
315 | 
316 | 317 |

:ROOT_RESOURCE_DENY

318 | 319 |

This defines an array of resources that cannot be accessed without proxying though another controller.

320 | 321 |
:ROOT_RESOURCE_DENY: [ 'baz' ]
322 | 
323 | 324 |

:SHOW_STACK

325 | 326 |

If defined, server error messages will contain the stack trace. This is not recommended when these errors could possibly 327 | be delivered to the client.

328 | 329 |
:SHOW_STACK: true
330 | 
331 | 332 |

License

333 | 334 |

Copyright (c) 2010 Chris St. John

335 | 336 |

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 337 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 338 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 339 | persons to whom the Software is furnished to do so, subject to the following conditions:

340 | 341 |

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 342 | Software.

343 | 344 |

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 345 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 346 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 347 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

348 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'rake/testtask' 3 | 4 | require 'bundler' 5 | Bundler::GemHelper.install_tasks 6 | 7 | task :default => [:test_all] 8 | 9 | desc 'Run all tests.' 10 | task :test_all do 11 | for n in 0..5 12 | Rake::Task['test'+n.to_s].invoke 13 | end 14 | end 15 | 16 | desc 'Run base tests.' 17 | Rake::TestTask.new('test0') { |t| 18 | t.pattern = 'test/test_*.rb' 19 | } 20 | 21 | desc 'Run sample_app_1 tests.' 22 | Rake::TestTask.new('test1') { |t| 23 | t.pattern = 'test/sample_app_1/**/test_*.rb' 24 | } 25 | 26 | desc 'Run sample_app_2 tests.' 27 | Rake::TestTask.new('test2') { |t| 28 | t.pattern = 'test/sample_app_2/**/test_*.rb' 29 | } 30 | 31 | desc 'Run sample_app_3 tests.' 32 | Rake::TestTask.new('test3') { |t| 33 | t.pattern = 'test/sample_app_3/**/test_*.rb' 34 | } 35 | 36 | desc 'Run sample_app_4 tests.' 37 | Rake::TestTask.new('test4') { |t| 38 | t.pattern = 'test/sample_app_4/**/test_*.rb' 39 | } 40 | 41 | 42 | desc 'Run sample_app_5 tests.' 43 | Rake::TestTask.new('test5') { |t| 44 | t.pattern = 'test/sample_app_5/**/test_*.rb' 45 | } 46 | -------------------------------------------------------------------------------- /bin/restrack: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'rubygems' 3 | require 'restrack' 4 | 5 | verb = ARGV[0].to_sym 6 | noun = ARGV[1] 7 | 8 | 9 | case verb 10 | when :generate, :gen, :g 11 | name = ARGV[2] 12 | case noun.to_sym 13 | when :service, :serv, :s 14 | puts "Generating new RESTRack service #{name}..." 15 | RESTRack::Generator.generate_service( name ) 16 | when :controller, :cont, :c 17 | predicate = ARGV[3] ? ARGV[3].to_sym : nil 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 27 | end 28 | puts 'Creation is complete.' 29 | when :server, :s 30 | options = { :Port => noun || 9292, :config => 'config.ru' } 31 | options.merge({ :environment => ARGV[2] }) unless ARGV[2].nil? 32 | Rack::Server.start( options ) 33 | end 34 | 35 | # TODO: Print current version of RESTrack via command-line call -------------------------------------------------------------------------------- /lib/restrack.rb: -------------------------------------------------------------------------------- 1 | %w[ 2 | rack 3 | logger 4 | pp 5 | find 6 | yaml 7 | rubygems 8 | json 9 | xmlsimple 10 | builder 11 | active_support/inflector 12 | mime/types 13 | ].each do |file| 14 | require file 15 | end 16 | 17 | # Dynamically load all files in lib 18 | Find.find( File.join(File.dirname(__FILE__)) ) do |file| 19 | next if File.extname(file) != '.rb' 20 | require file 21 | end 22 | 23 | include HTTPStatus 24 | include ActiveSupport::Inflector 25 | -------------------------------------------------------------------------------- /lib/restrack/generator.rb: -------------------------------------------------------------------------------- 1 | require 'erb' 2 | require 'fileutils' 3 | require 'rubygems' 4 | require 'active_support/inflector' 5 | 6 | module RESTRack 7 | class Generator 8 | 9 | TEMPLATE = { 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', 15 | :hooks => 'hooks.rb.erb' 16 | } 17 | 18 | class << self 19 | 20 | # Generate controller file 21 | def generate_controller(name) 22 | template = get_template_for( :controller ) 23 | resultant_string = template.result( get_binding_for_controller( name ) ) 24 | File.open("#{base_dir}/controllers/#{name}_controller.rb", 'w') {|f| f.puts resultant_string } 25 | # Generate view folder for controller 26 | FileUtils.makedirs("#{base_dir}/views/#{name}") 27 | end 28 | 29 | # Generate controller file the descends from specified parent, to enable 30 | # grouping of controller types and/or overarching functionality. 31 | def generate_descendant_controller(name, parent) 32 | template = get_template_for( :descendant_controller ) 33 | resultant_string = template.result( get_binding_for_descendant_controller( name, parent ) ) 34 | File.open("#{base_dir}/controllers/#{name}_controller.rb", 'w') {|f| f.puts resultant_string } 35 | # Generate view folder for controller 36 | FileUtils.makedirs("#{base_dir}/views/#{name}") 37 | end 38 | 39 | # Generate a new RESTRack service 40 | def generate_service(name) 41 | FileUtils.makedirs("#{name}/config") 42 | FileUtils.makedirs("#{name}/controllers") 43 | FileUtils.makedirs("#{name}/models") 44 | FileUtils.makedirs("#{name}/test") 45 | FileUtils.makedirs("#{name}/views") 46 | 47 | template = get_template_for( :service ) 48 | resultant_string = template.result( get_binding_for_service( name ) ) 49 | File.open("#{name}/loader.rb", 'w') {|f| f.puts resultant_string } 50 | 51 | template = get_template_for( :rackup ) 52 | resultant_string = template.result( get_binding_for_service( name ) ) 53 | File.open("#{name}/config.ru", 'w') {|f| f.puts resultant_string } 54 | 55 | template = get_template_for( :constants ) 56 | resultant_string = template.result( get_binding_for_service( name ) ) 57 | File.open("#{name}/config/constants.yaml", 'w') {|f| f.puts resultant_string } 58 | 59 | template = get_template_for( :hooks ) 60 | resultant_string = template.result( get_binding_for_service( name ) ) 61 | File.open("#{name}/hooks.rb", 'w') {|f| f.puts resultant_string } 62 | end 63 | 64 | private 65 | 66 | def get_template_for(type) 67 | template_file = File.new(File.join(File.dirname(__FILE__),"generator/#{TEMPLATE[type]}")) 68 | template = ERB.new( template_file.read, nil, "%" ) 69 | end 70 | 71 | def get_binding_for_controller(name) 72 | @name = name 73 | @service_name = get_service_name 74 | binding 75 | end 76 | 77 | def get_binding_for_descendant_controller(name, parent) 78 | @name = name 79 | @parent = parent 80 | @service_name = get_service_name 81 | binding 82 | end 83 | 84 | def get_binding_for_service(name) 85 | @service_name = name 86 | binding 87 | end 88 | 89 | def get_service_name 90 | line = '' 91 | begin 92 | File.open(File.join(base_dir, 'config/constants.yaml')) { |f| line = f.gets } 93 | rescue 94 | raise File.join(base_dir, 'config/constants.yaml') + ' not found or could not be opened!' 95 | end 96 | begin 97 | check = line.match(/#GENERATOR-CONST#.*Application-Namespace\s*=>\s*(.+)/)[0] 98 | service_name = $1 99 | rescue 100 | raise '#GENERATOR-CONST# line has been removed or modified in config/constants.yaml.' 101 | end 102 | return service_name 103 | end 104 | 105 | def base_dir 106 | base_dir = nil 107 | this_path = File.join( Dir.pwd, 'config/constants.yaml') 108 | while this_path != '/config/constants.yaml' 109 | if File.exists?( this_path ) 110 | base_dir = Dir.pwd 111 | break 112 | else 113 | this_path = File.join('..', this_path) 114 | end 115 | end 116 | raise 'The config/constants.yaml file could not found when determining base_dir!' unless base_dir 117 | return base_dir 118 | end 119 | 120 | end # class << self 121 | 122 | end # class 123 | end # module 124 | -------------------------------------------------------------------------------- /lib/restrack/generator/config.ru.erb: -------------------------------------------------------------------------------- 1 | # rackup config.ru 2 | require File.join(File.dirname(__FILE__),'loader') 3 | run <%= @service_name.camelize %>::WebService.new 4 | -------------------------------------------------------------------------------- /lib/restrack/generator/constants.yaml.erb: -------------------------------------------------------------------------------- 1 | #GENERATOR-CONST# -DO NOT REMOVE OR CHANGE THIS LINE- Application-Namespace => <%= @service_name %> 2 | # 3 | # = constants.yaml 4 | # This is where RESTRack applications define the constants relevant to their particular 5 | # application that are used by the RESTRack base classes. 6 | 7 | # Application log path definition 8 | :LOG: '/var/log/<%= @service_name %>/<%= @service_name %>.log' 9 | # Request log path definition 10 | :REQUEST_LOG: '/var/log/<%= @service_name %>/<%= @service_name %>.request.log' 11 | 12 | # Logger object levels 13 | :LOG_LEVEL: :DEBUG 14 | :REQUEST_LOG_LEVEL: :DEBUG 15 | 16 | # Supported formats are :JSON, :XML, :YAML, :BIN, :TEXT 17 | :DEFAULT_FORMAT: :JSON 18 | # The resource which will handle root level requests where the name is not specified. Best for users of this not to implement method_missing in their default controller, unless they are checking for bad URI. 19 | :DEFAULT_RESOURCE: nil 20 | 21 | # These are the resources which can be accessed from the root of your web service. If left empty, all resources are available at the root. 22 | :ROOT_RESOURCE_ACCEPT: [] 23 | # These are the resources which cannot be accessed from the root of your web service. Use either this or ROOT_RESOURCE_ACCEPT as a blacklist or whitelist to establish routing (relationships defined in resource controllers define further routing). 24 | :ROOT_RESOURCE_DENY: [] 25 | -------------------------------------------------------------------------------- /lib/restrack/generator/controller.rb.erb: -------------------------------------------------------------------------------- 1 | class <%= @service_name.camelize %>::<%= @name.camelize %>Controller < RESTRack::ResourceController 2 | 3 | def index 4 | 5 | end 6 | 7 | def create 8 | 9 | end 10 | 11 | def replace 12 | 13 | end 14 | 15 | def drop 16 | 17 | end 18 | 19 | def show(id) 20 | 21 | end 22 | 23 | def update(id) 24 | 25 | end 26 | 27 | def destroy(id) 28 | 29 | end 30 | 31 | def add(id) 32 | 33 | end 34 | 35 | end 36 | -------------------------------------------------------------------------------- /lib/restrack/generator/descendant_controller.rb.erb: -------------------------------------------------------------------------------- 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 36 | -------------------------------------------------------------------------------- /lib/restrack/generator/hooks.rb.erb: -------------------------------------------------------------------------------- 1 | module RESTRack 2 | class Hooks 3 | # Use this to execute a code block prior to all requests handled by your service. 4 | # For example, to do database connection per request you could establish db connections and transactions here. 5 | def pre_processor(request) 6 | 7 | end 8 | # Use this to execute code after all requests handled by your service. 9 | # For example, to do database connection per request you could commit transactions and/or teardown db connections here. 10 | def post_processor(response) 11 | 12 | end 13 | end # class Hooks 14 | end # module RESTRack 15 | -------------------------------------------------------------------------------- /lib/restrack/generator/loader.rb.erb: -------------------------------------------------------------------------------- 1 | require 'restrack' 2 | 3 | module <%= @service_name.camelize %>; end 4 | class <%= @service_name.camelize %>::WebService < RESTRack::WebService; end 5 | 6 | RESTRack::CONFIG = RESTRack::load_config(File.join(File.dirname(__FILE__), 'config/constants.yaml')) 7 | RESTRack::CONFIG[:ROOT] = File.dirname(__FILE__) 8 | 9 | require File.join(RESTRack::CONFIG[:ROOT], 'hooks') if File.exists?(File.join(RESTRack::CONFIG[:ROOT], 'hooks.rb')) 10 | 11 | # Dynamically load all controllers 12 | Find.find( File.join(File.dirname(__FILE__), 'controllers') ) do |file| 13 | next if File.extname(file) != '.rb' 14 | require file 15 | end 16 | 17 | if File.directory?( File.join(File.dirname(__FILE__), 'models') ) 18 | # Dynamically load all models 19 | Find.find( File.join(File.dirname(__FILE__), 'models') ) do |file| 20 | next if File.extname(file) != '.rb' 21 | require file 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/restrack/http_status.rb: -------------------------------------------------------------------------------- 1 | module HTTPStatus 2 | class HTTP400BadRequest < Exception; end 3 | class HTTP401Unauthorized < Exception; end 4 | class HTTP403Forbidden < Exception; end 5 | class HTTP404ResourceNotFound < Exception; end 6 | class HTTP405MethodNotAllowed < Exception; end 7 | class HTTP409Conflict < Exception; end 8 | class HTTP410Gone < Exception; end 9 | class HTTP422ResourceInvalid < Exception; end # for ActiveResource (this is not a standard HTTP response code, but AR needs it for error communication on validations) 10 | class HTTP500ServerError < Exception; end 11 | class HTTP502BadGateway < Exception; end 12 | end -------------------------------------------------------------------------------- /lib/restrack/resource_controller.rb: -------------------------------------------------------------------------------- 1 | require 'restrack/resource_relations' 2 | 3 | module RESTRack 4 | 5 | # All RESTRack controllers should descend from ResourceController. This class 6 | # provides the methods for your controllers. 7 | # 8 | # HTTP Verb: | GET | PUT | POST | DELETE 9 | # Collection URI (/widgets/): | index | replace | create | drop 10 | # Element URI (/widgets/42): | show | update | add | destroy 11 | # 12 | 13 | class ResourceController 14 | extend RESTRack::ResourceRelations 15 | 16 | attr_reader :action, :id, :params, :resource_request 17 | class << self; attr_accessor :key_type; end 18 | 19 | # Base initialization method for resources and storage of request input 20 | # This method should not be overriden in decendent classes. 21 | def self.__init(resource_request) 22 | __self = self.new 23 | return __self.__init(resource_request) 24 | end 25 | def __init(resource_request) 26 | @resource_request = resource_request 27 | @request = @resource_request.request 28 | @params = @resource_request.params 29 | @post_params = @resource_request.post_params 30 | @get_params = @resource_request.get_params 31 | self 32 | end 33 | 34 | # Call the controller's action and return output in the proper format. 35 | def call 36 | self.determine_action 37 | args = [] 38 | args << @id unless @id.blank? 39 | unless self.respond_to?(@action.to_sym) or self.respond_to?(:method_missing) 40 | raise HTTP405MethodNotAllowed, 'Method not provided on controller.' 41 | end 42 | begin 43 | self.send(@action.to_sym, *args) 44 | rescue ArgumentError 45 | raise HTTP400BadRequest, 'Method called with the incorrect number of arguments. This is most likely due to a call to an action that accepts identifier, which was not supplied in URL.' 46 | end 47 | end 48 | 49 | # all internal methods are protected rather than private so that calling methods *could* be overriden if necessary. 50 | protected 51 | 52 | # This returns a datastructure which will automatically be converted by RESTRack 53 | # into the error format expected by ActiveResource. ActiveResource expects 54 | # attribute input errors to be responded to with a status code of 422, which 55 | # is a non-standard HTTP code. Use this to produce the required format of 56 | # "......" for the response XML. 57 | def package_error(error) 58 | ARFormattedError.new(error) 59 | end 60 | 61 | # Find the action, and id if relevant, that the controller must call. 62 | def determine_action 63 | term = @resource_request.url_chain.shift 64 | if term.nil? 65 | @id = nil 66 | @action = nil 67 | # id terms can be pushed on the url_stack which are not of type String by relationship handlers 68 | elsif term.is_a? String and self.methods.include?( term.to_sym ) 69 | @id = self.methods.include?(@resource_request.url_chain[0]) ? nil : @resource_request.url_chain.shift 70 | @action = term.to_sym 71 | else 72 | begin 73 | self.class.format_string_id(term) 74 | rescue 75 | # if this isn't a valid id then it must be a request for an action that doesn't exist (we tested it as an action name above) 76 | raise HTTP405MethodNotAllowed, 'Action not provided or found and unknown HTTP request method.' 77 | end 78 | @id = term 79 | term = @resource_request.url_chain.shift 80 | if term.nil? 81 | @action = nil 82 | elsif self.methods.include?( term.to_sym ) 83 | @action = term.to_sym 84 | else 85 | raise HTTP405MethodNotAllowed, 'Action not provided or found and unknown HTTP request method.' 86 | end 87 | end 88 | @id = self.class.format_string_id(@id) if @id.is_a? String 89 | # If the action is not set with the request URI, determine the action from HTTP Verb. 90 | get_action_from_context if @action.blank? 91 | end 92 | 93 | # 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. 94 | def self.format_string_id(id) 95 | return nil unless id 96 | # default key type of resources is String 97 | self.key_type ||= String 98 | unless self.key_type.blank? or self.key_type.ancestors.include?(String) 99 | if self.key_type.ancestors.include?(Integer) 100 | id = Integer(id) 101 | elsif self.key_type.ancestors.include?(Float) 102 | id = Float(id) 103 | else 104 | raise HTTP500ServerError, "Invalid key identifier type specified on resource #{self.class.to_s}." 105 | end 106 | end 107 | id 108 | end 109 | 110 | # Get action from HTTP verb 111 | def get_action_from_context 112 | if @resource_request.request.get? 113 | @action = @id.blank? ? :index : :show 114 | elsif @resource_request.request.put? 115 | @action = @id.blank? ? :replace : :update 116 | elsif @resource_request.request.post? 117 | @action = @id.blank? ? :create : :add 118 | elsif @resource_request.request.delete? 119 | @action = @id.blank? ? :drop : :destroy 120 | else 121 | raise HTTP405MethodNotAllowed, 'Action not provided or found and unknown HTTP request method.' 122 | end 123 | end 124 | 125 | end # class ResourceController 126 | end # module RESTRack 127 | -------------------------------------------------------------------------------- /lib/restrack/resource_relations.rb: -------------------------------------------------------------------------------- 1 | module RESTRack 2 | # To extend relationship definition methods to RESTRack::ResourceController 3 | module ResourceRelations 4 | 5 | # This method allows one to access a related resource, without providing a direct link to specific relation(s). 6 | def pass_through_to(entity, opts = {}) 7 | entity_name = opts[:as] || entity 8 | define_method( entity_name.to_sym, 9 | 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 10 | @resource_request.call_controller(entity) 11 | end 12 | ) 13 | end 14 | 15 | # This method defines that there is a single link to a member from an entity collection. 16 | # The second parameter is an options hash to support setting the local name of the relation via ':as => :foo'. 17 | # 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. 18 | # This adds an accessor instance method whose name is the entity's class. 19 | def has_relationship_to(entity, opts = {}, &get_entity_id_from_relation_id) 20 | entity_name = opts[:as] || entity 21 | define_method( entity_name.to_sym, 22 | 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 23 | id = get_entity_id_from_relation_id.call(@id) 24 | @resource_request.url_chain.unshift(id) 25 | @resource_request.call_controller(entity) 26 | end 27 | ) 28 | end 29 | 30 | # This method defines that there are multiple links to members from an entity collection (an array of entity identifiers). 31 | # This adds an accessor instance method whose name is the entity's class. 32 | def has_relationships_to(entity, opts = {}, &get_entity_id_from_relation_id) 33 | entity_name = opts[:as] || entity 34 | define_method( entity_name.to_sym, 35 | 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 36 | entity_array = get_entity_id_from_relation_id.call(@id) 37 | begin 38 | index = @resource_request.url_chain.shift.to_i 39 | rescue 40 | raise HTTP400BadRequest, 'You requested an item by index and the index was not a valid number.' 41 | end 42 | unless index < entity_array.length 43 | raise HTTP404ResourceNotFound, 'You requested an item by index and the index was larger than this item\'s list of relations\' length.' 44 | end 45 | id = entity_array[index] 46 | @resource_request.url_chain.unshift(id) 47 | @resource_request.call_controller(entity) 48 | end 49 | ) 50 | end 51 | 52 | # This method defines that there are multiple links to members from an entity collection (an array of entity identifiers). 53 | # This adds an accessor instance method whose name is the entity's class. 54 | def has_defined_relationships_to(entity, opts = {}, &get_entity_id_from_relation_id) 55 | entity_name = opts[:as] || entity 56 | define_method( entity_name.to_sym, 57 | 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 58 | entity_array = get_entity_id_from_relation_id.call(@id) 59 | id = @resource_request.url_chain.shift 60 | raise HTTP400BadRequest, 'No ID provided for has_defined_relationships_to routing.' if id.nil? 61 | id = RESTRack.controller_class_for(entity).format_string_id(id) if id.is_a? String 62 | unless entity_array.include?( id ) 63 | raise HTTP404ResourceNotFound, 'Relation entity does not belong to referring resource.' 64 | end 65 | @resource_request.url_chain.unshift(id) 66 | @resource_request.call_controller(entity) 67 | end 68 | ) 69 | end 70 | 71 | # This method defines that there are mapped links to members from an entity collection (a hash of entity identifiers). 72 | # This adds an accessor instance method whose name is the entity's class. 73 | def has_mapped_relationships_to(entity, opts = {}, &get_entity_id_from_relation_id) 74 | entity_name = opts[:as] || entity 75 | define_method( entity_name.to_sym, 76 | 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 77 | entity_map = get_entity_id_from_relation_id.call(@id) 78 | key = @resource_request.url_chain.shift 79 | id = entity_map[key.to_sym] 80 | @resource_request.url_chain.unshift(id) 81 | @resource_request.call_controller(entity) 82 | end 83 | ) 84 | end 85 | 86 | # Allows decendent controllers to set a data type for the id other than the default. 87 | def keyed_with_type(klass) 88 | self.key_type = klass 89 | end 90 | 91 | end # module ResourceRelations 92 | end # module RESTRack 93 | -------------------------------------------------------------------------------- /lib/restrack/resource_request.rb: -------------------------------------------------------------------------------- 1 | module RESTRack 2 | # The ResourceRequest class handles all incoming requests. 3 | class ResourceRequest 4 | attr_reader :request, :request_id, :params, :post_params, :get_params, :active_controller 5 | attr_accessor :mime_type, :url_chain 6 | 7 | # Initialize the ResourceRequest by assigning a request_id and determining the path, format, and controller of the resource. 8 | # Accepting options to allow us to override request_id for testing. 9 | def initialize(opts) 10 | @request = opts[:request] 11 | @request_id = opts[:request_id] || get_request_id 12 | # Write input details to logs 13 | RESTRack.request_log.info "{#{@request_id}} #{@request.request_method} #{@request.path_info} requested from #{@request.ip}" 14 | @headers = Rack::Utils::HeaderHash.new 15 | @request.env.select {|k,v| k.start_with? 'HTTP_'}.each do |k,v| 16 | @headers[k.sub(/^HTTP_/, '')] = v 17 | end 18 | end 19 | 20 | def prepare 21 | # MIME type should be determined before raising any exceptions for proper error reporting 22 | # Set up the initial routing. 23 | @url_chain = @request.path_info.split('/') 24 | @url_chain.shift if @url_chain[0] == '' 25 | # Pull extension from URL 26 | extension = '' 27 | unless @url_chain[-1].nil? 28 | @url_chain[-1] = @url_chain[-1].sub(/\.([^.]*)$/) do |s| 29 | extension = $1.downcase 30 | '' # Return an empty string as the substitution so that the extension is removed from `@url_chain[-1]` 31 | end 32 | end 33 | # Determine MIME type from extension 34 | @mime_type = get_mime_type_from( extension ) 35 | # Now safe to raise exceptions 36 | raise HTTP400BadRequest, "Request path of #{@request.path_info} is invalid" if @request.path_info.include?('//') 37 | 38 | # For CORS support 39 | if RESTRack::CONFIG[:CORS] 40 | raise HTTP403Forbidden if @headers['Origin'].nil? 41 | raise HTTP403Forbidden unless RESTRack::CONFIG[:CORS]['Access-Control-Allow-Origin'] == '*' or RESTRack::CONFIG[:CORS]['Access-Control-Allow-Origin'].include?(@headers['Origin']) 42 | raise HTTP403Forbidden unless @request.env['REQUEST_METHOD'] == 'OPTIONS' or RESTRack::CONFIG[:CORS]['Access-Control-Allow-Methods'] == '*' or RESTRack::CONFIG[:CORS]['Access-Control-Allow-Methods'].include?(@request.env['REQUEST_METHOD']) 43 | end 44 | 45 | # Pull input data from POST body 46 | @post_params = parse_body( @request ) 47 | @get_params = parse_query_string( @request ) 48 | @params = {} 49 | # TODO: symbolize! 50 | if @post_params.respond_to?(:merge) 51 | @params = @post_params.merge( @get_params ) 52 | else 53 | @params = @get_params 54 | end 55 | RESTRack.log.debug 'combined params: ' + @params.inspect 56 | 57 | # Pull first controller from URL 58 | @active_resource_name = @url_chain.shift 59 | unless @active_resource_name.nil? or RESTRack.controller_exists?(@active_resource_name) 60 | @url_chain.unshift( @active_resource_name ) 61 | end 62 | if @active_resource_name.nil? or not RESTRack.controller_exists?(@active_resource_name) 63 | raise HTTP404ResourceNotFound unless RESTRack::CONFIG[:DEFAULT_RESOURCE] 64 | @active_resource_name = RESTRack::CONFIG[:DEFAULT_RESOURCE] 65 | end 66 | raise HTTP403Forbidden unless RESTRack::CONFIG[:ROOT_RESOURCE_ACCEPT].blank? or RESTRack::CONFIG[:ROOT_RESOURCE_ACCEPT].include?(@active_resource_name) 67 | raise HTTP403Forbidden if not RESTRack::CONFIG[:ROOT_RESOURCE_DENY].blank? and RESTRack::CONFIG[:ROOT_RESOURCE_DENY].include?(@active_resource_name) 68 | @active_controller = instantiate_controller( @active_resource_name ) 69 | end 70 | 71 | # Call the next entity in the path stack. 72 | # Method called by controller relationship methods. 73 | def call_controller(resource_name) 74 | @active_resource_name = resource_name 75 | @active_controller = instantiate_controller( resource_name.to_s.camelize ) 76 | @active_controller.call 77 | end 78 | 79 | def content_type 80 | @mime_type.to_s 81 | end 82 | 83 | private 84 | def get_request_id 85 | t = Time.now 86 | return t.strftime('%FT%T') + '.' + t.usec.to_s 87 | end 88 | 89 | # Pull input data from POST body 90 | def parse_body(request) 91 | post_params = request.body.read 92 | RESTRack.log.debug "{#{@request_id}} #{request.content_type} raw POST data in:\n" + post_params 93 | if RESTRack::CONFIG[:TRANSCODE] 94 | post_params.encode!( RESTRack::CONFIG[:TRANSCODE] ) 95 | end 96 | if RESTRack::CONFIG[:FORCE_ENCODING] 97 | post_params = post_params.force_encoding( RESTRack::CONFIG[:FORCE_ENCODING] ) 98 | end 99 | unless request.content_type.blank? 100 | request_mime_type = MIME::Type.new( request.content_type ) 101 | if request_mime_type.like?( RESTRack.mime_type_for( :JSON ) ) 102 | post_params = JSON.parse( post_params ) rescue post_params 103 | elsif request_mime_type.like?( RESTRack.mime_type_for( :XML ) ) 104 | post_params = XmlSimple.xml_in( post_params, 'ForceArray' => false ) rescue post_params 105 | if post_params.respond_to? :each_key 106 | post_params.each_key do |p| 107 | post_params[p] = nil if post_params[p].is_a?(Hash) and post_params[p]['nil'] # XmlSimple oddity 108 | if post_params[p].is_a? Hash and post_params[p]['type'] == 'integer' 109 | begin 110 | post_params[p] = Integer(post_params[p]['content']) 111 | rescue 112 | raise HTTP422ResourceInvalid, "Integer type declared but non-integer supplied in XML #{p.to_s} node: " + post_params[p]['content'].to_s 113 | end 114 | end 115 | if post_params[p].is_a? Hash and post_params[p].keys.empty? 116 | post_params[p] = nil 117 | end 118 | end 119 | end 120 | elsif request_mime_type.like?( RESTRack.mime_type_for( :YAML ) ) 121 | post_params = YAML.parse( post_params ) rescue post_params 122 | end 123 | end 124 | RESTRack.log.debug "{#{@request_id}} #{request_mime_type.to_s} parsed POST data in:\n" + post_params.pretty_inspect 125 | post_params 126 | end 127 | 128 | def parse_query_string(request) 129 | get_params = request.GET 130 | RESTRack.log.debug "{#{@request_id}} GET data in:\n" + get_params.pretty_inspect 131 | get_params 132 | end 133 | 134 | # Determine the MIME type of the request from the extension provided. 135 | def get_mime_type_from(extension) 136 | unless extension.blank? 137 | mime_type = RESTRack.mime_type_for( extension ) 138 | end 139 | if mime_type.blank? 140 | unless RESTRack::CONFIG[:DEFAULT_FORMAT].blank? 141 | mime_type = RESTRack.mime_type_for( RESTRack::CONFIG[:DEFAULT_FORMAT].to_s.downcase ) 142 | else 143 | mime_type = RESTRack.mime_type_for( :JSON ) 144 | end 145 | end 146 | mime_type 147 | end 148 | 149 | # Called from the locate method, this method dynamically finds the class based on the URI and instantiates an object of that class via the __init method on RESTRack::ResourceController. 150 | def instantiate_controller( resource_name ) 151 | RESTRack.log.debug "{#{@request_id}} Locating Resource #{resource_name}" 152 | begin 153 | return RESTRack.controller_class_for( resource_name ).__init(self) 154 | rescue Exception => e 155 | raise HTTP404ResourceNotFound, "The resource #{RESTRack::CONFIG[:SERVICE_NAME]}::#{RESTRack.controller_name(resource_name)} could not be instantiated." 156 | end 157 | end 158 | 159 | end # class ResourceRequest 160 | end # module RESTRack -------------------------------------------------------------------------------- /lib/restrack/response.rb: -------------------------------------------------------------------------------- 1 | module RESTRack 2 | class Response 3 | attr_reader :status, :content_type, :body, :mime_type, :headers, :request 4 | 5 | def initialize(request) 6 | @request = request 7 | begin 8 | @request.prepare 9 | RESTRack.log.debug "{#{@request.request_id}} Retrieving Output" 10 | if RESTRack::CONFIG[:CORS] and @request.request.env['REQUEST_METHOD'] == 'OPTIONS' 11 | @body = '' 12 | @status = 200 13 | else 14 | @body = @request.active_controller.call 15 | @status = body.blank? ? 204 : 200 16 | end 17 | RESTRack.log.debug "(#{@request.request_id}) HTTP200OK '#{@request.mime_type.to_s}' response data:\n" + @body.inspect 18 | RESTRack.request_log.info "(#{@request.request_id}) HTTP200OK" 19 | rescue Exception => exception 20 | # This will log the returned status code 21 | if @request && @request.request_id 22 | RESTRack.request_log.info "(#{@request.request_id}) #{exception.class.to_s} " + exception.message 23 | else 24 | RESTRack.request_log.info "() #{exception.class.to_s} " + exception.message 25 | end 26 | case 27 | when exception.is_a?( HTTP400BadRequest ) 28 | @status = 400 29 | @body = exception.message || 'The request cannot be fulfilled due to bad syntax.' 30 | when exception.is_a?( HTTP401Unauthorized ) 31 | @status = 401 32 | @body = exception.message || 'You have failed authentication for access to the resource.' 33 | when exception.is_a?( HTTP403Forbidden ) 34 | @status = 403 35 | @body = exception.message || 'You are forbidden to access that resource.' 36 | when exception.is_a?( HTTP404ResourceNotFound ) 37 | @status = 404 38 | @body = exception.message || 'The resource you requested could not be found.' 39 | when exception.is_a?( HTTP405MethodNotAllowed ) 40 | @status = 405 41 | @body = exception.message || 'The resource you requested does not support the request method provided.' 42 | when exception.is_a?( HTTP409Conflict ) 43 | @status = 409 44 | @body = exception.message || 'The resource you requested is in a conflicted state.' 45 | when exception.is_a?( HTTP410Gone ) 46 | @status = 410 47 | @body = exception.message || 'The resource you requested is no longer available.' 48 | when exception.is_a?( HTTP422ResourceInvalid ) 49 | @status = 422 50 | @body = exception.message || 'Invalid attribute values sent for resource.' 51 | when exception.is_a?( HTTP502BadGateway ) 52 | @status = 502 53 | @body = exception.message || 'The server was acting as a gateway or proxy and received an invalid response from the upstream server.' 54 | log_server_warning(exception) 55 | else # HTTP500ServerError 56 | server_error(exception) 57 | end # case Exception 58 | end # begin / rescue 59 | @mime_type = MIME::Type.new(@request.mime_type) 60 | @content_type = @request.content_type 61 | end 62 | 63 | def output 64 | @headers ||= {} 65 | @headers['Content-Type'] = content_type 66 | if RESTRack::CONFIG[:CORS] 67 | @headers['Access-Control-Allow-Origin'] = RESTRack::CONFIG[:CORS]['Access-Control-Allow-Origin'] if RESTRack::CONFIG[:CORS]['Access-Control-Allow-Origin'] 68 | @headers['Access-Control-Allow-Methods'] = RESTRack::CONFIG[:CORS]['Access-Control-Allow-Methods'] if RESTRack::CONFIG[:CORS]['Access-Control-Allow-Methods'] 69 | end 70 | return [status, headers, [package(body)] ] 71 | end 72 | 73 | private 74 | def log_server_warning(exception) 75 | if @request && @request.request_id 76 | RESTRack.log.warn "(#{@request.request_id}) #{exception.class.to_s} " + exception.message + "\n" + exception.backtrace.join("\n") 77 | else 78 | RESTRack.log.warn "() #{exception.class.to_s} " + exception.message + "\n" + exception.backtrace.join("\n") 79 | end 80 | end 81 | 82 | def log_server_error(exception) 83 | if @request && @request.request_id 84 | RESTRack.log.error "(#{@request.request_id}) #{exception.class.to_s} " + exception.message + "\n" + exception.backtrace.join("\n") 85 | else 86 | RESTRack.log.error "() #{exception.class.to_s} " + exception.message + "\n" + exception.backtrace.join("\n") 87 | end 88 | end 89 | 90 | def server_error(exception) 91 | log_server_error(exception) 92 | msg = '' 93 | if RESTRack::CONFIG[:SHOW_STACK] 94 | msg = (exception.message == exception.class.to_s) ? exception.backtrace.join("\n") : exception.message + "\nstack trace:\n" + exception.backtrace.join("\n") 95 | else 96 | msg = exception.message 97 | end 98 | @status = 500 99 | @body = msg || 'Server Error.' 100 | end 101 | 102 | # This handles outputing properly formatted content based on the file extension in the URL. 103 | def package(data) 104 | if mime_type.like?( RESTRack.mime_type_for( :JSON ) ) 105 | output = data.to_json 106 | elsif mime_type.like?( RESTRack.mime_type_for( :JS ) ) and !@request.get_params['callback'].nil? 107 | output = @request.get_params['callback'] + "(#{data.to_json});" 108 | elsif mime_type.like?( RESTRack.mime_type_for( :XML ) ) 109 | if File.exists? builder_file 110 | output = builder_up(data) 111 | elsif data.respond_to?(:to_xml) 112 | output = data.to_xml 113 | else 114 | output = XmlSimple.xml_out(data, 'AttrPrefix' => true, 'XmlDeclaration' => true, 'NoIndent' => true) 115 | end 116 | elsif mime_type.like?(RESTRack.mime_type_for( :YAML ) ) 117 | output = YAML.dump(data) 118 | elsif mime_type.like?(RESTRack.mime_type_for( :TEXT ) ) 119 | output = data.to_s 120 | else 121 | output = data 122 | end 123 | return output 124 | end # def package 125 | 126 | # Use Builder to generate the XML. 127 | def builder_up(data) 128 | buffer = '' 129 | xml = Builder::XmlMarkup.new(:target => buffer) 130 | xml.instruct! 131 | eval( File.new( builder_file ).read ) 132 | return buffer 133 | end 134 | 135 | # Builds the path to the builder file for the current controller action. 136 | def builder_file 137 | "#{RESTRack::CONFIG[:ROOT]}/views/#{@active_resource_name}/#{@request.active_controller.action}.xml.builder" 138 | end 139 | 140 | end # class Response 141 | end # module RESTRack -------------------------------------------------------------------------------- /lib/restrack/support.rb: -------------------------------------------------------------------------------- 1 | module RESTRack 2 | require 'mime/types' 3 | require 'yaml' 4 | require 'logger' 5 | 6 | class << self 7 | def log; @@log; end 8 | def request_log; @@request_log; end 9 | end # of class methods 10 | 11 | def self.load_config(file) 12 | config = YAML.load_file(file) 13 | # Open the logs on spin up. 14 | @@log ||= Logger.new( config[:LOG] ) 15 | @@log.level = Logger.const_get( config[:LOG_LEVEL] ) 16 | @@request_log ||= Logger.new( config[:REQUEST_LOG] ) 17 | @@request_log.level = Logger.const_get( config[:REQUEST_LOG_LEVEL] ) 18 | # Do config validations 19 | if config[:ROOT_RESOURCE_ACCEPT].is_a?(Array) and config[:ROOT_RESOURCE_ACCEPT].length == 1 and config[:ROOT_RESOURCE_ACCEPT][0].lstrip.rstrip == '' 20 | config[:ROOT_RESOURCE_ACCEPT] = nil 21 | @@log.warn 'Improper format for RESTRack::CONFIG[:ROOT_RESOURCE_ACCEPT], should be nil or empty array not array containing empty string.' 22 | end 23 | if not config[:ROOT_RESOURCE_ACCEPT].blank? and not config[:DEFAULT_RESOURCE].blank? and not config[:ROOT_RESOURCE_ACCEPT].include?( config[:DEFAULT_RESOURCE] ) 24 | @@log.warn 'RESTRack::CONFIG[:DEFAULT_RESOURCE] should be a member of RESTRack::CONFIG[:ROOT_RESOURCE_ACCEPT].' 25 | end 26 | config 27 | end 28 | 29 | def self.mime_type_for(format) 30 | MIME::Types.type_for(format.to_s.downcase)[0] 31 | end 32 | 33 | def self.controller_exists?(resource_name) 34 | begin 35 | return Kernel.const_get( RESTRack::CONFIG[:SERVICE_NAME].to_sym ).const_defined?( controller_name(resource_name).to_sym ) 36 | rescue # constants can't start with numerics 37 | return false 38 | end 39 | end 40 | 41 | def self.controller_class_for(resource_name) 42 | Kernel.const_get( RESTRack::CONFIG[:SERVICE_NAME].to_sym ).const_get( controller_name(resource_name).to_sym ) 43 | end 44 | 45 | def self.controller_name(resource_name) 46 | "#{resource_name.to_s.camelize}Controller".to_sym 47 | end 48 | 49 | def self.controller_has_action?(resource_name, action) 50 | controller_class_for(resource_name).const_defined?( action.to_sym ) 51 | end 52 | 53 | end 54 | 55 | class Object 56 | def blank? 57 | # Courtesy of Rails' ActiveSupport, thank you DHH et al. 58 | respond_to?(:empty?) ? empty? : !self 59 | end 60 | end 61 | 62 | class ARFormattedError < String 63 | # provide this method, as if it is present it will be used to render the xml rather than XmlSimple 64 | def to_xml 65 | "#{self}" 66 | end 67 | def to_json 68 | "{\"errors\": [{\"error\": \"#{self}\"}]}" 69 | end 70 | end 71 | 72 | # We will support ".text" as an extension 73 | MIME::Types['text/plain'][0].extensions << 'text' 74 | MIME::Types.index_extensions( MIME::Types['text/plain'][0] ) 75 | -------------------------------------------------------------------------------- /lib/restrack/version.rb: -------------------------------------------------------------------------------- 1 | module RESTRack 2 | VERSION = "1.6.8" 3 | end 4 | -------------------------------------------------------------------------------- /lib/restrack/web_service.rb: -------------------------------------------------------------------------------- 1 | module RESTRack 2 | class WebService 3 | 4 | # Establish the namespace pointer. 5 | def initialize 6 | RESTRack::CONFIG[:SERVICE_NAME] = self.class.to_s.split('::')[0].to_sym 7 | @request_hook = RESTRack::Hooks.new if RESTRack.const_defined?(:Hooks) 8 | end 9 | 10 | # Handle requests in the Rack way. 11 | def call( env ) 12 | resource_request = RESTRack::ResourceRequest.new( :request => Rack::Request.new(env) ) 13 | unless @request_hook.nil? or (RESTRack::CONFIG.has_key?(:PRE_PROCESSOR_DISABLED) and RESTRack::CONFIG[:PRE_PROCESSOR_DISABLED]) 14 | @request_hook.pre_processor(resource_request) 15 | end 16 | response = RESTRack::Response.new(resource_request) 17 | unless @request_hook.nil? or (RESTRack::CONFIG.has_key?(:POST_PROCESSOR_DISABLED) and RESTRack::CONFIG[:POST_PROCESSOR_DISABLED]) 18 | @request_hook.post_processor(response) 19 | end 20 | return response.output 21 | end # method call 22 | 23 | end # class WebService 24 | end # module RESTRack -------------------------------------------------------------------------------- /restrack.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "restrack/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "restrack" 7 | s.version = RESTRack::VERSION 8 | s.platform = Gem::Platform::RUBY 9 | s.authors = ['Chris St. John'] 10 | s.email = ['chris@stjohnstudios.com'] 11 | s.homepage = 'http://github.com/stjohncj/RESTRack' 12 | s.summary = %q{A lightweight MVC framework developed specifically for JSON (and XML) REST services.} 13 | s.description = %q{ 14 | RESTRack is a Rack-based MVC framework that makes it extremely easy to develop RESTful data services. It is inspired by 15 | Rails, and follows a few of its conventions. But it has no routes file, routing relationships are done through 16 | supplying custom code blocks to class methods such as "has_relationship_to" or "has_mapped_relationships_to". 17 | 18 | RESTRack aims at being lightweight and easy to use. It will automatically render JSON and XML for the data 19 | structures you return in your actions (any structure parsable by the "json" and 20 | "xml-simple" gems, respectively). 21 | 22 | If you supply a view for a controller action, you do that using a builder file. Builder files are stored in the 23 | view directory grouped by controller name subdirectories (`view//.xml.builder`). XML format 24 | requests will then render the view template with the builder gem, rather than generating XML with XmlSimple. 25 | } 26 | 27 | s.rubyforge_project = "restrack" 28 | 29 | s.files = `git ls-files`.split("\n") 30 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 31 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 32 | s.require_paths = ["lib"] 33 | 34 | s.add_runtime_dependency 'rack' 35 | s.add_development_dependency 'rack-test' 36 | s.add_runtime_dependency 'i18n' 37 | s.add_runtime_dependency 'json' 38 | s.add_runtime_dependency 'xml-simple', '>= 1.0.13' 39 | s.add_runtime_dependency 'builder' 40 | s.add_runtime_dependency 'activesupport' 41 | s.add_runtime_dependency 'mime-types' 42 | 43 | end 44 | -------------------------------------------------------------------------------- /test/sample_app_1/config.ru: -------------------------------------------------------------------------------- 1 | # Rails.root/config.ru 2 | require File.join(File.dirname(__FILE__), 'loader') 3 | run SampleApp::WebService.new 4 | -------------------------------------------------------------------------------- /test/sample_app_1/config/constants.yaml: -------------------------------------------------------------------------------- 1 | #GENERATOR-CONST# -DO NOT REMOVE OR CHANGE THIS LINE- Application-Namespace => SampleApp 2 | # 3 | # = constants.yaml 4 | # This is where RESTRack applications define the constants relevant to their particular 5 | # application that are used by the RESTRack base classes. 6 | 7 | # Application log path definition 8 | :LOG: '/var/log/restrack/sample_app.log' 9 | # Request log path definition 10 | :REQUEST_LOG: '/var/log/restrack/sample_app.request.log' 11 | 12 | # Logger object levels 13 | :LOG_LEVEL: :DEBUG # Logger object level 14 | :REQUEST_LOG_LEVEL: :DEBUG # Logger object level 15 | 16 | # Supported formats are :JSON, :XML, :YAML, :BIN, :TEXT 17 | :DEFAULT_FORMAT: :JSON 18 | # The resource which will handle root level requests where the name is not specified. Best for users of this not to implement method_missing in their default controller, unless they are checking for bad URI. 19 | # This setting ('bazu') won't work because of :ROOT_RESOURCE_ACCEPT VALUE (:DEFAULT_RESOURCE should be a member of :ROOT_RESOURCE_ACCEPT). 20 | :DEFAULT_RESOURCE: bazu 21 | 22 | # These are the resources which can be accessed from the root of your web service. If left empty, all resources are available at the root. 23 | :ROOT_RESOURCE_ACCEPT: [ foo_bar, errors ] 24 | # These are the resources which cannot be accessed from the root of your web service. Use either this or ROOT_RESOURCE_ACCEPT as a blacklist or whitelist to establish routing (relationships defined in resource controllers define further routing). 25 | :ROOT_RESOURCE_DENY: [ baz ] 26 | 27 | # The stack trace will not be added to 500 response body by default, set to true to enable. 28 | :SHOW_STACK: true 29 | 30 | # :TRANSCODE: and :FORCE_ENCODING: are optional config settings 31 | # String#encode will be called when this value is set 32 | #:TRANSCODE: ISO-8859-1 #or UTF-8 etc 33 | # String#force_encoding will be called when this value is set 34 | #:FORCE_ENCODING: ISO-8859-1 35 | 36 | # :CORS: is an optional config setting 37 | # CORS Header configuration 38 | # Supported: 39 | # - Access-Control-Allow-Origin: http://localhost 40 | # - Access-Control-Allow-Methods: POST, GET 41 | # List of all: 42 | # - Access-Control-Allow-Origin: | * 43 | # e.g. Access-Control-Allow-Origin: http://mozilla.com 44 | # - Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header 45 | # - Access-Control-Max-Age: 46 | # - Access-Control-Allow-Credentials: true | false 47 | # - Access-Control-Allow-Methods: [, ]* 48 | # e.g. Access-Control-Allow-Methods: POST, GET 49 | # - Access-Control-Allow-Headers: [, ]* 50 | #:CORS: 51 | # Access-Control-Allow-Origin: http://restrack.me 52 | # Access-Control-Allow-Methods: POST, GET 53 | 54 | # :PRE_PROCESSOR_DISABLED: and :POST_PROCESSOR_DISABLED: are optional config settings and are false by default 55 | #:PRE_PROCESSOR_DISABLED: true 56 | #:POST_PROCESSOR_DISABLED: true -------------------------------------------------------------------------------- /test/sample_app_1/controllers/bat_controller.rb: -------------------------------------------------------------------------------- 1 | class SampleApp::BatController < RESTRack::ResourceController 2 | 3 | def show(id) 4 | return { :WOM => 'BAT!' } if id == 777 5 | return { :WOM => 'NOBAT!' } if id == '777' 6 | return { :SUHWING => 'BATTER' } 7 | end 8 | 9 | end -------------------------------------------------------------------------------- /test/sample_app_1/controllers/bata_controller.rb: -------------------------------------------------------------------------------- 1 | class SampleApp::BataController < RESTRack::ResourceController 2 | 3 | def show(id) 4 | return { :BAZ => 'HOLA!' } if id == 777 5 | return { :BAZ => 'ALOHA!' } if id == '777' 6 | return { :OTHER => 'YUP' } 7 | end 8 | 9 | def index 10 | return [1,2,3,4,5] 11 | end 12 | 13 | end -------------------------------------------------------------------------------- /test/sample_app_1/controllers/baz_controller.rb: -------------------------------------------------------------------------------- 1 | class SampleApp::BazController < RESTRack::ResourceController 2 | 3 | def show(id) 4 | return { :BAZ => 'HOLA!' } if id == 777 5 | return { :BAZ => 'ALOHA!' } if id == '777' 6 | return { :OTHER => 'YUP' } 7 | end 8 | 9 | end -------------------------------------------------------------------------------- /test/sample_app_1/controllers/baza_controller.rb: -------------------------------------------------------------------------------- 1 | class SampleApp::BazaController < RESTRack::ResourceController 2 | 3 | keyed_with_type Fixnum 4 | 5 | def show(id) 6 | return { :BAZA => 'YESSIR' } if id == 1 7 | return { :NOWAY => 'JOSE' } if id == 8 8 | return { :NOTTODAY => 'JOSEPH' } 9 | end 10 | 11 | end -------------------------------------------------------------------------------- /test/sample_app_1/controllers/bazu_controller.rb: -------------------------------------------------------------------------------- 1 | class SampleApp::BazuController < RESTRack::ResourceController 2 | 3 | def show(id) 4 | return 1 if id == 1 5 | return 0 6 | end 7 | 8 | def index 9 | return [ 10 | { :id => 1, :val => 111 }, 11 | { :id => 2, :val => 222 }, 12 | { :id => 3, :val => 333 } 13 | ] 14 | end 15 | 16 | end -------------------------------------------------------------------------------- /test/sample_app_1/controllers/errors_controller.rb: -------------------------------------------------------------------------------- 1 | class SampleApp::ErrorsController < RESTRack::ResourceController 2 | 3 | #module HTTPStatus 4 | # class HTTP400BadRequest < Exception; end 5 | # class HTTP401Unauthorized < Exception; end 6 | # class HTTP403Forbidden < Exception; end 7 | # class HTTP404ResourceNotFound < Exception; end 8 | # class HTTP405MethodNotAllowed < Exception; end 9 | # class HTTP409Conflict < Exception; end 10 | # class HTTP410Gone < Exception; end 11 | # class HTTP500ServerError < Exception; end 12 | #end 13 | 14 | def bad_request 15 | raise HTTP400BadRequest, package_error('tester') 16 | end 17 | 18 | def unauthorized 19 | raise HTTP401Unauthorized, package_error('tester') 20 | end 21 | 22 | def forbidden 23 | raise HTTP403Forbidden, package_error('tester') 24 | end 25 | 26 | def resource_not_found 27 | raise HTTP404ResourceNotFound, package_error('tester') 28 | end 29 | 30 | def method_not_allowed 31 | raise HTTP405MethodNotAllowed, package_error('tester') 32 | end 33 | 34 | def conflict 35 | raise HTTP409Conflict, package_error('tester') 36 | end 37 | 38 | def gone 39 | raise HTTP410Gone, package_error('tester') 40 | end 41 | 42 | def resource_invalid 43 | raise HTTP422ResourceInvalid, package_error('This is a WebDAV HTTP extension code used by ActiveResource to communicate validation errors.') 44 | end 45 | 46 | def resource_invalid_active_resource_format 47 | raise HTTP422ResourceInvalid, package_error('This is how ActiveResource expects errors to come through.') 48 | end 49 | 50 | def server_error 51 | raise HTTP500ServerError, package_error('tester') 52 | end 53 | 54 | def server_error_with_backtrace 55 | raise HTTP500ServerError 56 | end 57 | 58 | end 59 | -------------------------------------------------------------------------------- /test/sample_app_1/controllers/foo_bar_controller.rb: -------------------------------------------------------------------------------- 1 | class SampleApp::FooBarController < RESTRack::ResourceController 2 | 3 | pass_through_to( :bata ) 4 | pass_through_to( :bata, :as => :other_bata ) 5 | 6 | has_relationship_to( :baz ) do |id| 7 | if id =='144' 8 | output = '777' 9 | else 10 | output = '666' 11 | end 12 | output # You can't "return" from a Proc! It will do a "return" in the outer method. Remember a "Proc" is not a Method. 13 | end 14 | 15 | has_relationship_to( :bat, :as => :slugger ) do |id| 16 | if id =='144' 17 | output = '777' 18 | else 19 | output = '666' 20 | end 21 | output # You can't "return" from a Proc! It will do a "return" in the outer method. Remember a "Proc" is not a Method. 22 | end 23 | 24 | has_relationships_to( :baza, :as => :children ) do |id| 25 | [1,2,3,4,5,6,7,8,9] 26 | end 27 | 28 | has_defined_relationships_to( :baza, :as => :def ) do |id| 29 | [1,8,9,17] 30 | end 31 | 32 | has_mapped_relationships_to( :bazu, :as => :maps ) do |id| 33 | { 34 | :first => 1, 35 | :second => 2, 36 | :third => 3 37 | } 38 | end 39 | 40 | def index 41 | [1,2,3,4,5,6,7] 42 | end 43 | def create 44 | { :success => true } 45 | end 46 | def replace 47 | { :success => true } 48 | end 49 | def drop 50 | { :success => true } 51 | end 52 | 53 | def show(id) 54 | if id == '1234567890' 55 | return { :foo => 'abc', :bar => '123', 'baz' => 456, :more => { :one => 1, :two => [1,2], :three => :deep_fu } } 56 | end 57 | if id == '42' 58 | return { 59 | :foo => 'abc', 60 | :bar => 123, 61 | :baz => { 62 | 'one' => [1], 63 | 'two' => ['1','2'], 64 | 'three' => ['1', 2, {:three => 3}], 65 | 4 => :four 66 | } 67 | } 68 | end 69 | return { :foo => 'bar', :baz => 123 } 70 | end 71 | def update(id) 72 | { :success => true } 73 | end 74 | def destroy(id) 75 | { :success => true } 76 | end 77 | def add(id) 78 | { :success => true } 79 | end 80 | 81 | def complex_show_xml_no_builder(id) 82 | if id == '1234567890' 83 | return { :foo => 'abc', :bar => '123', 'baz' => 456, :more => { :one => 1, :two => [1,2], :three => :deep_fu } } 84 | end 85 | if id == '42' 86 | return { 87 | :foo => 'abc', 88 | :bar => 123, 89 | :baz => { 90 | 'one' => [1], 91 | 'two' => ['1','2'], 92 | 'three' => ['1', 2, {:three => 3}], 93 | 4 => :four 94 | } 95 | } 96 | end 97 | end 98 | 99 | def echo 100 | return @post_params 101 | end 102 | 103 | def echo_get 104 | return @params.merge({ 'get?' => @resource_request.request.get?.to_s }) 105 | end 106 | 107 | def custom_entity(id) 108 | return id 109 | end 110 | 111 | def custom_collection 112 | return [1,1,2,3,5,8,13,21,34] 113 | end 114 | 115 | def show_encoding 116 | return @post_params.encoding.to_s 117 | end 118 | 119 | end 120 | -------------------------------------------------------------------------------- /test/sample_app_1/loader.rb: -------------------------------------------------------------------------------- 1 | # for development only 2 | $:.unshift File.expand_path(File.join(File.dirname(__FILE__),'../../lib')) 3 | ##### 4 | require 'restrack' 5 | 6 | module SampleApp; end 7 | class SampleApp::WebService < RESTRack::WebService; end 8 | 9 | RESTRack::CONFIG = RESTRack::load_config(File.join(File.dirname(__FILE__), 'config/constants.yaml')) 10 | RESTRack::CONFIG[:ROOT] = File.dirname(__FILE__) 11 | 12 | # Dynamically load all controllers 13 | Find.find( File.join(File.dirname(__FILE__), 'controllers') ) do |file| 14 | next if File.extname(file) != '.rb' 15 | require file 16 | end 17 | 18 | if File.directory?( File.join(File.dirname(__FILE__), 'models') ) 19 | # Dynamically load all models 20 | Find.find( File.join(File.dirname(__FILE__), 'models') ) do |file| 21 | next if File.extname(file) != '.rb' 22 | require file 23 | end 24 | end 25 | 26 | puts "sample_app_1 RESTRack::CONFIG:\n" 27 | config = RESTRack::CONFIG.keys.map {|c| c.to_s }.sort 28 | config.each do |key| 29 | puts "\t" + key + ' => ' + RESTRack::CONFIG[key.to_sym].to_s 30 | end 31 | puts "\n" 32 | -------------------------------------------------------------------------------- /test/sample_app_1/test/test_callback.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'test/unit' 3 | require 'rack/test' 4 | require File.expand_path(File.join(File.dirname(__FILE__),'..','loader')) 5 | require 'pp' 6 | 7 | class SampleApp::TestCallback < Test::Unit::TestCase 8 | 9 | def setup 10 | @ws = SampleApp::WebService.new 11 | end 12 | 13 | def test_show 14 | env = Rack::MockRequest.env_for('/foo_bar/144.js?callback=my_function', { 15 | :method => 'GET' 16 | }) 17 | output = '' 18 | assert_nothing_raised do 19 | output = @ws.call(env) 20 | end 21 | test_val = 'my_function('+ { :foo => 'bar', :baz => 123 }.to_json + ');' 22 | assert_equal test_val, output[2][0] 23 | end 24 | 25 | end 26 | -------------------------------------------------------------------------------- /test/sample_app_1/test/test_controller_actions.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'test/unit' 3 | require 'rack/test' 4 | require File.expand_path(File.join(File.dirname(__FILE__),'..','loader')) 5 | require 'pp' 6 | 7 | class SampleApp::TestControllerActions < Test::Unit::TestCase 8 | 9 | def setup 10 | @ws = SampleApp::WebService.new 11 | end 12 | 13 | def test_show 14 | env = Rack::MockRequest.env_for('/foo_bar/144', { 15 | :method => 'GET' 16 | }) 17 | output = '' 18 | assert_nothing_raised do 19 | output = @ws.call(env) 20 | end 21 | test_val = { :foo => 'bar', :baz => 123 }.to_json 22 | assert_equal test_val, output[2][0] 23 | end 24 | 25 | def test_update 26 | env = Rack::MockRequest.env_for('/foo_bar/144', { 27 | :method => 'PUT' 28 | }) 29 | output = '' 30 | assert_nothing_raised do 31 | output = @ws.call(env) 32 | end 33 | test_val = { :success => true }.to_json 34 | assert_equal test_val, output[2][0] 35 | end 36 | 37 | def test_add 38 | env = Rack::MockRequest.env_for('/foo_bar/144', { 39 | :method => 'POST' 40 | }) 41 | output = '' 42 | assert_nothing_raised do 43 | output = @ws.call(env) 44 | end 45 | test_val = { :success => true }.to_json 46 | assert_equal test_val, output[2][0] 47 | end 48 | 49 | def test_destroy 50 | env = Rack::MockRequest.env_for('/foo_bar/144', { 51 | :method => 'DELETE' 52 | }) 53 | output = '' 54 | assert_nothing_raised do 55 | output = @ws.call(env) 56 | end 57 | test_val = { :success => true }.to_json 58 | assert_equal test_val, output[2][0] 59 | end 60 | 61 | 62 | def test_index 63 | env = Rack::MockRequest.env_for('/foo_bar/', { 64 | :method => 'GET' 65 | }) 66 | output = '' 67 | assert_nothing_raised do 68 | output = @ws.call(env) 69 | end 70 | test_val = [1,2,3,4,5,6,7].to_json 71 | assert_equal test_val, output[2][0] 72 | end 73 | 74 | def test_replace 75 | env = Rack::MockRequest.env_for('/foo_bar', { 76 | :method => 'PUT' 77 | }) 78 | output = '' 79 | assert_nothing_raised do 80 | output = @ws.call(env) 81 | end 82 | test_val = { :success => true }.to_json 83 | assert_equal test_val, output[2][0] 84 | end 85 | 86 | def test_create 87 | env = Rack::MockRequest.env_for('/foo_bar/', { 88 | :method => 'POST' 89 | }) 90 | output = '' 91 | assert_nothing_raised do 92 | output = @ws.call(env) 93 | end 94 | test_val = { :success => true }.to_json 95 | assert_equal test_val, output[2][0] 96 | end 97 | 98 | def test_drop 99 | env = Rack::MockRequest.env_for('/foo_bar', { 100 | :method => 'DELETE' 101 | }) 102 | output = '' 103 | assert_nothing_raised do 104 | output = @ws.call(env) 105 | end 106 | test_val = { :success => true }.to_json 107 | assert_equal test_val, output[2][0] 108 | end 109 | 110 | def test_custom_entity_action 111 | env = Rack::MockRequest.env_for('/foo_bar/7476/custom_entity', { 112 | :method => 'GET' 113 | }) 114 | output = '' 115 | assert_nothing_raised do 116 | output = @ws.call(env) 117 | end 118 | test_val = '7476'.to_json 119 | assert_equal test_val, output[2][0] 120 | end 121 | 122 | def test_custom_collection_action 123 | env = Rack::MockRequest.env_for('/foo_bar/custom_collection', { 124 | :method => 'GET' 125 | }) 126 | output = '' 127 | assert_nothing_raised do 128 | output = @ws.call(env) 129 | end 130 | test_val = [1,1,2,3,5,8,13,21,34].to_json 131 | assert_equal test_val, output[2][0] 132 | end 133 | 134 | def test_missing 135 | env = Rack::MockRequest.env_for('/foo_bar/144/missing', { 136 | :method => 'GET' 137 | }) 138 | output = '' 139 | assert_nothing_raised do 140 | output = @ws.call(env) 141 | end 142 | assert_equal 405, output[0] 143 | end 144 | 145 | end 146 | -------------------------------------------------------------------------------- /test/sample_app_1/test/test_controller_inputs.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'test/unit' 3 | require 'rack/test' 4 | require File.expand_path(File.join(File.dirname(__FILE__),'..','loader')) 5 | require 'pp' 6 | 7 | class SampleApp::TestControllerInputs < Test::Unit::TestCase 8 | 9 | def setup 10 | @ws = SampleApp::WebService.new 11 | end 12 | 13 | def test_get_params 14 | env = Rack::MockRequest.env_for('/foo_bar/echo_get?test=1&hello=world', { 15 | :method => 'GET' 16 | }) 17 | output = '' 18 | assert_nothing_raised do 19 | output = @ws.call(env) 20 | end 21 | test_val = { :test => '1', :hello => 'world', 'get?' => 'true' }.to_json 22 | assert_equal test_val, output[2][0] 23 | end 24 | 25 | def test_FUBAR_params 26 | env = Rack::MockRequest.env_for('/foo_bar/echo_get?test=1&hello=world', { 27 | :method => 'DELETE' 28 | }) 29 | output = '' 30 | assert_nothing_raised do 31 | output = @ws.call(env) 32 | end 33 | test_val = { :test => '1', :hello => 'world', 'get?' => 'false' }.to_json 34 | assert_equal test_val, output[2][0] 35 | end 36 | 37 | def test_post_no_content_type 38 | test_val = "random text" # will be converted to json because of default response type 39 | env = Rack::MockRequest.env_for('/foo_bar/echo', { 40 | :method => 'POST', 41 | :input => test_val 42 | }) 43 | output = '' 44 | assert_nothing_raised do 45 | output = @ws.call(env) 46 | end 47 | assert_equal test_val.to_json, output[2][0] # will be converted to json because of default response type 48 | end 49 | 50 | def test_post_json 51 | test_val = { :echo => 'niner' }.to_json 52 | env = Rack::MockRequest.env_for('/foo_bar/echo', { 53 | :method => 'POST', 54 | :input => test_val, 55 | 'CONTENT_TYPE' => 'application/json' 56 | }) 57 | output = '' 58 | assert_nothing_raised do 59 | output = @ws.call(env) 60 | end 61 | assert_equal test_val, output[2][0] 62 | end 63 | 64 | def test_post_xml 65 | test_val = XmlSimple.xml_out({ :echo => 'niner' }, 'AttrPrefix' => true, 'XmlDeclaration' => true, 'NoIndent' => true) 66 | env = Rack::MockRequest.env_for('/foo_bar/echo.xml', { 67 | :method => 'POST', 68 | :input => test_val, 69 | 'CONTENT_TYPE' => 'application/xml' 70 | }) 71 | output = '' 72 | assert_nothing_raised do 73 | output = @ws.call(env) 74 | end 75 | assert_equal test_val, output[2][0] 76 | end 77 | 78 | def test_post_text 79 | test_val = 'OPCODE=PEBKAC' 80 | env = Rack::MockRequest.env_for('/foo_bar/echo.txt', { 81 | :method => 'POST', 82 | :input => test_val, 83 | 'CONTENT_TYPE' => 'text/plain' 84 | }) 85 | output = '' 86 | assert_nothing_raised do 87 | output = @ws.call(env) 88 | end 89 | assert_equal test_val, output[2][0] 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /test/sample_app_1/test/test_controller_modifiers.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'test/unit' 3 | require 'rack/test' 4 | require File.expand_path(File.join(File.dirname(__FILE__),'..','loader')) 5 | require 'pp' 6 | 7 | class SampleApp::TestControllerModifiers < Test::Unit::TestCase 8 | 9 | def setup 10 | @ws = SampleApp::WebService.new 11 | end 12 | 13 | def test_pass_through_to 14 | env = Rack::MockRequest.env_for('/foo_bar/144/bata/777', { 15 | :method => 'GET' 16 | }) 17 | output = '' 18 | assert_nothing_raised do 19 | output = @ws.call(env) 20 | end 21 | test_val = { :BAZ => 'ALOHA!' }.to_json 22 | assert_equal test_val, output[2][0] 23 | 24 | env = Rack::MockRequest.env_for('/foo_bar/133/bata/abc', { 25 | :method => 'GET' 26 | }) 27 | output = '' 28 | assert_nothing_raised do 29 | output = @ws.call(env) 30 | end 31 | test_val = { :OTHER => 'YUP' }.to_json 32 | assert_equal test_val, output[2][0] 33 | 34 | env = Rack::MockRequest.env_for('/foo_bar/144/bata/', { 35 | :method => 'GET' 36 | }) 37 | output = '' 38 | assert_nothing_raised do 39 | output = @ws.call(env) 40 | end 41 | test_val = [1,2,3,4,5].to_json 42 | assert_equal test_val, output[2][0] 43 | 44 | env = Rack::MockRequest.env_for('/foo_bar/144/other_bata', { 45 | :method => 'GET' 46 | }) 47 | output = '' 48 | assert_nothing_raised do 49 | output = @ws.call(env) 50 | end 51 | test_val = [1,2,3,4,5].to_json 52 | assert_equal test_val, output[2][0] 53 | end 54 | 55 | def test_has_relationship_to 56 | env = Rack::MockRequest.env_for('/foo_bar/144/baz', { 57 | :method => 'GET' 58 | }) 59 | output = '' 60 | assert_nothing_raised do 61 | output = @ws.call(env) 62 | end 63 | test_val = { :BAZ => 'ALOHA!' }.to_json 64 | assert_equal test_val, output[2][0] 65 | 66 | env = Rack::MockRequest.env_for('/foo_bar/133/baz', { 67 | :method => 'GET' 68 | }) 69 | output = '' 70 | assert_nothing_raised do 71 | output = @ws.call(env) 72 | end 73 | test_val = { :OTHER => 'YUP' }.to_json 74 | assert_equal test_val, output[2][0] 75 | 76 | env = Rack::MockRequest.env_for('/foo_bar/144/baz/', { 77 | :method => 'GET' 78 | }) 79 | output = '' 80 | assert_nothing_raised do 81 | output = @ws.call(env) 82 | end 83 | test_val = { :BAZ => 'ALOHA!' }.to_json 84 | assert_equal test_val, output[2][0] 85 | 86 | #------ 87 | 88 | env = Rack::MockRequest.env_for('/foo_bar/144/slugger', { 89 | :method => 'GET' 90 | }) 91 | output = '' 92 | assert_nothing_raised do 93 | output = @ws.call(env) 94 | end 95 | test_val = { :WOM => 'NOBAT!' }.to_json 96 | assert_equal test_val, output[2][0] 97 | 98 | env = Rack::MockRequest.env_for('/foo_bar/133/slugger', { 99 | :method => 'GET' 100 | }) 101 | output = '' 102 | assert_nothing_raised do 103 | output = @ws.call(env) 104 | end 105 | test_val = { :SUHWING => 'BATTER' }.to_json 106 | assert_equal test_val, output[2][0] 107 | 108 | env = Rack::MockRequest.env_for('/foo_bar/144/slugger/', { 109 | :method => 'GET' 110 | }) 111 | output = '' 112 | assert_nothing_raised do 113 | output = @ws.call(env) 114 | end 115 | test_val = { :WOM => 'NOBAT!' }.to_json 116 | assert_equal test_val, output[2][0] 117 | end 118 | 119 | def test_has_relationships_to 120 | env = Rack::MockRequest.env_for('/foo_bar/133/children/0', { 121 | :method => 'GET' 122 | }) 123 | output = '' 124 | assert_nothing_raised do 125 | output = @ws.call(env) 126 | end 127 | test_val = { :BAZA => 'YESSIR' }.to_json 128 | assert_equal test_val, output[2][0] 129 | 130 | env = Rack::MockRequest.env_for('/foo_bar/133/children/7', { 131 | :method => 'GET' 132 | }) 133 | output = '' 134 | assert_nothing_raised do 135 | output = @ws.call(env) 136 | end 137 | test_val = { :NOWAY => 'JOSE' }.to_json 138 | assert_equal test_val, output[2][0] 139 | 140 | env = Rack::MockRequest.env_for('/foo_bar/133/children/11', { 141 | :method => 'GET' 142 | }) 143 | output = '' 144 | assert_nothing_raised do 145 | output = @ws.call(env) 146 | end 147 | assert_equal 404, output[0] 148 | end 149 | 150 | def test_has_defined_relationships_to 151 | # goes to baza 152 | env = Rack::MockRequest.env_for('/foo_bar/133/def/1', { 153 | :method => 'GET' 154 | }) 155 | output = '' 156 | assert_nothing_raised do 157 | output = @ws.call(env) 158 | end 159 | test_val = { :BAZA => 'YESSIR' }.to_json 160 | assert_equal test_val, output[2][0] 161 | 162 | env = Rack::MockRequest.env_for('/foo_bar/133/def/8', { 163 | :method => 'GET' 164 | }) 165 | output = '' 166 | assert_nothing_raised do 167 | output = @ws.call(env) 168 | end 169 | test_val = { :NOWAY => 'JOSE' }.to_json 170 | assert_equal test_val, output[2][0] 171 | 172 | # this should 404 173 | env = Rack::MockRequest.env_for('/foo_bar/133/def/11', { 174 | :method => 'GET' 175 | }) 176 | output = '' 177 | assert_nothing_raised do 178 | output = @ws.call(env) 179 | end 180 | assert_equal 404, output[0] 181 | 182 | # this should 400 183 | env = Rack::MockRequest.env_for('/foo_bar/133/def', { 184 | :method => 'GET' 185 | }) 186 | output = '' 187 | assert_nothing_raised do 188 | output = @ws.call(env) 189 | end 190 | assert_equal 400, output[0] 191 | end 192 | 193 | def test_has_mapped_relationships_to 194 | env = Rack::MockRequest.env_for('/foo_bar/133/maps/first', { 195 | :method => 'GET' 196 | }) 197 | output = '' 198 | assert_nothing_raised do 199 | output = @ws.call(env) 200 | end 201 | test_val = '1' 202 | assert_equal test_val, output[2][0] 203 | 204 | env = Rack::MockRequest.env_for('/foo_bar/133/maps/second', { 205 | :method => 'GET' 206 | }) 207 | output = '' 208 | assert_nothing_raised do 209 | output = @ws.call(env) 210 | end 211 | test_val = '0' 212 | assert_equal test_val, output[2][0] 213 | 214 | env = Rack::MockRequest.env_for('/foo_bar/133/maps/third', { 215 | :method => 'GET' 216 | }) 217 | output = '' 218 | assert_nothing_raised do 219 | output = @ws.call(env) 220 | end 221 | test_val = '0' 222 | assert_equal test_val, output[2][0] 223 | end 224 | 225 | def test_keyed_with_type 226 | # baza controller exercises this option 227 | env = Rack::MockRequest.env_for('/foo_bar/133/children/0', { 228 | :method => 'GET' 229 | }) 230 | output = '' 231 | assert_nothing_raised do 232 | output = @ws.call(env) 233 | end 234 | test_val = { :BAZA => 'YESSIR' }.to_json 235 | assert_equal test_val, output[2][0] 236 | end 237 | 238 | end 239 | -------------------------------------------------------------------------------- /test/sample_app_1/test/test_cors_jsonp_support.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'test/unit' 3 | require 'rack/test' 4 | require File.expand_path(File.join(File.dirname(__FILE__),'..','loader')) 5 | require 'pp' 6 | 7 | class SampleApp::TestCORSHeaders < Test::Unit::TestCase 8 | 9 | def setup 10 | @ws = SampleApp::WebService.new 11 | end 12 | 13 | def teardown 14 | RESTRack::CONFIG.delete(:CORS) 15 | end 16 | 17 | def test_cors_no_origin_header 18 | RESTRack::CONFIG[:CORS] = {} 19 | RESTRack::CONFIG[:CORS]['Access-Control-Allow-Origin'] = 'http://restrack.me' 20 | RESTRack::CONFIG[:CORS]['Access-Control-Allow-Methods'] = 'POST, GET' 21 | env = Rack::MockRequest.env_for('/foo_bar/144', { 22 | :method => 'GET' 23 | }) 24 | output = @ws.call(env) 25 | expected_status = 403 26 | expected_headers = { 27 | "Content-Type" => "application/json", 28 | "Access-Control-Allow-Origin" => "http://restrack.me", 29 | "Access-Control-Allow-Methods" => "POST, GET" 30 | } 31 | assert_equal expected_status, output[0] 32 | assert_equal expected_headers, output[1] 33 | end 34 | 35 | def test_cors_on_allowed_domain 36 | RESTRack::CONFIG[:CORS] = {} 37 | RESTRack::CONFIG[:CORS]['Access-Control-Allow-Origin'] = 'http://restrack.me' 38 | RESTRack::CONFIG[:CORS]['Access-Control-Allow-Methods'] = 'POST, GET' 39 | env = Rack::MockRequest.env_for('/foo_bar/144', { 40 | :method => 'GET', 41 | 'HTTP_Origin' => 'http://restrack.me' 42 | }) 43 | output = @ws.call(env) 44 | expected_status = 200 45 | expected_headers = { 46 | "Content-Type" => "application/json", 47 | "Access-Control-Allow-Origin" => "http://restrack.me", 48 | "Access-Control-Allow-Methods" => "POST, GET" 49 | } 50 | expected_body = { :foo => 'bar', :baz => 123 }.to_json 51 | assert_equal expected_status, output[0] 52 | assert_equal expected_headers, output[1] 53 | assert_equal expected_body, output[2][0] 54 | end 55 | 56 | def test_cors_on_disallowed_domain 57 | RESTRack::CONFIG[:CORS] = {} 58 | RESTRack::CONFIG[:CORS]['Access-Control-Allow-Origin'] = 'http://restrack.me' 59 | RESTRack::CONFIG[:CORS]['Access-Control-Allow-Methods'] = 'POST, GET' 60 | env = Rack::MockRequest.env_for('/foo_bar/144', { 61 | :method => 'GET', 62 | 'HTTP_Origin' => 'http://somehacker.net' 63 | }) 64 | output = @ws.call(env) 65 | expected_status = 403 66 | expected_headers = { 67 | "Content-Type" => "application/json", 68 | "Access-Control-Allow-Origin" => "http://restrack.me", 69 | "Access-Control-Allow-Methods" => "POST, GET" 70 | } 71 | assert_equal expected_status, output[0] 72 | assert_equal expected_headers, output[1] 73 | end 74 | 75 | def test_cors_on_disallowed_method 76 | RESTRack::CONFIG[:CORS] = {} 77 | RESTRack::CONFIG[:CORS]['Access-Control-Allow-Origin'] = 'http://restrack.me' 78 | RESTRack::CONFIG[:CORS]['Access-Control-Allow-Methods'] = 'POST, GET' 79 | env = Rack::MockRequest.env_for('/foo_bar/144', { 80 | :method => 'PUT', 81 | 'HTTP_Origin' => 'http://restrack.me' 82 | }) 83 | output = @ws.call(env) 84 | expected_status = 403 85 | expected_headers = { 86 | "Content-Type" => "application/json", 87 | "Access-Control-Allow-Origin" => "http://restrack.me", 88 | "Access-Control-Allow-Methods" => "POST, GET" 89 | } 90 | assert_equal expected_status, output[0] 91 | assert_equal expected_headers, output[1] 92 | end 93 | 94 | def test_options_request 95 | RESTRack::CONFIG[:CORS] = {} 96 | RESTRack::CONFIG[:CORS]['Access-Control-Allow-Origin'] = 'http://restrack.me' 97 | RESTRack::CONFIG[:CORS]['Access-Control-Allow-Methods'] = 'POST, GET' 98 | env = Rack::MockRequest.env_for('/foo_bar/144', { 99 | :method => 'OPTIONS', 100 | 'HTTP_Origin' => 'http://restrack.me' 101 | }) 102 | output = @ws.call(env) 103 | expected_status = 200 104 | expected_headers = { 105 | "Content-Type" => "application/json", 106 | "Access-Control-Allow-Origin" => "http://restrack.me", 107 | "Access-Control-Allow-Methods" => "POST, GET" 108 | } 109 | assert_equal expected_status, output[0] 110 | assert_equal expected_headers, output[1] 111 | end 112 | 113 | end 114 | -------------------------------------------------------------------------------- /test/sample_app_1/test/test_errors.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'test/unit' 3 | require 'rack/test' 4 | require File.expand_path(File.join(File.dirname(__FILE__),'..','loader')) 5 | require 'pp' 6 | 7 | class SampleApp::TestControllerActions < Test::Unit::TestCase 8 | 9 | def setup 10 | @ws = SampleApp::WebService.new 11 | end 12 | 13 | #module HTTPStatus 14 | # class HTTP400BadRequest < Exception; end 15 | # class HTTP401Unauthorized < Exception; end 16 | # class HTTP403Forbidden < Exception; end 17 | # class HTTP404ResourceNotFound < Exception; end 18 | # class HTTP405MethodNotAllowed < Exception; end 19 | # class HTTP409Conflict < Exception; end 20 | # class HTTP410Gone < Exception; end 21 | # class HTTP500ServerError < Exception; end 22 | #end 23 | 24 | def test_bad_request 25 | response_code = 400 26 | env = Rack::MockRequest.env_for('/errors/bad_request', { 27 | :method => 'GET' 28 | }) 29 | output = '' 30 | #assert_nothing_raised do 31 | output = @ws.call(env) 32 | #end 33 | assert_equal response_code, output[0] 34 | tester = {"errors" => [{"error"=>"tester"}]} 35 | assert_equal tester, JSON.parse(output[2][0]) 36 | end 37 | 38 | def test_unauthorized 39 | response_code = 401 40 | env = Rack::MockRequest.env_for('/errors/unauthorized', { 41 | :method => 'GET' 42 | }) 43 | output = '' 44 | assert_nothing_raised do 45 | output = @ws.call(env) 46 | end 47 | assert_equal response_code, output[0] 48 | tester = {"errors" => [{"error"=>"tester"}]} 49 | assert_equal tester, JSON.parse(output[2][0]) 50 | end 51 | 52 | def test_forbidden 53 | response_code = 403 54 | env = Rack::MockRequest.env_for('/errors/forbidden', { 55 | :method => 'GET' 56 | }) 57 | output = '' 58 | assert_nothing_raised do 59 | output = @ws.call(env) 60 | end 61 | assert_equal response_code, output[0] 62 | tester = {"errors" => [{"error"=>"tester"}]} 63 | assert_equal tester, JSON.parse(output[2][0]) 64 | end 65 | 66 | def test_resource_not_found 67 | response_code = 404 68 | env = Rack::MockRequest.env_for('/errors/resource_not_found', { 69 | :method => 'GET' 70 | }) 71 | output = '' 72 | assert_nothing_raised do 73 | output = @ws.call(env) 74 | end 75 | assert_equal response_code, output[0] 76 | tester = {"errors" => [{"error"=>"tester"}]} 77 | assert_equal tester, JSON.parse(output[2][0]) 78 | end 79 | 80 | def test_method_not_allowed 81 | response_code = 405 82 | env = Rack::MockRequest.env_for('/errors/method_not_allowed', { 83 | :method => 'GET' 84 | }) 85 | output = '' 86 | assert_nothing_raised do 87 | output = @ws.call(env) 88 | end 89 | assert_equal response_code, output[0] 90 | tester = {"errors" => [{"error"=>"tester"}]} 91 | assert_equal tester, JSON.parse(output[2][0]) 92 | end 93 | 94 | def test_conflict 95 | response_code = 409 96 | env = Rack::MockRequest.env_for('/errors/conflict', { 97 | :method => 'GET' 98 | }) 99 | output = '' 100 | assert_nothing_raised do 101 | output = @ws.call(env) 102 | end 103 | assert_equal response_code, output[0] 104 | tester = {"errors" => [{"error"=>"tester"}]} 105 | assert_equal tester, JSON.parse(output[2][0]) 106 | end 107 | 108 | def test_gone 109 | response_code = 410 110 | env = Rack::MockRequest.env_for('/errors/gone', { 111 | :method => 'GET' 112 | }) 113 | output = '' 114 | assert_nothing_raised do 115 | output = @ws.call(env) 116 | end 117 | assert_equal response_code, output[0] 118 | tester = {"errors" => [{"error"=>"tester"}]} 119 | assert_equal tester, JSON.parse(output[2][0]) 120 | end 121 | 122 | def test_resource_invalid 123 | response_code = 422 124 | env = Rack::MockRequest.env_for('/errors/resource_invalid', { 125 | :method => 'GET' 126 | }) 127 | output = '' 128 | assert_nothing_raised do 129 | output = @ws.call(env) 130 | end 131 | assert_equal response_code, output[0] 132 | tester = {"errors" => [{"error"=>"This is a WebDAV HTTP extension code used by ActiveResource to communicate validation errors."}]} 133 | assert_equal tester, JSON.parse(output[2][0]) 134 | end 135 | 136 | def test_resource_invalid_active_record_format 137 | response_code = 422 138 | env = Rack::MockRequest.env_for('/errors/resource_invalid_active_resource_format.xml', { 139 | :method => 'GET' 140 | }) 141 | output = '' 142 | #assert_nothing_raised do 143 | output = @ws.call(env) 144 | #end 145 | assert_equal response_code, output[0] 146 | assert_equal 'This is how ActiveResource expects errors to come through.', output[2][0] 147 | 148 | response_code = 422 149 | env = Rack::MockRequest.env_for('/errors/resource_invalid_active_resource_format', { 150 | :method => 'GET' 151 | }) 152 | output = '' 153 | #assert_nothing_raised do 154 | output = @ws.call(env) 155 | #end 156 | assert_equal response_code, output[0] 157 | tester = {"errors" => [{"error"=>"This is how ActiveResource expects errors to come through."}]} 158 | assert_equal tester, JSON.parse(output[2][0]) 159 | end 160 | 161 | def test_server_error 162 | response_code = 500 163 | # This will/should spam the log 164 | env = Rack::MockRequest.env_for('/errors/server_error', { 165 | :method => 'GET' 166 | }) 167 | output = '' 168 | #assert_nothing_raised do 169 | output = @ws.call(env) 170 | #end 171 | assert_equal response_code, output[0] 172 | end 173 | 174 | def test_server_error_with_backtrace 175 | response_code = 500 176 | # This will/should spam the log 177 | env = Rack::MockRequest.env_for('/errors/server_error_with_backtrace', { 178 | :method => 'GET' 179 | }) 180 | output = '' 181 | assert_nothing_raised do 182 | output = @ws.call(env) 183 | end 184 | assert_equal response_code, output[0] 185 | assert_not_equal 'tester', output[2][0] 186 | end 187 | 188 | def test_server_error_without_backtrace 189 | RESTRack::CONFIG[:SHOW_STACK] = false 190 | response_code = 500 191 | # This will/should spam the log 192 | env = Rack::MockRequest.env_for('/errors/server_error', { 193 | :method => 'GET' 194 | }) 195 | output = '' 196 | assert_nothing_raised do 197 | output = @ws.call(env) 198 | end 199 | assert_equal response_code, output[0] 200 | tester = {"errors"=>[{"error"=>"tester"}]} 201 | assert_equal tester, JSON.parse(output[2][0]) 202 | RESTRack::CONFIG[:SHOW_STACK] = true 203 | end 204 | 205 | end -------------------------------------------------------------------------------- /test/sample_app_1/test/test_formats.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'test/unit' 3 | require 'rack/test' 4 | require File.expand_path(File.join(File.dirname(__FILE__),'..','loader')) 5 | require 'pp' 6 | 7 | class SampleApp::TestFormats < Test::Unit::TestCase 8 | 9 | def setup 10 | @ws = SampleApp::WebService.new 11 | end 12 | 13 | def test_show_builder_xml 14 | env = Rack::MockRequest.env_for('/foo_bar/144.xml', { 15 | :method => 'GET' 16 | }) 17 | output = '' 18 | assert_nothing_raised do 19 | output = @ws.call(env) 20 | end 21 | test_val = "\nbar123" 22 | assert_equal test_val, output[2][0] 23 | end 24 | 25 | def test_show_default_xml 26 | env = Rack::MockRequest.env_for('/foo_bar/index.xml', { 27 | :method => 'GET' 28 | }) 29 | output = '' 30 | assert_nothing_raised do 31 | output = @ws.call(env) 32 | end 33 | test_val = XmlSimple.xml_out([1,2,3,4,5,6,7], 'AttrPrefix' => true, 'XmlDeclaration' => true, 'NoIndent' => true) 34 | assert_equal test_val, output[2][0] 35 | 36 | env = Rack::MockRequest.env_for('/foo_bar.xml', { 37 | :method => 'GET' 38 | }) 39 | output = '' 40 | assert_nothing_raised do 41 | output = @ws.call(env) 42 | end 43 | test_val = XmlSimple.xml_out([1,2,3,4,5,6,7], 'AttrPrefix' => true, 'XmlDeclaration' => true, 'NoIndent' => true) 44 | assert_equal test_val, output[2][0] 45 | end 46 | 47 | def test_show_json 48 | env = Rack::MockRequest.env_for('/foo_bar/144', { 49 | :method => 'GET' 50 | }) 51 | output = '' 52 | assert_nothing_raised do 53 | output = @ws.call(env) 54 | end 55 | test_val = { :foo => 'bar', :baz => 123 }.to_json 56 | assert_equal test_val, output[2][0] 57 | end 58 | 59 | def test_complex_data_structure_json 60 | env = Rack::MockRequest.env_for('/foo_bar/1234567890', { 61 | :method => 'GET' 62 | }) 63 | output = '' 64 | assert_nothing_raised do 65 | output = @ws.call(env) 66 | end 67 | test_val = "{\"foo\":\"abc\",\"bar\":\"123\",\"baz\":456,\"more\":{\"one\":1,\"two\":[1,2],\"three\":\"deep_fu\"}}" 68 | assert_equal test_val, output[2][0] 69 | 70 | env = Rack::MockRequest.env_for('/foo_bar/42', { 71 | :method => 'GET' 72 | }) 73 | output = '' 74 | assert_nothing_raised do 75 | output = @ws.call(env) 76 | end 77 | test_val = { 78 | :foo => 'abc', 79 | :bar => 123, 80 | :baz => { 81 | 'one' => [1], 82 | 'two' => ['1','2'], 83 | 'three' => ['1', 2, {:three => 3}], 84 | 4 => :four 85 | } 86 | }.to_json 87 | assert_equal test_val, output[2][0] 88 | end 89 | 90 | def test_complex_data_structure_xml 91 | env = Rack::MockRequest.env_for('/foo_bar/1234567890/complex_show_xml_no_builder.xml', { 92 | :method => 'GET' 93 | }) 94 | output = '' 95 | assert_nothing_raised do 96 | output = @ws.call(env) 97 | end 98 | test_val = "\nabc123456112deep_fu" 99 | assert_equal test_val, output[2][0] 100 | 101 | env = Rack::MockRequest.env_for('/foo_bar/42/complex_show_xml_no_builder.xml', { 102 | :method => 'GET' 103 | }) 104 | output = '' 105 | assert_nothing_raised do 106 | output = @ws.call(env) 107 | end 108 | test_val = XmlSimple.xml_out({ 109 | :foo => 'abc', 110 | :bar => 123, 111 | :baz => { 112 | 'one' => [1], 113 | 'two' => ['1','2'], 114 | 'three' => ['1', 2, {:three => 3}], 115 | 4 => :four 116 | } 117 | }, 'AttrPrefix' => true, 'XmlDeclaration' => true, 'NoIndent' => true) 118 | assert_equal test_val, output[2][0] 119 | end 120 | 121 | end 122 | -------------------------------------------------------------------------------- /test/sample_app_1/test/test_resource_request.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | require 'rubygems' 4 | require 'test/unit' 5 | require 'rack/test' 6 | require File.expand_path(File.join(File.dirname(__FILE__),'..','loader')) 7 | 8 | class SampleApp::TestResourceRequest < Test::Unit::TestCase 9 | 10 | def setup 11 | @ws = SampleApp::WebService.new # init logs 12 | end 13 | 14 | def test_initialize 15 | env = Rack::MockRequest.env_for('/foo_bar', { 16 | :method => 'POST', 17 | :params => %Q|[ 18 | { 19 | "bar": "baz" 20 | } 21 | ]| 22 | }) 23 | request = Rack::Request.new(env) 24 | assert_nothing_raised do 25 | resource_request = RESTRack::ResourceRequest.new(:request => request) 26 | end 27 | end 28 | 29 | def test_root_resource_accept 30 | # These are the resources which can be accessed from the root of your web service. If left empty, all resources are available at the root. 31 | #:ROOT_RESOURCE_ACCEPT: [ 'foo_bar' ] 32 | 33 | RESTRack::CONFIG[:ROOT_RESOURCE_ACCEPT] = [ 'foo_bar' ] 34 | # This should not be allowed because it is not in ROOT_RESOURCE_ACCEPT 35 | env = Rack::MockRequest.env_for('/bat/144', { 36 | :method => 'GET' 37 | }) 38 | output = '' 39 | assert_nothing_raised do 40 | output = @ws.call(env) 41 | end 42 | #test_val = [403, {"Content-Type"=>"text/plain"}, "HTTPStatus::HTTP403Forbidden\nYou are forbidden to access that resource."] 43 | assert_equal 403, output[0] 44 | 45 | # This should be allowed 46 | env = Rack::MockRequest.env_for('/foo_bar/144', { 47 | :method => 'GET' 48 | }) 49 | output = '' 50 | assert_nothing_raised do 51 | output = @ws.call(env) 52 | end 53 | assert_equal 200, output[0] 54 | 55 | RESTRack::CONFIG[:ROOT_RESOURCE_ACCEPT] = [] 56 | env = Rack::MockRequest.env_for('/foo_bar/144', { 57 | :method => 'GET' 58 | }) 59 | output = '' 60 | assert_nothing_raised do 61 | output = @ws.call(env) 62 | end 63 | assert_equal 200, output[0] 64 | 65 | RESTRack::CONFIG[:ROOT_RESOURCE_ACCEPT] = nil 66 | env = Rack::MockRequest.env_for('/foo_bar/144', { 67 | :method => 'GET' 68 | }) 69 | output = '' 70 | assert_nothing_raised do 71 | output = @ws.call(env) 72 | end 73 | assert_equal 200, output[0] 74 | 75 | RESTRack::CONFIG[:ROOT_RESOURCE_ACCEPT] = [ 'foo_bar' ] 76 | end 77 | 78 | 79 | def test_root_resource_denied 80 | ## These are the resources which cannot be accessed from the root of your web service. Use either this or ROOT_RESOURCE_ACCEPT as a blacklist or whitelist to establish routing (relationships defined in resource controllers define further routing). 81 | #:ROOT_RESOURCE_DENY: [ 'baz' ] 82 | env = Rack::MockRequest.env_for('/baz/144', { 83 | :method => 'GET' 84 | }) 85 | output = '' 86 | assert_nothing_raised do 87 | output = @ws.call(env) 88 | end 89 | #test_val = [403, {"Content-Type"=>"text/plain"}, "HTTPStatus::HTTP403Forbidden\nYou are forbidden to access that resource."] 90 | assert_equal 403, output[0] 91 | 92 | env = Rack::MockRequest.env_for('/bat/144', { 93 | :method => 'GET' 94 | }) 95 | assert_nothing_raised do 96 | output = @ws.call(env) 97 | end 98 | assert_not_equal 200, output[0] 99 | 100 | RESTRack::CONFIG[:ROOT_RESOURCE_DENY] = [] 101 | env = Rack::MockRequest.env_for('/foo_bar/144', { 102 | :method => 'GET' 103 | }) 104 | output = '' 105 | assert_nothing_raised do 106 | output = @ws.call(env) 107 | end 108 | assert_equal 200, output[0] 109 | 110 | RESTRack::CONFIG[:ROOT_RESOURCE_DENY] = nil 111 | env = Rack::MockRequest.env_for('/foo_bar/144', { 112 | :method => 'GET' 113 | }) 114 | output = '' 115 | assert_nothing_raised do 116 | output = @ws.call(env) 117 | end 118 | assert_equal 200, output[0] 119 | 120 | RESTRack::CONFIG[:ROOT_RESOURCE_DENY] = [''] 121 | # it should handle this, although it is incorrect 122 | env = Rack::MockRequest.env_for('/foo_bar/144', { 123 | :method => 'GET' 124 | }) 125 | output = '' 126 | assert_nothing_raised do 127 | output = @ws.call(env) 128 | end 129 | assert_equal 200, output[0] 130 | 131 | RESTRack::CONFIG[:ROOT_RESOURCE_DENY] = [ 'baz' ] 132 | end 133 | 134 | def test_default_resource 135 | # This should be handled by bazu_controller 136 | env = Rack::MockRequest.env_for('/', { 137 | :method => 'GET' 138 | }) 139 | output = '' 140 | assert_nothing_raised do 141 | output = @ws.call(env) 142 | end 143 | assert_equal 403, output[0] 144 | end 145 | 146 | def test_transcode_defined 147 | RESTRack::CONFIG.delete(:TRANSCODE) 148 | RESTRack::CONFIG.delete(:FORCE_ENCODING) 149 | RESTRack::CONFIG[:TRANSCODE] = 'ISO-8859-1' 150 | 151 | env = Rack::MockRequest.env_for('/foo_bar/show_encoding', { 152 | :method => 'POST', 153 | :params => %Q|[ 154 | { 155 | "bar": "baz" 156 | } 157 | ]| 158 | }) 159 | output = '' 160 | assert_nothing_raised do 161 | output = @ws.call(env) 162 | end 163 | assert_equal ["\"ISO-8859-1\""], output[2] 164 | 165 | env = Rack::MockRequest.env_for('/foo_bar/show_encoding', { 166 | :method => 'POST', 167 | :params => %Q|[ 168 | { 169 | "bar": "€" 170 | } 171 | ]| 172 | }) 173 | output = '' 174 | #assert_nothing_raised do 175 | output = @ws.call(env) 176 | #end 177 | assert_equal 500, output[0] 178 | end 179 | 180 | def test_transcode_not_defined 181 | RESTRack::CONFIG.delete(:TRANSCODE) 182 | RESTRack::CONFIG.delete(:FORCE_ENCODING) 183 | 184 | env = Rack::MockRequest.env_for('/foo_bar/show_encoding', { 185 | :method => 'POST', 186 | :params => %Q|[ 187 | { 188 | "bar": "baz" 189 | } 190 | ]| 191 | }) 192 | output = '' 193 | assert_nothing_raised do 194 | output = @ws.call(env) 195 | end 196 | assert_equal ["\"ASCII-8BIT\""], output[2] 197 | 198 | env = Rack::MockRequest.env_for('/foo_bar/show_encoding', { 199 | :method => 'POST', 200 | :params => %Q|[ 201 | { 202 | "bar": "€" 203 | } 204 | ]| 205 | }) 206 | output = '' 207 | assert_nothing_raised do 208 | output = @ws.call(env) 209 | end 210 | assert_equal 200, output[0] 211 | end 212 | 213 | def test_force_encoding_defined 214 | RESTRack::CONFIG.delete(:TRANSCODE) 215 | RESTRack::CONFIG.delete(:FORCE_ENCODING) 216 | RESTRack::CONFIG[:FORCE_ENCODING] = 'ISO-8859-1' 217 | 218 | env = Rack::MockRequest.env_for('/foo_bar/show_encoding', { 219 | :method => 'POST', 220 | :params => %Q|[ 221 | { 222 | "bar": "baz" 223 | } 224 | ]| 225 | }) 226 | output = '' 227 | assert_nothing_raised do 228 | output = @ws.call(env) 229 | end 230 | assert_equal ["\"ISO-8859-1\""], output[2] 231 | 232 | env = Rack::MockRequest.env_for('/foo_bar/show_encoding', { 233 | :method => 'POST', 234 | :params => %Q|[ 235 | { 236 | "bar": "€" 237 | } 238 | ]| 239 | }) 240 | output = '' 241 | assert_nothing_raised do 242 | output = @ws.call(env) 243 | end 244 | assert_equal 200, output[0] 245 | end 246 | 247 | def test_force_encoding_not_defined 248 | RESTRack::CONFIG.delete(:TRANSCODE) 249 | RESTRack::CONFIG.delete(:FORCE_ENCODING) 250 | 251 | env = Rack::MockRequest.env_for('/foo_bar/show_encoding', { 252 | :method => 'POST', 253 | :params => %Q|[ 254 | { 255 | "bar": "baz" 256 | } 257 | ]| 258 | }) 259 | output = '' 260 | assert_nothing_raised do 261 | output = @ws.call(env) 262 | end 263 | assert_equal ["\"ASCII-8BIT\""], output[2] 264 | 265 | env = Rack::MockRequest.env_for('/foo_bar/show_encoding', { 266 | :method => 'POST', 267 | :params => %Q|[ 268 | { 269 | "bar": "€" 270 | } 271 | ]| 272 | }) 273 | output = '' 274 | assert_nothing_raised do 275 | output = @ws.call(env) 276 | end 277 | assert_equal 200, output[0] 278 | end 279 | 280 | end 281 | -------------------------------------------------------------------------------- /test/sample_app_1/test/test_web_service.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'test/unit' 3 | require 'rack/test' 4 | require File.expand_path(File.join(File.dirname(__FILE__),'..','loader')) 5 | 6 | class SampleApp::TestWebService < Test::Unit::TestCase 7 | 8 | def setup 9 | @ws = SampleApp::WebService.new # init logs 10 | end 11 | 12 | def test_service_name 13 | env = Rack::MockRequest.env_for('/foo_bar', { 14 | :method => 'POST', 15 | :params => %Q|[ 16 | { 17 | "bar": "baz" 18 | } 19 | ]| 20 | }) 21 | output = '' 22 | output = @ws.call(env) 23 | assert_nothing_raised do 24 | RESTRack::CONFIG[:SERVICE_NAME].to_sym.to_s 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/sample_app_1/views/foo_bar/show.xml.builder: -------------------------------------------------------------------------------- 1 | xml.data do 2 | xml.foo data[:foo] 3 | xml.baz data[:baz] 4 | end 5 | -------------------------------------------------------------------------------- /test/sample_app_2/config/constants.yaml: -------------------------------------------------------------------------------- 1 | #GENERATOR-CONST# -DO NOT REMOVE OR CHANGE THIS LINE- Application-Namespace => SampleApp 2 | # 3 | # = constants.yaml 4 | # This is where RESTRack applications define the constants relevant to their particular 5 | # application that are used by the RESTRack base classes. 6 | 7 | # Application log path definition 8 | :LOG: '/var/log/restrack/sample_app.log' 9 | # Request log path definition 10 | :REQUEST_LOG: '/var/log/restrack/sample_app.request.log' 11 | 12 | # Logger object levels 13 | :LOG_LEVEL: :WARN # Logger object level 14 | :REQUEST_LOG_LEVEL: :WARN # Logger object level 15 | 16 | # Supported formats are :JSON, :XML, :YAML, :BIN, :TEXT 17 | :DEFAULT_FORMAT: :JSON 18 | # The resource which will handle root level requests where the name is not specified. Best for users of this not to implement method_missing in their default controller, unless they are checking for bad URI. 19 | :DEFAULT_RESOURCE: 'bazu' 20 | 21 | # These are the resources which can be accessed from the root of your web service. If left empty, all resources are available at the root. 22 | #:ROOT_RESOURCE_ACCEPT: [] 23 | # These are the resources which cannot be accessed from the root of your web service. Use either this or ROOT_RESOURCE_ACCEPT as a blacklist or whitelist to establish routing (relationships defined in resource controllers define further routing). 24 | :ROOT_RESOURCE_DENY: [ 'baz' ] 25 | -------------------------------------------------------------------------------- /test/sample_app_2/controllers/bat_controller.rb: -------------------------------------------------------------------------------- 1 | class SampleApp::BatController < RESTRack::ResourceController 2 | 3 | def show(id) 4 | return { :WOM => 'BAT!' } if id == 777 5 | return { :WOM => 'NOBAT!' } if id == '777' 6 | return { :SUHWING => 'BATTER' } 7 | end 8 | 9 | end -------------------------------------------------------------------------------- /test/sample_app_2/controllers/baz_controller.rb: -------------------------------------------------------------------------------- 1 | class SampleApp::BazController < RESTRack::ResourceController 2 | 3 | def show(id) 4 | return { :BAZ => 'HOLA!' } if id == 777 5 | return { :BAZ => 'ALOHA!' } if id == '777' 6 | return { :OTHER => 'YUP' } 7 | end 8 | 9 | end -------------------------------------------------------------------------------- /test/sample_app_2/controllers/baza_controller.rb: -------------------------------------------------------------------------------- 1 | class SampleApp::BazaController < RESTRack::ResourceController 2 | 3 | keyed_with_type Fixnum 4 | 5 | def show(id) 6 | return { :BAZA => 'YESSIR' } if id == 1 7 | return { :NOWAY => 'JOSE' } if id == 8 8 | return { :NOTTODAY => 'JOSEPH' } 9 | end 10 | 11 | end -------------------------------------------------------------------------------- /test/sample_app_2/controllers/bazu_controller.rb: -------------------------------------------------------------------------------- 1 | class SampleApp::BazuController < RESTRack::ResourceController 2 | 3 | keyed_with_type Fixnum 4 | 5 | def index 6 | [ 7 | { :id => 1, :val => 111 }, 8 | { :id => 2, :val => 222 }, 9 | { :id => 3, :val => 333 } 10 | ] 11 | end 12 | 13 | def show(id) 14 | return 1 if id == 1 15 | return 0 16 | end 17 | 18 | end -------------------------------------------------------------------------------- /test/sample_app_2/controllers/foo_bar_controller.rb: -------------------------------------------------------------------------------- 1 | class SampleApp::FooBarController < RESTRack::ResourceController 2 | 3 | has_relationship_to( :baz ) do |id| 4 | if id =='144' 5 | output = '777' 6 | else 7 | output = '666' 8 | end 9 | output # You can't "return" from a Proc! It will do a "return" in the outer method. Remember a "Proc" is not a Method. 10 | end 11 | 12 | has_relationship_to( :bat, :as => :slugger ) do |id| 13 | if id =='144' 14 | output = '777' 15 | else 16 | output = '666' 17 | end 18 | output # You can't "return" from a Proc! It will do a "return" in the outer method. Remember a "Proc" is not a Method. 19 | end 20 | 21 | has_relationships_to( :baza, :as => :children ) do |id| 22 | [0,1,2,3,4,5,6,7,8,9] 23 | end 24 | 25 | has_mapped_relationships_to( :bazu, :as => :maps ) do |id| 26 | { 27 | :first => 1, 28 | :second => 2, 29 | :third => 3 30 | } 31 | end 32 | 33 | def index 34 | [1,2,3,4,5,6,7] 35 | end 36 | def create 37 | { :success => true } 38 | end 39 | def replace 40 | { :success => true } 41 | end 42 | def drop 43 | { :success => true } 44 | end 45 | 46 | def show(id) 47 | { :foo => 'bar', :baz => 123 } 48 | end 49 | def update(id) 50 | { :success => true } 51 | end 52 | def destroy(id) 53 | { :success => true } 54 | end 55 | def add(id) 56 | { :success => true } 57 | end 58 | 59 | end 60 | -------------------------------------------------------------------------------- /test/sample_app_2/loader.rb: -------------------------------------------------------------------------------- 1 | # for development only 2 | $:.unshift File.expand_path(File.join(File.dirname(__FILE__),'../../lib')) 3 | ##### 4 | require 'restrack' 5 | 6 | module SampleApp; end 7 | class SampleApp::WebService < RESTRack::WebService; end 8 | 9 | RESTRack::CONFIG = RESTRack::load_config(File.join(File.dirname(__FILE__), 'config/constants.yaml')) 10 | RESTRack::CONFIG[:ROOT] = File.dirname(__FILE__) 11 | 12 | # Dynamically load all controllers 13 | Find.find( File.join(File.dirname(__FILE__), 'controllers') ) do |file| 14 | next if File.extname(file) != '.rb' 15 | require file 16 | end 17 | 18 | if File.directory?( File.join(File.dirname(__FILE__), 'models') ) 19 | # Dynamically load all models 20 | Find.find( File.join(File.dirname(__FILE__), 'models') ) do |file| 21 | next if File.extname(file) != '.rb' 22 | require file 23 | end 24 | end 25 | 26 | puts "sample_app_2 RESTRack::CONFIG:\n" 27 | config = RESTRack::CONFIG.keys.map {|c| c.to_s }.sort 28 | config.each do |key| 29 | puts "\t" + key + ' => ' + RESTRack::CONFIG[key.to_sym].to_s 30 | end 31 | puts "\n" 32 | -------------------------------------------------------------------------------- /test/sample_app_2/test/test_controller_modifiers.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'test/unit' 3 | require 'rack/test' 4 | require File.expand_path(File.join(File.dirname(__FILE__),'..','loader')) 5 | require 'pp' 6 | 7 | class SampleApp::TestControllerModifiers < Test::Unit::TestCase 8 | 9 | def setup 10 | @ws = SampleApp::WebService.new 11 | end 12 | 13 | def test_has_relationship_to 14 | env = Rack::MockRequest.env_for('/foo_bar/144/baz', { 15 | :method => 'GET' 16 | }) 17 | output = '' 18 | assert_nothing_raised do 19 | output = @ws.call(env) 20 | end 21 | test_val = { :BAZ => 'ALOHA!' }.to_json 22 | assert_equal test_val, output[2][0] 23 | 24 | env = Rack::MockRequest.env_for('/foo_bar/133/baz', { 25 | :method => 'GET' 26 | }) 27 | output = '' 28 | assert_nothing_raised do 29 | output = @ws.call(env) 30 | end 31 | test_val = { :OTHER => 'YUP' }.to_json 32 | assert_equal test_val, output[2][0] 33 | 34 | env = Rack::MockRequest.env_for('/foo_bar/144/baz/', { 35 | :method => 'GET' 36 | }) 37 | output = '' 38 | assert_nothing_raised do 39 | output = @ws.call(env) 40 | end 41 | test_val = { :BAZ => 'ALOHA!' }.to_json 42 | assert_equal test_val, output[2][0] 43 | end 44 | 45 | def test_has_relationships_to 46 | env = Rack::MockRequest.env_for('/foo_bar/133/children/1', { 47 | :method => 'GET' 48 | }) 49 | output = '' 50 | assert_nothing_raised do 51 | output = @ws.call(env) 52 | end 53 | test_val = { :BAZA => 'YESSIR' }.to_json 54 | assert_equal test_val, output[2][0] 55 | 56 | env = Rack::MockRequest.env_for('/foo_bar/133/children/8', { 57 | :method => 'GET' 58 | }) 59 | output = '' 60 | assert_nothing_raised do 61 | output = @ws.call(env) 62 | end 63 | test_val = { :NOWAY => 'JOSE' }.to_json 64 | assert_equal test_val, output[2][0] 65 | 66 | env = Rack::MockRequest.env_for('/foo_bar/133/children/11', { 67 | :method => 'GET' 68 | }) 69 | output = '' 70 | assert_nothing_raised do 71 | output = @ws.call(env) 72 | end 73 | assert_equal 404, output[0] 74 | end 75 | 76 | def test_has_mapped_relationships_to 77 | env = Rack::MockRequest.env_for('/foo_bar/133/maps/first', { 78 | :method => 'GET' 79 | }) 80 | output = '' 81 | assert_nothing_raised do 82 | output = @ws.call(env) 83 | end 84 | test_val = 1.to_json 85 | assert_equal test_val, output[2][0] 86 | 87 | env = Rack::MockRequest.env_for('/foo_bar/133/maps/second', { 88 | :method => 'GET' 89 | }) 90 | output = '' 91 | assert_nothing_raised do 92 | output = @ws.call(env) 93 | end 94 | test_val = 0.to_json 95 | assert_equal test_val, output[2][0] 96 | 97 | env = Rack::MockRequest.env_for('/foo_bar/133/maps/third', { 98 | :method => 'GET' 99 | }) 100 | output = '' 101 | assert_nothing_raised do 102 | output = @ws.call(env) 103 | end 104 | test_val = 0.to_json 105 | assert_equal test_val, output[2][0] 106 | end 107 | 108 | def test_keyed_with_type 109 | # baza controller exercises this option 110 | env = Rack::MockRequest.env_for('/bazu/1', { 111 | :method => 'GET' 112 | }) 113 | output = '' 114 | assert_nothing_raised do 115 | output = @ws.call(env) 116 | end 117 | test_val = 1.to_json 118 | assert_equal test_val, output[2][0] 119 | end 120 | 121 | end 122 | -------------------------------------------------------------------------------- /test/sample_app_2/test/test_resource_request.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'test/unit' 3 | require 'rack/test' 4 | require File.expand_path(File.join(File.dirname(__FILE__),'..','loader')) 5 | 6 | class SampleApp::TestResourceRequest < Test::Unit::TestCase 7 | 8 | def setup 9 | @ws = SampleApp::WebService.new # init logs 10 | end 11 | 12 | ## These are the resources which can be accessed from the root of your web service. If left empty, all resources are available at the root. 13 | ##:ROOT_RESOURCE_ACCEPT: [] 14 | ## These are the resources which cannot be accessed from the root of your web service. Use either this or ROOT_RESOURCE_ACCEPT as a blacklist or whitelist to establish routing (relationships defined in resource controllers define further routing). 15 | #:ROOT_RESOURCE_DENY: [ 'baz' ] 16 | def test_root_resource_denied 17 | env = Rack::MockRequest.env_for('/baz/144', { 18 | :method => 'GET' 19 | }) 20 | output = '' 21 | assert_nothing_raised do 22 | output = @ws.call(env) 23 | end 24 | #test_val = [403, {"Content-Type"=>"text/plain"}, "HTTPStatus::HTTP403Forbidden\nYou are forbidden to access that resource."] 25 | assert_equal 403, output[0] 26 | 27 | env = Rack::MockRequest.env_for('/bat/144', { 28 | :method => 'GET' 29 | }) 30 | assert_nothing_raised do 31 | output = @ws.call(env) 32 | end 33 | #test_val = [403, {"Content-Type"=>"text/plain"}, "HTTPStatus::HTTP403Forbidden\nYou are forbidden to access that resource."] 34 | assert_not_equal 403, output[0] 35 | end 36 | 37 | def test_root_resource_accept 38 | env = Rack::MockRequest.env_for('/foo_bar/144', { 39 | :method => 'GET' 40 | }) 41 | output = '' 42 | assert_nothing_raised do 43 | output = @ws.call(env) 44 | end 45 | #test_val = [403, {"Content-Type"=>"text/plain"}, "HTTPStatus::HTTP403Forbidden\nYou are forbidden to access that resource."] 46 | assert_not_equal 403, output[0] 47 | end 48 | 49 | def test_default_resource 50 | # This should be handled by bazu_controller 51 | env = Rack::MockRequest.env_for('/bad_request', { 52 | :method => 'GET' 53 | }) 54 | output = '' 55 | assert_nothing_raised do 56 | output = @ws.call(env) 57 | end 58 | # no bad_request method defined/allowed 59 | assert_equal 405, output[0] 60 | 61 | # This should be handled by bazu_controller 62 | env = Rack::MockRequest.env_for('/123', { 63 | :method => 'GET' 64 | }) 65 | output = '' 66 | assert_nothing_raised do 67 | output = @ws.call(env) 68 | end 69 | assert_equal 200, output[0] 70 | 71 | # the following request should hit the default controller's index method (BazuController) 72 | env = Rack::MockRequest.env_for('', { 73 | :method => 'GET' 74 | }) 75 | output = '' 76 | assert_nothing_raised do 77 | output = @ws.call(env) 78 | end 79 | test_val = [ 80 | { :id => 1, :val => 111 }, 81 | { :id => 2, :val => 222 }, 82 | { :id => 3, :val => 333 } 83 | ].to_json 84 | assert_equal test_val, output[2][0] 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /test/sample_app_2/views/foo_bar/show.xml.builder: -------------------------------------------------------------------------------- 1 | xml.data do 2 | xml.foo data[:foo] 3 | xml.baz data[:baz] 4 | end 5 | -------------------------------------------------------------------------------- /test/sample_app_3/config/constants.yaml: -------------------------------------------------------------------------------- 1 | #GENERATOR-CONST# -DO NOT REMOVE OR CHANGE THIS LINE- Application-Namespace => SampleApp 2 | # 3 | # = constants.yaml 4 | # This is where RESTRack applications define the constants relevant to their particular 5 | # application that are used by the RESTRack base classes. 6 | 7 | # Application log path definition 8 | :LOG: '/var/log/restrack/sample_app.log' 9 | # Request log path definition 10 | :REQUEST_LOG: '/var/log/restrack/sample_app.request.log' 11 | 12 | # Logger object levels 13 | :LOG_LEVEL: :DEBUG # Logger object level 14 | :REQUEST_LOG_LEVEL: :DEBUG # Logger object level 15 | 16 | # Supported formats are :JSON, :XML, :YAML, :BIN, :TEXT 17 | :DEFAULT_FORMAT: :JSON 18 | # The resource which will handle root level requests where the name is not specified. Best for users of this not to implement method_missing in their default controller, unless they are checking for bad URI. 19 | :DEFAULT_RESOURCE: bazu 20 | 21 | # These are the resources which can be accessed from the root of your web service. If left empty, all resources are available at the root. 22 | :ROOT_RESOURCE_ACCEPT: [ baz ] 23 | # These are the resources which cannot be accessed from the root of your web service. Use either this or ROOT_RESOURCE_ACCEPT as a blacklist or whitelist to establish routing (relationships defined in resource controllers define further routing). 24 | #:ROOT_RESOURCE_DENY: [] 25 | -------------------------------------------------------------------------------- /test/sample_app_3/controllers/bat_controller.rb: -------------------------------------------------------------------------------- 1 | class SampleApp::BatController < RESTRack::ResourceController 2 | 3 | def show(id) 4 | return { :WOM => 'BAT!' } if id == 777 5 | return { :WOM => 'NOBAT!' } if id == '777' 6 | return { :SUHWING => 'BATTER' } 7 | end 8 | 9 | end -------------------------------------------------------------------------------- /test/sample_app_3/controllers/baz_controller.rb: -------------------------------------------------------------------------------- 1 | class SampleApp::BazController < RESTRack::ResourceController 2 | 3 | def show(id) 4 | return { :BAZ => 'HOLA!' } if id == 777 5 | return { :BAZ => 'ALOHA!' } if id == '777' 6 | return { :OTHER => 'YUP' } 7 | end 8 | 9 | end -------------------------------------------------------------------------------- /test/sample_app_3/controllers/baza_controller.rb: -------------------------------------------------------------------------------- 1 | class SampleApp::BazaController < RESTRack::ResourceController 2 | 3 | keyed_with_type Fixnum 4 | 5 | def show(id) 6 | return { :BAZA => 'YESSIR' } if id == 1 7 | return { :NOWAY => 'JOSE' } if id == 8 8 | return { :NOTTODAY => 'JOSEPH' } 9 | end 10 | 11 | end -------------------------------------------------------------------------------- /test/sample_app_3/controllers/bazu_controller.rb: -------------------------------------------------------------------------------- 1 | class SampleApp::BazuController < RESTRack::ResourceController 2 | 3 | def show(id) 4 | return 1 if id == 1 5 | return 0 6 | end 7 | 8 | end -------------------------------------------------------------------------------- /test/sample_app_3/controllers/foo_bar_controller.rb: -------------------------------------------------------------------------------- 1 | class SampleApp::FooBarController < RESTRack::ResourceController 2 | 3 | has_relationship_to( :baz, :as => :baz ) do |id| 4 | if id =='144' 5 | output = '777' 6 | else 7 | output = '666' 8 | end 9 | output # You can't "return" from a Proc! It will do a "return" in the outer method. Remember a "Proc" is not a Method. 10 | end 11 | 12 | has_relationship_to( :bat, :as => :slugger ) do |id| 13 | if id =='144' 14 | output = '777' 15 | else 16 | output = '666' 17 | end 18 | output # You can't "return" from a Proc! It will do a "return" in the outer method. Remember a "Proc" is not a Method. 19 | end 20 | 21 | has_relationships_to( :baza, :as => :children ) do |id| 22 | [1,2,3,4,5,6,7,8,9] 23 | end 24 | 25 | has_mapped_relationships_to( :bazu, :as => :maps ) do |id| 26 | { 27 | :first => 1, 28 | :second => 2, 29 | :third => 3 30 | } 31 | end 32 | 33 | def index 34 | [1,2,3,4,5,6,7] 35 | end 36 | def create 37 | { :success => true } 38 | end 39 | def replace 40 | { :success => true } 41 | end 42 | def drop 43 | { :success => true } 44 | end 45 | 46 | def show(id) 47 | { :foo => 'bar', :baz => 123 } 48 | end 49 | def update(id) 50 | { :success => true } 51 | end 52 | def destroy(id) 53 | { :success => true } 54 | end 55 | def add(id) 56 | { :success => true } 57 | end 58 | 59 | end -------------------------------------------------------------------------------- /test/sample_app_3/loader.rb: -------------------------------------------------------------------------------- 1 | # for development only 2 | $:.unshift File.expand_path(File.join(File.dirname(__FILE__),'../../lib')) 3 | ##### 4 | require 'restrack' 5 | 6 | module SampleApp; end 7 | class SampleApp::WebService < RESTRack::WebService; end 8 | 9 | RESTRack::CONFIG = RESTRack::load_config(File.join(File.dirname(__FILE__), 'config/constants.yaml')) 10 | RESTRack::CONFIG[:ROOT] = File.dirname(__FILE__) 11 | 12 | # Dynamically load all controllers 13 | Find.find( File.join(File.dirname(__FILE__), 'controllers') ) do |file| 14 | next if File.extname(file) != '.rb' 15 | require file 16 | end 17 | 18 | if File.directory?( File.join(File.dirname(__FILE__), 'models') ) 19 | # Dynamically load all models 20 | Find.find( File.join(File.dirname(__FILE__), 'models') ) do |file| 21 | next if File.extname(file) != '.rb' 22 | require file 23 | end 24 | end 25 | 26 | puts "sample_app_3 RESTRack::CONFIG:\n" 27 | config = RESTRack::CONFIG.keys.map {|c| c.to_s }.sort 28 | config.each do |key| 29 | puts "\t" + key + ' => ' + RESTRack::CONFIG[key.to_sym].to_s 30 | end 31 | puts "\n" 32 | -------------------------------------------------------------------------------- /test/sample_app_3/test/test_resource_request.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'test/unit' 3 | require 'rack/test' 4 | require File.expand_path(File.join(File.dirname(__FILE__),'..','loader')) 5 | 6 | class SampleApp::TestResourceRequest < Test::Unit::TestCase 7 | 8 | def setup 9 | @ws = SampleApp::WebService.new # init logs 10 | end 11 | 12 | ## These are the resources which can be accessed from the root of your web service. If left empty, all resources are available at the root. 13 | #:ROOT_RESOURCE_ACCEPT: [ 'baz' ] 14 | ## These are the resources which cannot be accessed from the root of your web service. Use either this or ROOT_RESOURCE_ACCEPT as a blacklist or whitelist to establish routing (relationships defined in resource controllers define further routing). 15 | ##:ROOT_RESOURCE_DENY: [] 16 | def test_root_resource_denied 17 | # ROOT_RESOURCE_DENY is not supplied, baz is an allowed resource 18 | env = Rack::MockRequest.env_for('/baz/144', { 19 | :method => 'GET' 20 | }) 21 | output = '' 22 | assert_nothing_raised do 23 | output = @ws.call(env) 24 | end 25 | #test_val = [403, {"Content-Type"=>"text/plain"}, "HTTPStatus::HTTP403Forbidden\nYou are forbidden to access that resource."] 26 | assert_not_equal 403, output[0] 27 | end 28 | 29 | def test_root_resource_accept 30 | # this resource is not in ROOT_RESOURCE_ACCEPT, which is set to ['baz'] 31 | env = Rack::MockRequest.env_for('/foo_bar/144', { 32 | :method => 'GET' 33 | }) 34 | output = '' 35 | assert_nothing_raised do 36 | output = @ws.call(env) 37 | end 38 | #test_val = [403, {"Content-Type"=>"text/plain"}, "HTTPStatus::HTTP403Forbidden\nYou are forbidden to access that resource."] 39 | assert_equal 403, output[0] 40 | end 41 | 42 | end 43 | -------------------------------------------------------------------------------- /test/sample_app_3/views/foo_bar/show.xml.builder: -------------------------------------------------------------------------------- 1 | xml.data do 2 | xml.foo data[:foo] 3 | xml.baz data[:baz] 4 | end 5 | -------------------------------------------------------------------------------- /test/sample_app_4/config/constants.yaml: -------------------------------------------------------------------------------- 1 | #GENERATOR-CONST# -DO NOT REMOVE OR CHANGE THIS LINE- Application-Namespace => SampleApp 2 | # 3 | # = constants.yaml 4 | # This is where RESTRack applications define the constants relevant to their particular 5 | # application that are used by the RESTRack base classes. 6 | 7 | # Application log path definition 8 | :LOG: '/var/log/restrack/sample_app.log' 9 | # Request log path definition 10 | :REQUEST_LOG: '/var/log/restrack/sample_app.request.log' 11 | 12 | # Logger object levels 13 | :LOG_LEVEL: :DEBUG # Logger object level 14 | :REQUEST_LOG_LEVEL: :DEBUG # Logger object level 15 | 16 | # Supported formats are :JSON, :XML, :YAML, :BIN, :TEXT 17 | #:DEFAULT_FORMAT: :JSON 18 | # The resource which will handle root level requests where the name is not specified. Best for users of this not to implement method_missing in their default controller, unless they are checking for bad URI. 19 | :DEFAULT_RESOURCE: '' 20 | 21 | # These are the resources which can be accessed from the root of your web service. If left empty, all resources are available at the root. 22 | :ROOT_RESOURCE_ACCEPT: [ '' ] 23 | # These are the resources which cannot be accessed from the root of your web service. Use either this or ROOT_RESOURCE_ACCEPT as a blacklist or whitelist to establish routing (relationships defined in resource controllers define further routing). 24 | :ROOT_RESOURCE_DENY: [ '' ] 25 | -------------------------------------------------------------------------------- /test/sample_app_4/controllers/bar_controller.rb: -------------------------------------------------------------------------------- 1 | class SampleApp::BarController < RESTRack::ResourceController 2 | 3 | def index 4 | [0,1,2,3] 5 | end 6 | 7 | def show(id) 8 | data = "Hello from Bar with id of #{id}." 9 | end 10 | 11 | end 12 | -------------------------------------------------------------------------------- /test/sample_app_4/controllers/baz_controller.rb: -------------------------------------------------------------------------------- 1 | class SampleApp::BazController < RESTRack::ResourceController 2 | 3 | def initialize 4 | @resource_request.mime_type = RESTRack.mime_type_for(:JSON) 5 | end 6 | 7 | def index 8 | ['cat', 'dog', 'rat', 'emu'] 9 | end 10 | 11 | def show(id) 12 | data = { id => "Hello from Bazzz." } 13 | end 14 | 15 | end 16 | -------------------------------------------------------------------------------- /test/sample_app_4/controllers/foo_controller.rb: -------------------------------------------------------------------------------- 1 | class SampleApp::FooController < RESTRack::ResourceController 2 | 3 | pass_through_to( :bar ) 4 | pass_through_to( :baz, :as => :bazzz ) 5 | 6 | def show_yaml(id) 7 | @resource_request.mime_type = RESTRack.mime_type_for('yaml') 8 | data = { :foo => id, :baz => 'bat' } 9 | end 10 | 11 | def show_text(id) 12 | @resource_request.mime_type = RESTRack.mime_type_for('text') 13 | data = "Hello #{id}!" 14 | end 15 | 16 | def show_image(id) 17 | @resource_request.mime_type = RESTRack.mime_type_for('png') 18 | data = File.read(File.join(File.dirname(__FILE__),'../views/alphatest.png')) 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /test/sample_app_4/loader.rb: -------------------------------------------------------------------------------- 1 | # for development only 2 | $:.unshift File.expand_path(File.join(File.dirname(__FILE__),'../../lib')) 3 | ##### 4 | require 'restrack' 5 | 6 | module SampleApp; end 7 | class SampleApp::WebService < RESTRack::WebService; end 8 | 9 | RESTRack::CONFIG = RESTRack::load_config(File.join(File.dirname(__FILE__), 'config/constants.yaml')) 10 | RESTRack::CONFIG[:ROOT] = File.dirname(__FILE__) 11 | 12 | # Dynamically load all controllers 13 | Find.find( File.join(File.dirname(__FILE__), 'controllers') ) do |file| 14 | next if File.extname(file) != '.rb' 15 | require file 16 | end 17 | 18 | if File.directory?( File.join(File.dirname(__FILE__), 'models') ) 19 | # Dynamically load all models 20 | Find.find( File.join(File.dirname(__FILE__), 'models') ) do |file| 21 | next if File.extname(file) != '.rb' 22 | require file 23 | end 24 | end 25 | 26 | puts "sample_app_4 RESTRack::CONFIG:\n" 27 | config = RESTRack::CONFIG.keys.map {|c| c.to_s }.sort 28 | config.each do |key| 29 | puts "\t" + key + ' => ' + RESTRack::CONFIG[key.to_sym].to_s 30 | end 31 | puts "\n" 32 | -------------------------------------------------------------------------------- /test/sample_app_4/test/test_controller_modifiers.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'test/unit' 3 | require 'rack/test' 4 | require File.expand_path(File.join(File.dirname(__FILE__),'..','loader')) 5 | require 'pp' 6 | 7 | class SampleApp::TestControllerModifiers < Test::Unit::TestCase 8 | 9 | def setup 10 | @ws = SampleApp::WebService.new 11 | end 12 | 13 | def test_pass_through_to 14 | env = Rack::MockRequest.env_for('/foo/123/bar', { 15 | :method => 'GET' 16 | }) 17 | output = '' 18 | assert_nothing_raised do 19 | output = @ws.call(env) 20 | end 21 | test_val = [0,1,2,3].to_json 22 | assert_equal test_val, output[2][0] 23 | 24 | 25 | #"Hello from Bar with id of 26 | end 27 | 28 | end 29 | -------------------------------------------------------------------------------- /test/sample_app_4/test/test_formats.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'test/unit' 3 | require 'rack/test' 4 | require File.expand_path(File.join(File.dirname(__FILE__),'..','loader')) 5 | require 'pp' 6 | 7 | class SampleApp::TestFormats < Test::Unit::TestCase 8 | 9 | def setup 10 | @ws = SampleApp::WebService.new 11 | end 12 | 13 | def test_yaml 14 | env = Rack::MockRequest.env_for('/foo/123/show_yaml', { 15 | :method => 'GET' 16 | }) 17 | output = '' 18 | assert_nothing_raised do 19 | output = @ws.call(env) 20 | end 21 | assert_equal 'text/x-yaml', output[1]['Content-Type'] 22 | test_val = YAML.dump( { :foo => '123', :baz => 'bat' } ) 23 | assert_equal test_val, output[2][0] 24 | end 25 | 26 | def test_text 27 | env = Rack::MockRequest.env_for('/foo/123/show_text', { 28 | :method => 'GET' 29 | }) 30 | output = '' 31 | assert_nothing_raised do 32 | output = @ws.call(env) 33 | end 34 | assert_equal 'text/plain', output[1]['Content-Type'] 35 | test_val = 'Hello 123!' 36 | assert_equal test_val, output[2][0] 37 | end 38 | 39 | def test_image 40 | env = Rack::MockRequest.env_for('/foo/123/show_image', { 41 | :method => 'GET' 42 | }) 43 | output = '' 44 | assert_nothing_raised do 45 | output = @ws.call(env) 46 | end 47 | assert_equal 'image/png', output[1]['Content-Type'] 48 | assert output[2][0].length 49 | end 50 | 51 | end 52 | -------------------------------------------------------------------------------- /test/sample_app_4/views/alphatest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stjohncj/RESTRack/f8f4075fb32c88308b3ce34d8d91f79fdb53dc80/test/sample_app_4/views/alphatest.png -------------------------------------------------------------------------------- /test/sample_app_5/config.ru: -------------------------------------------------------------------------------- 1 | # rackup config.ru 2 | require File.join(File.dirname(__FILE__),'loader') 3 | run SampleApp5::WebService.new 4 | -------------------------------------------------------------------------------- /test/sample_app_5/config/constants.yaml: -------------------------------------------------------------------------------- 1 | #GENERATOR-CONST# -DO NOT REMOVE OR CHANGE THIS LINE- Application-Namespace => sample_app_5 2 | # 3 | # = constants.yaml 4 | # This is where RESTRack applications define the constants relevant to their particular 5 | # application that are used by the RESTRack base classes. 6 | 7 | # Application log path definition 8 | :LOG: '/var/log/restrack/sample_app_5.log' 9 | # Request log path definition 10 | :REQUEST_LOG: '/var/log/restrack/sample_app_5.request.log' 11 | 12 | # Logger object levels 13 | :LOG_LEVEL: :DEBUG 14 | :REQUEST_LOG_LEVEL: :DEBUG 15 | 16 | # Supported formats are :JSON, :XML, :YAML, :BIN, :TEXT 17 | :DEFAULT_FORMAT: :JSON 18 | # The resource which will handle root level requests where the name is not specified. Best for users of this not to implement method_missing in their default controller, unless they are checking for bad URI. 19 | :DEFAULT_RESOURCE: nil 20 | 21 | # These are the resources which can be accessed from the root of your web service. If left empty, all resources are available at the root. 22 | :ROOT_RESOURCE_ACCEPT: [] 23 | # These are the resources which cannot be accessed from the root of your web service. Use either this or ROOT_RESOURCE_ACCEPT as a blacklist or whitelist to establish routing (relationships defined in resource controllers define further routing). 24 | :ROOT_RESOURCE_DENY: [] 25 | -------------------------------------------------------------------------------- /test/sample_app_5/controllers/hook_controller.rb: -------------------------------------------------------------------------------- 1 | class SampleApp5::HookController < RESTRack::ResourceController 2 | 3 | def show(id) 4 | if id == 'pre_processor' 5 | { 'pre_processor_flag' => @resource_request.instance_variable_get(:@pre_processor_executed).to_s } 6 | else 7 | { 'neither' => true } 8 | end 9 | end 10 | 11 | def dynamic_request 12 | @resource_request.instance_variable_set('@error_code', 123456) 13 | @resource_request.instance_variable_set('@error_message', 'Invalid password or email address combination') 14 | end 15 | 16 | end -------------------------------------------------------------------------------- /test/sample_app_5/hooks.rb: -------------------------------------------------------------------------------- 1 | module RESTRack 2 | class Hooks 3 | # Use this to execute a code block prior to all requests handled by your service. 4 | # For example, to do database connection per request you could establish db connections and transactions here. 5 | def pre_processor(request) 6 | request.instance_variable_set(:@pre_processor_executed, true) 7 | end 8 | # Use this to execute code after all requests handled by your service. 9 | # For example, to do database connection per request you could commit transactions and/or teardown db connections here. 10 | def post_processor(response) 11 | $post_processor_executed = true 12 | $response = response 13 | end 14 | end # class Hooks 15 | end # module RESTRack -------------------------------------------------------------------------------- /test/sample_app_5/loader.rb: -------------------------------------------------------------------------------- 1 | # for development only 2 | $:.unshift File.expand_path(File.join(File.dirname(__FILE__),'../../lib')) 3 | ##### 4 | require 'restrack' 5 | 6 | module SampleApp5; end 7 | class SampleApp5::WebService < RESTRack::WebService; end 8 | 9 | RESTRack::CONFIG = RESTRack::load_config(File.join(File.dirname(__FILE__), 'config/constants.yaml')) 10 | RESTRack::CONFIG[:ROOT] = File.dirname(__FILE__) 11 | 12 | require File.join(RESTRack::CONFIG[:ROOT], 'hooks') if File.exists?(File.join(RESTRack::CONFIG[:ROOT], 'hooks.rb')) 13 | 14 | # Dynamically load all controllers 15 | Find.find( File.join(File.dirname(__FILE__), 'controllers') ) do |file| 16 | next if File.extname(file) != '.rb' 17 | require file 18 | end 19 | 20 | if File.directory?( File.join(File.dirname(__FILE__), 'models') ) 21 | # Dynamically load all models 22 | Find.find( File.join(File.dirname(__FILE__), 'models') ) do |file| 23 | next if File.extname(file) != '.rb' 24 | require file 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/sample_app_5/test/test_hooks.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'test/unit' 3 | require 'rack/test' 4 | require File.expand_path(File.join(File.dirname(__FILE__),'..','loader')) 5 | require 'pp' 6 | 7 | class SampleApp5::TestHooks < Test::Unit::TestCase 8 | 9 | def setup 10 | @ws = SampleApp5::WebService.new 11 | end 12 | 13 | def test_pre_processor_enabled 14 | env = Rack::MockRequest.env_for('/hook/pre_processor', { 15 | :method => 'GET' 16 | }) 17 | output = '' 18 | assert_nothing_raised do 19 | output = @ws.call(env) 20 | end 21 | test_val = { 'pre_processor_flag' => true.to_s }.to_json 22 | assert_equal test_val, output[2][0] 23 | $post_processor_executed = nil 24 | end 25 | 26 | def test_pre_processor_disabled 27 | RESTRack::CONFIG[:PRE_PROCESSOR_DISABLED] = true 28 | env = Rack::MockRequest.env_for('/hook/pre_processor', { 29 | :method => 'GET' 30 | }) 31 | output = '' 32 | assert_nothing_raised do 33 | output = @ws.call(env) 34 | end 35 | test_val = { 'pre_processor_flag' => nil.to_s }.to_json 36 | assert_equal test_val, output[2][0] 37 | RESTRack::CONFIG[:PRE_PROCESSOR_DISABLED] = false 38 | $post_processor_executed = nil 39 | end 40 | 41 | def test_post_processor_enabled 42 | env = Rack::MockRequest.env_for('/hook/post_processor', { 43 | :method => 'GET' 44 | }) 45 | output = '' 46 | assert_nothing_raised do 47 | output = @ws.call(env) 48 | end 49 | assert $post_processor_executed 50 | $post_processor_executed = nil 51 | end 52 | 53 | def test_post_processor_disabled 54 | RESTRack::CONFIG[:POST_PROCESSOR_DISABLED] = true 55 | env = Rack::MockRequest.env_for('/hook/post_processor', { 56 | :method => 'GET' 57 | }) 58 | output = '' 59 | assert_nothing_raised do 60 | output = @ws.call(env) 61 | end 62 | assert !$post_processor_executed 63 | RESTRack::CONFIG[:POST_PROCESSOR_DISABLED] = false 64 | $post_processor_executed = nil 65 | end 66 | 67 | def test_post_processor_has_response 68 | RESTRack::CONFIG[:PRE_PROCESSOR_DISABLED] = false 69 | RESTRack::CONFIG[:POST_PROCESSOR_DISABLED] = false 70 | env = Rack::MockRequest.env_for('/hook/post_processor', { 71 | :method => 'GET' 72 | }) 73 | output = '' 74 | assert_nothing_raised do 75 | output = @ws.call(env) 76 | end 77 | assert $response.is_a? RESTRack::Response 78 | $post_processor_executed = nil 79 | end 80 | 81 | def test_post_processor_has_http_status_in_response 82 | RESTRack::CONFIG[:PRE_PROCESSOR_DISABLED] = false 83 | RESTRack::CONFIG[:POST_PROCESSOR_DISABLED] = false 84 | env = Rack::MockRequest.env_for('/hook/post_processor', { 85 | :method => 'GET' 86 | }) 87 | output = '' 88 | assert_nothing_raised do 89 | output = @ws.call(env) 90 | end 91 | assert_equal(200, $response.status) 92 | $post_processor_executed = nil 93 | end 94 | 95 | def test_post_processor_allows_pass_through_of_dynamic_request_attributes 96 | RESTRack::CONFIG[:PRE_PROCESSOR_DISABLED] = false 97 | RESTRack::CONFIG[:POST_PROCESSOR_DISABLED] = false 98 | env = Rack::MockRequest.env_for('/hook/dynamic_request', { 99 | :method => 'GET' 100 | }) 101 | output = '' 102 | assert_nothing_raised do 103 | output = @ws.call(env) 104 | end 105 | assert_equal(123456, $response.request.instance_variable_get('@error_code')) 106 | assert_equal('Invalid password or email address combination', $response.request.instance_variable_get('@error_message')) 107 | $post_processor_executed = nil 108 | end 109 | 110 | end -------------------------------------------------------------------------------- /test/test_support.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'test/unit' 3 | require File.expand_path(File.join(File.dirname(__FILE__),'../lib/restrack/support')) 4 | 5 | module RESTRack 6 | class TestSupport < Test::Unit::TestCase 7 | 8 | RESTRack::CONFIG = RESTRack.load_config(File.expand_path(File.join(File.dirname(__FILE__),'../test/sample_app_1/config/constants.yaml'))) 9 | 10 | def test_constants 11 | assert_nothing_raised do 12 | RESTRack::CONFIG[:LOG].to_sym.to_s 13 | RESTRack::CONFIG[:LOG_LEVEL].to_sym.to_s 14 | RESTRack::CONFIG[:REQUEST_LOG].to_sym.to_s 15 | RESTRack::CONFIG[:REQUEST_LOG_LEVEL].to_sym.to_s 16 | RESTRack::CONFIG[:DEFAULT_FORMAT].to_sym.to_s 17 | RESTRack::CONFIG[:DEFAULT_RESOURCE].to_sym.to_s 18 | assert RESTRack::CONFIG[:ROOT_RESOURCE_ACCEPT].blank? || RESTRack::CONFIG[:ROOT_RESOURCE_ACCEPT].class == Array 19 | assert RESTRack::CONFIG[:ROOT_RESOURCE_DENY].blank? || RESTRack::CONFIG[:ROOT_RESOURCE_DENY].class == Array 20 | end 21 | end 22 | 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/test_web_service.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'test/unit' 3 | require 'rack/test' 4 | require File.expand_path(File.join(File.dirname(__FILE__),'../lib/restrack')) 5 | 6 | module RESTRack 7 | class TestWebService < Test::Unit::TestCase 8 | 9 | RESTRack::CONFIG = RESTRack.load_config(File.expand_path(File.join(File.dirname(__FILE__),'../test/sample_app_1/config/constants.yaml'))) 10 | 11 | def test_call 12 | env = Rack::MockRequest.env_for('/foo_bar', { 13 | :method => 'POST', 14 | :params => %Q|[ 15 | { 16 | "bar": "baz" 17 | } 18 | ]| 19 | }) 20 | #assert_nothing_raised do 21 | ws = RESTRack::WebService.new # init logs 22 | ws.call(env) 23 | #end 24 | end 25 | 26 | def test_initialize 27 | assert_nothing_raised do 28 | ws = RESTRack::WebService.new 29 | end 30 | end 31 | 32 | end # class TestWebService 33 | end # module RESTRack 34 | --------------------------------------------------------------------------------