├── .gitignore ├── Gemfile ├── Gemfile.lock ├── MIT-LICENSE ├── README.md ├── Rakefile ├── VERSION ├── apify.gemspec ├── app ├── helpers │ └── apify_helper.rb └── views │ └── apify │ └── api │ ├── _actions.html.erb │ ├── _client.html.erb │ ├── _overview.html.erb │ ├── _protocol.html.erb │ └── docs.html.erb ├── examples ├── client │ └── client.rb └── host │ ├── README │ ├── Rakefile │ ├── app │ ├── controllers │ │ ├── api_controller.rb │ │ └── application_controller.rb │ ├── helpers │ │ ├── api_helper.rb │ │ └── application_helper.rb │ └── models │ │ └── api.rb │ ├── config │ ├── boot.rb │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── backtrace_silencers.rb │ │ ├── cookie_verification_secret.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── new_rails_defaults.rb │ │ └── session_store.rb │ ├── locales │ │ └── en.yml │ └── routes.rb │ ├── db │ ├── development.sqlite3 │ └── seeds.rb │ ├── log │ ├── development.log │ ├── production.log │ ├── server.log │ └── test.log │ ├── public │ ├── 404.html │ ├── 422.html │ ├── 500.html │ ├── favicon.ico │ ├── javascripts │ │ └── application.js │ └── robots.txt │ └── script │ ├── about │ ├── console │ ├── dbconsole │ ├── destroy │ ├── generate │ ├── performance │ ├── benchmarker │ └── profiler │ ├── plugin │ ├── runner │ └── server ├── lib ├── apify.rb └── apify │ ├── action.rb │ ├── api.rb │ ├── api_controller.rb │ ├── client.rb │ ├── errors.rb │ ├── exchange.rb │ ├── patterns.rb │ └── schema_helper.rb ├── rails └── init.rb └── spec ├── apify ├── action_spec.rb └── client_spec.rb ├── app_root ├── app │ ├── controllers │ │ ├── api_controller.rb │ │ └── application_controller.rb │ └── models │ │ └── api.rb ├── config │ ├── boot.rb │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── in_memory.rb │ │ ├── mysql.rb │ │ ├── postgresql.rb │ │ ├── sqlite.rb │ │ └── sqlite3.rb │ └── routes.rb ├── lib │ └── console_with_fixtures.rb └── script │ └── console ├── controllers └── api_controller_spec.rb ├── models └── api_spec.rb ├── rcov.opts ├── spec.opts └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | doc 2 | pkg 3 | *.gem 4 | .idea 5 | spec/app_root/log/* 6 | 7 | 8 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'rails', '=2.3.10' 4 | gem 'rspec', '=1.3.1' 5 | gem 'rspec-rails', '=1.3.3' 6 | gem 'rest-client' 7 | gem 'jsonschema' 8 | gem 'json' 9 | gem 'webmock' 10 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | actionmailer (2.3.10) 5 | actionpack (= 2.3.10) 6 | actionpack (2.3.10) 7 | activesupport (= 2.3.10) 8 | rack (~> 1.1.0) 9 | activerecord (2.3.10) 10 | activesupport (= 2.3.10) 11 | activeresource (2.3.10) 12 | activesupport (= 2.3.10) 13 | activesupport (2.3.10) 14 | addressable (2.2.2) 15 | crack (0.1.8) 16 | json (1.4.6) 17 | jsonschema (2.0.0) 18 | mime-types (1.16) 19 | rack (1.1.0) 20 | rails (2.3.10) 21 | actionmailer (= 2.3.10) 22 | actionpack (= 2.3.10) 23 | activerecord (= 2.3.10) 24 | activeresource (= 2.3.10) 25 | activesupport (= 2.3.10) 26 | rake (>= 0.8.3) 27 | rake (0.8.7) 28 | rest-client (1.6.1) 29 | mime-types (>= 1.16) 30 | rspec (1.3.1) 31 | rspec-rails (1.3.3) 32 | rack (>= 1.0.0) 33 | rspec (= 1.3.1) 34 | webmock (1.4.0) 35 | addressable (>= 2.2.2) 36 | crack (>= 0.1.7) 37 | 38 | PLATFORMS 39 | ruby 40 | 41 | DEPENDENCIES 42 | json 43 | jsonschema 44 | rails (= 2.3.10) 45 | rest-client 46 | rspec (= 1.3.1) 47 | rspec-rails (= 1.3.3) 48 | webmock 49 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Henning Koch 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Apify 2 | ===== 3 | 4 | Apify lets you bolt a [JSON](http://en.wikipedia.org/wiki/JSON) API onto your Rails application. 5 | Optional features are auto-generated API documentation, request validation with [JSON Schema](http://json-schema.org/) 6 | and a client class to consume Apify APIs from Ruby code. 7 | 8 | 9 | Two minute quickstart 10 | --------------------- 11 | 12 | Install the gem with 13 | 14 | sudo gem install apify 15 | 16 | For Rails 2, add the following to your `environment.rb`: 17 | 18 | config.gem 'apify' 19 | 20 | For Rails 3, add the following to your `Gemfile`: 21 | 22 | gem 'apify' 23 | 24 | Describe your API actions in `models/api.rb`: 25 | 26 | class Api < Apify::Api 27 | get :ping do 28 | respond do 29 | { 'message' => 'pong' } 30 | end 31 | end 32 | end 33 | 34 | Create a controller to serve your API in `controllers/api_controller.rb`: 35 | 36 | class ApiController < Apify::ApiController 37 | api Api 38 | end 39 | 40 | Connect the routes to your API in `config/routes.rb`: 41 | 42 | ActionController::Routing::Routes.draw do |map| 43 | Api.draw_routes(map) 44 | end 45 | 46 | You have now exposed an API action under `http://host/api/ping`. 47 | 48 | An example for this setup can also be found under the `examples/host` directory inside the repository. 49 | 50 | 51 | Protocol 52 | -------- 53 | 54 | If you are planning to consume your Apify API with the `Apify::Client` class, you won't need to know most of this. Nonetheless, this is what you're getting into: 55 | 56 | - Apify is about sending JSON objects (hashes) back and forth. No fancy envelope formats. 57 | - An Apify API defines actions with a name and HTTP method (`GET, POST, PUT, DELETE`). 58 | - API actions are accessed over HTTP. Each action gets its own route. 59 | - API actions can take arguments. Those are serialized into a **single** HTTP parameter `args` as JSON. This is to simplify client code that consumes your API (nested params are hard). 60 | - Successful responses are returned with a status of 200 (OK). 61 | - The body of a successful response is always a hash, serialized as JSON. 62 | - Requests that have errors are returned with a status that is **not** 200. The body of a error response is an error message in the response's content type (won't be JSON in most cases). 63 | 64 | 65 | Defining API actions 66 | -------------------- 67 | 68 | API actions are defined in a model such as `models/api.rb`: 69 | 70 | - This model needs to inherit from `Apify::Api`. 71 | - An API method has a name and a HTTP method (`GET, POST, PUT, DELETE`) 72 | - An API action always returns a hash. If an API action returns nil, Apify will turn that into an empty hash for you. 73 | 74 | Here is an example for a simple, reading API action: 75 | 76 | get :ping do 77 | respond do 78 | { 'message' => 'pong' } 79 | end 80 | end 81 | 82 | Action arguments can be accessed through the `args` hash. Its keys are always strings, never symbols. 83 | 84 | Here is an example for an API action that takes an argument: 85 | 86 | post :hello do 87 | respond do 88 | { 'message' => 'Hello ' + args['name'] } 89 | end 90 | end 91 | 92 | 93 | Schemas 94 | ------- 95 | 96 | You can describe the expected format of method arguments and response values using [JSON Schema](http://json-schema.org/). This lets you define rules like that an argument is required or must be in a given format. 97 | 98 | Schemas are an optional feature, but providing schemas has some benefits: 99 | 100 | - Your actions don't need to handle unexpected arguments (remember that strangers are going to call your code). 101 | - You don't need to worry about responding with values that break your own contract. Apify will validate your own responses and raise an error if they don't validate. 102 | - Apify can auto-generate public documentation for actions with schemas. This documentation will contain generated examples for a successful request and response and also offer your schemas for download. 103 | 104 | You can provide a schema for arguments, a schema for response values, or both. Here is the `hello` action from the above example with schemas: 105 | 106 | post :hello do 107 | 108 | schema :args do 109 | object('name' => string) 110 | end 111 | 112 | schema :value do 113 | object('message' => string) 114 | end 115 | 116 | respond do 117 | { 'message' => 'Hello ' + args['name'] } 118 | end 119 | 120 | end 121 | 122 | Your API now allows to download the schema and an auto-generated example in JSON format: 123 | 124 | - POST http://host/api/hello?schema=args 125 | - POST http://host/api/hello?example=args 126 | - POST http://host/api/hello?schema=value 127 | - POST http://host/api/hello?example=value 128 | 129 | Here is another example for a more complex schema: 130 | 131 | get :contacts do 132 | schema :value do 133 | array( 134 | object( 135 | 'name' => string, 136 | 'phone' => integer, 137 | 'phone_type' => enum(string, 'home', 'office'), 138 | 'email' => optional(email), 139 | 'website' => optional(url), 140 | 'favorite' => boolean 141 | ) 142 | ) 143 | end 144 | end 145 | 146 | Note that the schema considers an key/value pair to be "present" when the key is present, even if the value is `nil`. That means if an object entry is optional and you want to omit that entry, you need to leave out the entire key/value pair. 147 | 148 | 149 | Auto-generated API documentation 150 | -------------------------------- 151 | 152 | Every Apify API comes with auto-generated HTML documentation: 153 | 154 | - The documentation can be accessed from the `docs` action of an `ApiController`, e.g. http://host/api/docs 155 | - The documentation contains parts of this README (protocol, instructions to use the Ruby client) 156 | - If your actions have schemas, the documentation includes those schemas and request/response examples generated from them. 157 | 158 | You can give your actions descriptions to make the API documentation even more useful: 159 | 160 | post :hello do 161 | 162 | description 'Says hello to the given name.' 163 | 164 | respond do 165 | { 'message' => 'Hello ' + args['name'] } 166 | end 167 | 168 | end 169 | 170 | 171 | 172 | Authentication 173 | -------------- 174 | 175 | If your API uses a single username and password for all requests, you can activate basic authentication like this: 176 | 177 | class ApiController < ApplicationController::Base 178 | api Api 179 | authenticate :user => 'api', :password => 'secret' 180 | end 181 | 182 | If your use case is more complex, just roll your own authentication. Your `ApiController` is just a regular controller. 183 | 184 | 185 | Consuming an API with the Ruby client 186 | ------------------------------------- 187 | 188 | An easy way to consume an Apify API is to use the Apify::Client class: 189 | 190 | - The client calls an Apify API with a Ruby hash as argument and returns the response as a Ruby hash. 191 | - It takes care of the protocol details and lets your users focus on exchanging data. 192 | - Your users will only need the `apify` gem. There are no dependencies on Rails. 193 | - The auto-generated documentation contains these instructions on how to install and use the client class. 194 | 195 | You already know how to require the `apify` gem from a Rails application. This is how you require the client class when your code is not a Rails application: 196 | 197 | require 'rubygems' 198 | gem 'apify' 199 | require 'apify/client' 200 | 201 | Here is an example for how to use the client class: 202 | 203 | client = Apify::Client.new(:host => 'localhost:3000', :user => 'api', :password => 'secret') 204 | client.post('/api/hello', :name => 'Jack') # { 'message' => 'Hello Jack' } 205 | 206 | Errors can be caught and inspected like this: 207 | 208 | begin 209 | client.get('/api/hello', :name => 'Jack') # { 'message' => 'Hello Jack' } 210 | rescue Apify::RequestFailed => e 211 | puts "Oh no! The API request failed." 212 | puts "Message: #{e.message}" 213 | puts "Response: #{e.response_body}" 214 | end 215 | 216 | Use the `:protocol` option to connect using SSL: 217 | 218 | client = Apify::Client.new(:host => 'api.site.com', :user => 'api', :password => 'secret', :protocol => 'https') 219 | 220 | An example for an API client can be found under the `examples/client` directory inside the repository. 221 | 222 | 223 | Dealing with dates and timestamps 224 | --------------------------------- 225 | 226 | Unfortunately dates and timestamps are not among the data types defined by JSON and JSON Schema. You can work around this in any way you like, but Apify supports a default approach: 227 | 228 | - The workaround supported by Apify is to handle a date as a string formatted like "2011-05-01" and a timestamp as a string formatted like "2011-05-01 12:00:04". 229 | - Aside from being easy to parse, you can feed such strings into a model's date or time field and ActiveRecord will convert them into `Date` and `Time` objects. 230 | - Apify gives you helper methods `sql_date` and `sql_datetime` to support this pattern in your schemas and actions. 231 | 232 | Here are examples for actions that deal with dates and timestamps this way: 233 | 234 | get :now do 235 | schema :value do 236 | object('now' => sql_datetime) 237 | end 238 | respond do 239 | { 'now' => sql_datetime(Time.now) } 240 | end 241 | end 242 | 243 | get :today do 244 | schema :value do 245 | object('today' => sql_date) 246 | end 247 | respond do 248 | { 'today' => sql_date(Date.today) } 249 | end 250 | end 251 | 252 | 253 | Testing API actions 254 | ------------------- 255 | 256 | Since your API actions end up being vanilla Ruby methods that turn argument hashes into value hashes, you can test them without special tools. 257 | 258 | Here is an example in RSpec: 259 | 260 | describe Api, 'hello' do 261 | 262 | it 'should greet the given name' do 263 | Api.post(:hello, :name => 'Jack').should == { 'message' => 'Hello Jack' } 264 | end 265 | 266 | it 'should require a name argument' do 267 | expect { Api.post(:hello) }.to raise_error(Apify::Invalid) 268 | end 269 | 270 | end 271 | 272 | 273 | Custom routing 274 | -------------- 275 | 276 | You can create a route for each action of an API by adding this to your `config/routes.rb`: 277 | 278 | ActionController::Routing::Routes.draw do |map| 279 | Api.draw_routes(map) 280 | end 281 | 282 | This will create URLs like `/api/ping`, `/api/hello`, etc. You can change the `/api` prefix like this: 283 | 284 | ActionController::Routing::Routes.draw do |map| 285 | Api.draw_routes(map, :base_path => 'interface/v1') 286 | end 287 | 288 | You can also forego the automatic route generation completely and roll your own URLs: 289 | 290 | ActionController::Routing::Routes.draw do |map| 291 | map.connect 'api/ping', :controller => 'api', :action => 'ping', :conditions { :method => :get } 292 | map.connect 'api/hello', :controller => 'api', :action => 'hello', :conditions { :method => :post } 293 | end 294 | 295 | 296 | Compatibility 297 | --------------------- 298 | 299 | Has only been tested with Rails 2.3. Will probably blow up in anything newer. 300 | 301 | Credits 302 | ------- 303 | 304 | Henning Koch ([makandra.com](http://makandra.com/), [gem-session.com](http://gem-session.com/)) 305 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'rake/rdoctask' 3 | require 'spec/rake/spectask' 4 | 5 | desc 'Default: Run all specs.' 6 | task :default => :spec 7 | 8 | desc "Run all specs" 9 | Spec::Rake::SpecTask.new() do |t| 10 | t.spec_opts = ['--options', "\"spec/spec.opts\""] 11 | t.spec_files = FileList['spec/**/*_spec.rb'] 12 | end 13 | 14 | desc 'Generate documentation for the apify gem.' 15 | Rake::RDocTask.new(:rdoc) do |rdoc| 16 | rdoc.rdoc_dir = 'rdoc' 17 | rdoc.title = 'apify' 18 | rdoc.options << '--line-numbers' << '--inline-source' 19 | rdoc.rdoc_files.include('README') 20 | rdoc.rdoc_files.include('lib/**/*.rb') 21 | end 22 | 23 | begin 24 | require 'jeweler' 25 | Jeweler::Tasks.new do |gemspec| 26 | gemspec.name = "apify" 27 | gemspec.summary = "Compact definition of JSON APIs for Rails applications. " 28 | gemspec.email = "github@makandra.de" 29 | gemspec.homepage = "http://github.com/makandra/apify" 30 | gemspec.description = "Compact definition of JSON APIs for Rails applications. " 31 | gemspec.authors = ["Henning Koch"] 32 | gemspec.add_dependency 'json' 33 | gemspec.add_dependency 'jsonschema' 34 | gemspec.add_dependency 'rest-client', '>=1.6.1' 35 | end 36 | Jeweler::GemcutterTasks.new 37 | rescue LoadError 38 | puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler" 39 | end 40 | 41 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.5.4 2 | -------------------------------------------------------------------------------- /apify.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command 4 | # -*- encoding: utf-8 -*- 5 | 6 | Gem::Specification.new do |s| 7 | s.name = %q{apify} 8 | s.version = "0.5.4" 9 | 10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 11 | s.authors = ["Henning Koch"] 12 | s.date = %q{2011-03-02} 13 | s.description = %q{Compact definition of JSON APIs for Rails applications. } 14 | s.email = %q{github@makandra.de} 15 | s.extra_rdoc_files = [ 16 | "README.md" 17 | ] 18 | s.files = [ 19 | ".gitignore", 20 | "Gemfile", 21 | "Gemfile.lock", 22 | "MIT-LICENSE", 23 | "README.md", 24 | "Rakefile", 25 | "VERSION", 26 | "apify.gemspec", 27 | "app/helpers/apify_helper.rb", 28 | "app/views/apify/api/_actions.html.erb", 29 | "app/views/apify/api/_client.html.erb", 30 | "app/views/apify/api/_overview.html.erb", 31 | "app/views/apify/api/_protocol.html.erb", 32 | "app/views/apify/api/docs.html.erb", 33 | "examples/client/client.rb", 34 | "examples/host/README", 35 | "examples/host/Rakefile", 36 | "examples/host/app/controllers/api_controller.rb", 37 | "examples/host/app/controllers/application_controller.rb", 38 | "examples/host/app/helpers/api_helper.rb", 39 | "examples/host/app/helpers/application_helper.rb", 40 | "examples/host/app/models/api.rb", 41 | "examples/host/config/boot.rb", 42 | "examples/host/config/database.yml", 43 | "examples/host/config/environment.rb", 44 | "examples/host/config/environments/development.rb", 45 | "examples/host/config/environments/production.rb", 46 | "examples/host/config/environments/test.rb", 47 | "examples/host/config/initializers/backtrace_silencers.rb", 48 | "examples/host/config/initializers/cookie_verification_secret.rb", 49 | "examples/host/config/initializers/inflections.rb", 50 | "examples/host/config/initializers/mime_types.rb", 51 | "examples/host/config/initializers/new_rails_defaults.rb", 52 | "examples/host/config/initializers/session_store.rb", 53 | "examples/host/config/locales/en.yml", 54 | "examples/host/config/routes.rb", 55 | "examples/host/db/development.sqlite3", 56 | "examples/host/db/seeds.rb", 57 | "examples/host/log/development.log", 58 | "examples/host/log/production.log", 59 | "examples/host/log/server.log", 60 | "examples/host/log/test.log", 61 | "examples/host/public/404.html", 62 | "examples/host/public/422.html", 63 | "examples/host/public/500.html", 64 | "examples/host/public/favicon.ico", 65 | "examples/host/public/javascripts/application.js", 66 | "examples/host/public/robots.txt", 67 | "examples/host/script/about", 68 | "examples/host/script/console", 69 | "examples/host/script/dbconsole", 70 | "examples/host/script/destroy", 71 | "examples/host/script/generate", 72 | "examples/host/script/performance/benchmarker", 73 | "examples/host/script/performance/profiler", 74 | "examples/host/script/plugin", 75 | "examples/host/script/runner", 76 | "examples/host/script/server", 77 | "lib/apify.rb", 78 | "lib/apify/action.rb", 79 | "lib/apify/api.rb", 80 | "lib/apify/api_controller.rb", 81 | "lib/apify/client.rb", 82 | "lib/apify/errors.rb", 83 | "lib/apify/exchange.rb", 84 | "lib/apify/patterns.rb", 85 | "lib/apify/schema_helper.rb", 86 | "rails/init.rb", 87 | "spec/apify/action_spec.rb", 88 | "spec/apify/client_spec.rb", 89 | "spec/app_root/app/controllers/api_controller.rb", 90 | "spec/app_root/app/controllers/application_controller.rb", 91 | "spec/app_root/app/models/api.rb", 92 | "spec/app_root/config/boot.rb", 93 | "spec/app_root/config/database.yml", 94 | "spec/app_root/config/environment.rb", 95 | "spec/app_root/config/environments/in_memory.rb", 96 | "spec/app_root/config/environments/mysql.rb", 97 | "spec/app_root/config/environments/postgresql.rb", 98 | "spec/app_root/config/environments/sqlite.rb", 99 | "spec/app_root/config/environments/sqlite3.rb", 100 | "spec/app_root/config/routes.rb", 101 | "spec/app_root/lib/console_with_fixtures.rb", 102 | "spec/app_root/script/console", 103 | "spec/controllers/api_controller_spec.rb", 104 | "spec/models/api_spec.rb", 105 | "spec/rcov.opts", 106 | "spec/spec.opts", 107 | "spec/spec_helper.rb" 108 | ] 109 | s.homepage = %q{http://github.com/makandra/apify} 110 | s.rdoc_options = ["--charset=UTF-8"] 111 | s.require_paths = ["lib"] 112 | s.rubygems_version = %q{1.3.7} 113 | s.summary = %q{Compact definition of JSON APIs for Rails applications.} 114 | s.test_files = [ 115 | "spec/apify/action_spec.rb", 116 | "spec/apify/client_spec.rb", 117 | "spec/app_root/app/controllers/api_controller.rb", 118 | "spec/app_root/app/controllers/application_controller.rb", 119 | "spec/app_root/app/models/api.rb", 120 | "spec/app_root/config/boot.rb", 121 | "spec/app_root/config/environment.rb", 122 | "spec/app_root/config/environments/in_memory.rb", 123 | "spec/app_root/config/environments/mysql.rb", 124 | "spec/app_root/config/environments/postgresql.rb", 125 | "spec/app_root/config/environments/sqlite.rb", 126 | "spec/app_root/config/environments/sqlite3.rb", 127 | "spec/app_root/config/routes.rb", 128 | "spec/app_root/lib/console_with_fixtures.rb", 129 | "spec/controllers/api_controller_spec.rb", 130 | "spec/models/api_spec.rb", 131 | "spec/spec_helper.rb", 132 | "examples/client/client.rb", 133 | "examples/host/app/controllers/api_controller.rb", 134 | "examples/host/app/controllers/application_controller.rb", 135 | "examples/host/app/helpers/api_helper.rb", 136 | "examples/host/app/helpers/application_helper.rb", 137 | "examples/host/app/models/api.rb", 138 | "examples/host/config/boot.rb", 139 | "examples/host/config/environment.rb", 140 | "examples/host/config/environments/development.rb", 141 | "examples/host/config/environments/production.rb", 142 | "examples/host/config/environments/test.rb", 143 | "examples/host/config/initializers/backtrace_silencers.rb", 144 | "examples/host/config/initializers/cookie_verification_secret.rb", 145 | "examples/host/config/initializers/inflections.rb", 146 | "examples/host/config/initializers/mime_types.rb", 147 | "examples/host/config/initializers/new_rails_defaults.rb", 148 | "examples/host/config/initializers/session_store.rb", 149 | "examples/host/config/routes.rb", 150 | "examples/host/db/seeds.rb" 151 | ] 152 | 153 | if s.respond_to? :specification_version then 154 | current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION 155 | s.specification_version = 3 156 | 157 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 158 | s.add_runtime_dependency(%q, [">= 0"]) 159 | s.add_runtime_dependency(%q, [">= 0"]) 160 | s.add_runtime_dependency(%q, [">= 1.6.1"]) 161 | else 162 | s.add_dependency(%q, [">= 0"]) 163 | s.add_dependency(%q, [">= 0"]) 164 | s.add_dependency(%q, [">= 1.6.1"]) 165 | end 166 | else 167 | s.add_dependency(%q, [">= 0"]) 168 | s.add_dependency(%q, [">= 0"]) 169 | s.add_dependency(%q, [">= 1.6.1"]) 170 | end 171 | end 172 | 173 | -------------------------------------------------------------------------------- /app/helpers/apify_helper.rb: -------------------------------------------------------------------------------- 1 | module ApifyHelper 2 | 3 | def api_docs_artifact(action, artifact, nature) 4 | show_id = "#{nature}_#{artifact}_for_#{action.uid}" 5 | show_link = link_to_function('show', "document.getElementById('#{show_id}').style.display = 'block';") 6 | download_url = url_for(:action => action.name, artifact.to_sym => nature.to_s) 7 | download_tooltip = "#{action.method.to_s.upcase} #{download_url} to download" 8 | download_link = link_to('download', download_url, :method => action.method, :title => download_tooltip) 9 | json = JSON.pretty_generate(action.send(artifact, nature)) 10 | embedded = "" 11 | "#{artifact.to_s.humanize} (#{show_link}, #{download_link})#{embedded}".html_safe 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /app/views/apify/api/_actions.html.erb: -------------------------------------------------------------------------------- 1 |

Available actions

2 | 3 | <% api.actions.each do |action| %> 4 | 5 |
6 | 7 |

<%= action.method.to_s.upcase %> <%= action.name %>

8 | 9 | <% if action.description %> 10 |

<%= action.description.html_safe %>

11 | <% end %> 12 | 13 |
14 |
Arguments
15 |
16 | <% if action.takes_args? %> 17 | <%= api_docs_artifact(action, :schema, :args) %> 18 |
19 | <%= api_docs_artifact(action, :example, :args) %> 20 | <% else %> 21 | none 22 | <% end %> 23 |
24 |
Return value
25 |
26 | <% if action.returns_value? %> 27 | <%= api_docs_artifact(action, :schema, :value) %> 28 |
29 | <%= api_docs_artifact(action, :example, :value) %> 30 | <% else %> 31 | none 32 | <% end %> 33 |
34 |
35 | 36 |
37 | 38 | <% end %> 39 | -------------------------------------------------------------------------------- /app/views/apify/api/_client.html.erb: -------------------------------------------------------------------------------- 1 |

Using the Ruby client

2 | 3 |

4 | An easy way to use the API is to use the Apify::Client class. 5 | It takes care of the protocol details and lets you focus on exchanging data. 6 |

7 | 8 |

Installation

9 | 10 | First install the Apify gem: 11 | 12 |
sudo gem install apify
13 | 14 | If your client code is not a Rails application: 15 | 16 |
require 'rubygems'
17 | gem 'apify'
18 | require 'apify/client'
19 | 20 | In Rails 2, add the following to your environment.rb: 21 | 22 |
config.gem 'apify'
23 | 24 | In Rails 3, add the following to your Gemfile: 25 | 26 |
gem 'apify'
27 | 28 |

Usage

29 | 30 |
client = Apify::Client.new(:host => 'localhost:3000', :user => 'api', :password => 'secret')
31 | client.post('/api/hello', :name => 'Jack') # { 'message' => 'Hello Jack' }
32 | 33 |

34 | Errors can be caught and inspected like this: 35 |

36 | 37 |
begin
38 |   client.get('/api/hello', :name => 'Jack') # { 'message' => 'Hello Jack' }
39 | rescue Apify::RequestFailed => e
40 |   puts "Oh no! The API request failed."
41 |   puts "Message: #{e.message}"
42 |   puts "Response: #{e.response_body}"
43 | end
44 | 
45 | -------------------------------------------------------------------------------- /app/views/apify/api/_overview.html.erb: -------------------------------------------------------------------------------- 1 |

Overview

2 | 3 |
    4 |
  • The API works by exchanging messages encoded in JSON.
  • 5 |
  • There are no fancy message formats, it's all just sending arbitrary JSON objects back and forth.
  • 6 |
  • Building a lient cthat consumes the API is very straightforward. Also if you use Ruby, there is a helpful client class available.
  • 7 |
8 | 9 | -------------------------------------------------------------------------------- /app/views/apify/api/_protocol.html.erb: -------------------------------------------------------------------------------- 1 |

Protocol

2 | 3 |

Requests

4 | 5 |
    6 |
  • The API is accessed over HTTP.
  • 7 | <% if authentication_configured? %> 8 |
  • HTTP basic authentication is used.
  • 9 | <% end %> 10 |
  • Take care to use the correct HTTP method (GET, POST, PUT, DELETE) as documented for each action.
  • 11 |
  • Some (but not all) actions take arguments.
  • 12 |
  • Action arguments are serialized into a single HTTP parameter args as JSON.
  • 13 |
  • You can download an example for a request's arguments by appending ?example=args to the action URL.
  • 14 |
  • You can download a JSON schema for a valid request by appending ?schema=args to the action URL. Using the schema and a library like jasonschema you can validate your requests before sending them. The API will only process requests that conform to this schema.
  • 15 |
16 | 17 |

Responses

18 | 19 |
    20 |
  • Successful requests are returned with a status of 200 (OK).
  • 21 |
  • The body of a successful response is always a hash, serialized as JSON.
  • 22 |
  • Requests that have errors are returned with a status that is not 200. The body of a error response is an error message in the response's content type (won't be JSON in most cases).
  • 23 |
  • You can download an example for a successful response value by appending ?example=value to the action URL.
  • 24 |
  • You can download a JSON schema for a successful response by appending ?schema=value to the URL. The API guarantees that all responses conform to this schema.
  • 25 |
26 | 27 |

Example for a request/response exchange

28 | 29 |
    30 |
  1. 31 | API client sends to API host: POST http://api-host/api/hello?args=JSON 32 |
    33 | where JSON is a serialized JSON object: '{ "name": "Jack" }' 34 |
  2. 35 |
  3. 36 | API host returns a response with a status of 200 (OK) and a serialized JSON object as its body: '{ "message": "Hello Jack" }' 37 |
  4. 38 |
39 | -------------------------------------------------------------------------------- /app/views/apify/api/docs.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | API documentation 6 | 56 | 57 | 58 |
59 |

API documentation

60 | <%= render :partial => 'apify/api/overview' %> 61 | <%= render :partial => 'apify/api/protocol' %> 62 | <%= render :partial => 'apify/api/client' %> 63 | <%= render :partial => 'apify/api/actions' %> 64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /examples/client/client.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | gem 'apify' 3 | 4 | require 'apify/client' 5 | 6 | client = Apify::Client.new(:host => 'localhost', :port => 3000, :user => 'api', :password => 'secret') 7 | puts client.post('/api/hello', :name => 'Jack') 8 | -------------------------------------------------------------------------------- /examples/host/README: -------------------------------------------------------------------------------- 1 | == Welcome to Rails 2 | 3 | Rails is a web-application framework that includes everything needed to create 4 | database-backed web applications according to the Model-View-Control pattern. 5 | 6 | This pattern splits the view (also called the presentation) into "dumb" templates 7 | that are primarily responsible for inserting pre-built data in between HTML tags. 8 | The model contains the "smart" domain objects (such as Account, Product, Person, 9 | Post) that holds all the business logic and knows how to persist themselves to 10 | a database. The controller handles the incoming requests (such as Save New Account, 11 | Update Product, Show Post) by manipulating the model and directing data to the view. 12 | 13 | In Rails, the model is handled by what's called an object-relational mapping 14 | layer entitled Active Record. This layer allows you to present the data from 15 | database rows as objects and embellish these data objects with business logic 16 | methods. You can read more about Active Record in 17 | link:files/vendor/rails/activerecord/README.html. 18 | 19 | The controller and view are handled by the Action Pack, which handles both 20 | layers by its two parts: Action View and Action Controller. These two layers 21 | are bundled in a single package due to their heavy interdependence. This is 22 | unlike the relationship between the Active Record and Action Pack that is much 23 | more separate. Each of these packages can be used independently outside of 24 | Rails. You can read more about Action Pack in 25 | link:files/vendor/rails/actionpack/README.html. 26 | 27 | 28 | == Getting Started 29 | 30 | 1. At the command prompt, start a new Rails application using the rails command 31 | and your application name. Ex: rails myapp 32 | 2. Change directory into myapp and start the web server: script/server (run with --help for options) 33 | 3. Go to http://localhost:3000/ and get "Welcome aboard: You're riding the Rails!" 34 | 4. Follow the guidelines to start developing your application 35 | 36 | 37 | == Web Servers 38 | 39 | By default, Rails will try to use Mongrel if it's are installed when started with script/server, otherwise Rails will use WEBrick, the webserver that ships with Ruby. But you can also use Rails 40 | with a variety of other web servers. 41 | 42 | Mongrel is a Ruby-based webserver with a C component (which requires compilation) that is 43 | suitable for development and deployment of Rails applications. If you have Ruby Gems installed, 44 | getting up and running with mongrel is as easy as: gem install mongrel. 45 | More info at: http://mongrel.rubyforge.org 46 | 47 | Say other Ruby web servers like Thin and Ebb or regular web servers like Apache or LiteSpeed or 48 | Lighttpd or IIS. The Ruby web servers are run through Rack and the latter can either be setup to use 49 | FCGI or proxy to a pack of Mongrels/Thin/Ebb servers. 50 | 51 | == Apache .htaccess example for FCGI/CGI 52 | 53 | # General Apache options 54 | AddHandler fastcgi-script .fcgi 55 | AddHandler cgi-script .cgi 56 | Options +FollowSymLinks +ExecCGI 57 | 58 | # If you don't want Rails to look in certain directories, 59 | # use the following rewrite rules so that Apache won't rewrite certain requests 60 | # 61 | # Example: 62 | # RewriteCond %{REQUEST_URI} ^/notrails.* 63 | # RewriteRule .* - [L] 64 | 65 | # Redirect all requests not available on the filesystem to Rails 66 | # By default the cgi dispatcher is used which is very slow 67 | # 68 | # For better performance replace the dispatcher with the fastcgi one 69 | # 70 | # Example: 71 | # RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] 72 | RewriteEngine On 73 | 74 | # If your Rails application is accessed via an Alias directive, 75 | # then you MUST also set the RewriteBase in this htaccess file. 76 | # 77 | # Example: 78 | # Alias /myrailsapp /path/to/myrailsapp/public 79 | # RewriteBase /myrailsapp 80 | 81 | RewriteRule ^$ index.html [QSA] 82 | RewriteRule ^([^.]+)$ $1.html [QSA] 83 | RewriteCond %{REQUEST_FILENAME} !-f 84 | RewriteRule ^(.*)$ dispatch.cgi [QSA,L] 85 | 86 | # In case Rails experiences terminal errors 87 | # Instead of displaying this message you can supply a file here which will be rendered instead 88 | # 89 | # Example: 90 | # ErrorDocument 500 /500.html 91 | 92 | ErrorDocument 500 "

Application error

Rails application failed to start properly" 93 | 94 | 95 | == Debugging Rails 96 | 97 | Sometimes your application goes wrong. Fortunately there are a lot of tools that 98 | will help you debug it and get it back on the rails. 99 | 100 | First area to check is the application log files. Have "tail -f" commands running 101 | on the server.log and development.log. Rails will automatically display debugging 102 | and runtime information to these files. Debugging info will also be shown in the 103 | browser on requests from 127.0.0.1. 104 | 105 | You can also log your own messages directly into the log file from your code using 106 | the Ruby logger class from inside your controllers. Example: 107 | 108 | class WeblogController < ActionController::Base 109 | def destroy 110 | @weblog = Weblog.find(params[:id]) 111 | @weblog.destroy 112 | logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") 113 | end 114 | end 115 | 116 | The result will be a message in your log file along the lines of: 117 | 118 | Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1 119 | 120 | More information on how to use the logger is at http://www.ruby-doc.org/core/ 121 | 122 | Also, Ruby documentation can be found at http://www.ruby-lang.org/ including: 123 | 124 | * The Learning Ruby (Pickaxe) Book: http://www.ruby-doc.org/docs/ProgrammingRuby/ 125 | * Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) 126 | 127 | These two online (and free) books will bring you up to speed on the Ruby language 128 | and also on programming in general. 129 | 130 | 131 | == Debugger 132 | 133 | Debugger support is available through the debugger command when you start your Mongrel or 134 | Webrick server with --debugger. This means that you can break out of execution at any point 135 | in the code, investigate and change the model, AND then resume execution! 136 | You need to install ruby-debug to run the server in debugging mode. With gems, use 'gem install ruby-debug' 137 | Example: 138 | 139 | class WeblogController < ActionController::Base 140 | def index 141 | @posts = Post.find(:all) 142 | debugger 143 | end 144 | end 145 | 146 | So the controller will accept the action, run the first line, then present you 147 | with a IRB prompt in the server window. Here you can do things like: 148 | 149 | >> @posts.inspect 150 | => "[#nil, \"body\"=>nil, \"id\"=>\"1\"}>, 151 | #\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]" 152 | >> @posts.first.title = "hello from a debugger" 153 | => "hello from a debugger" 154 | 155 | ...and even better is that you can examine how your runtime objects actually work: 156 | 157 | >> f = @posts.first 158 | => #nil, "body"=>nil, "id"=>"1"}> 159 | >> f. 160 | Display all 152 possibilities? (y or n) 161 | 162 | Finally, when you're ready to resume execution, you enter "cont" 163 | 164 | 165 | == Console 166 | 167 | You can interact with the domain model by starting the console through script/console. 168 | Here you'll have all parts of the application configured, just like it is when the 169 | application is running. You can inspect domain models, change values, and save to the 170 | database. Starting the script without arguments will launch it in the development environment. 171 | Passing an argument will specify a different environment, like script/console production. 172 | 173 | To reload your controllers and models after launching the console run reload! 174 | 175 | == dbconsole 176 | 177 | You can go to the command line of your database directly through script/dbconsole. 178 | You would be connected to the database with the credentials defined in database.yml. 179 | Starting the script without arguments will connect you to the development database. Passing an 180 | argument will connect you to a different database, like script/dbconsole production. 181 | Currently works for mysql, postgresql and sqlite. 182 | 183 | == Description of Contents 184 | 185 | app 186 | Holds all the code that's specific to this particular application. 187 | 188 | app/controllers 189 | Holds controllers that should be named like weblogs_controller.rb for 190 | automated URL mapping. All controllers should descend from ApplicationController 191 | which itself descends from ActionController::Base. 192 | 193 | app/models 194 | Holds models that should be named like post.rb. 195 | Most models will descend from ActiveRecord::Base. 196 | 197 | app/views 198 | Holds the template files for the view that should be named like 199 | weblogs/index.html.erb for the WeblogsController#index action. All views use eRuby 200 | syntax. 201 | 202 | app/views/layouts 203 | Holds the template files for layouts to be used with views. This models the common 204 | header/footer method of wrapping views. In your views, define a layout using the 205 | layout :default and create a file named default.html.erb. Inside default.html.erb, 206 | call <% yield %> to render the view using this layout. 207 | 208 | app/helpers 209 | Holds view helpers that should be named like weblogs_helper.rb. These are generated 210 | for you automatically when using script/generate for controllers. Helpers can be used to 211 | wrap functionality for your views into methods. 212 | 213 | config 214 | Configuration files for the Rails environment, the routing map, the database, and other dependencies. 215 | 216 | db 217 | Contains the database schema in schema.rb. db/migrate contains all 218 | the sequence of Migrations for your schema. 219 | 220 | doc 221 | This directory is where your application documentation will be stored when generated 222 | using rake doc:app 223 | 224 | lib 225 | Application specific libraries. Basically, any kind of custom code that doesn't 226 | belong under controllers, models, or helpers. This directory is in the load path. 227 | 228 | public 229 | The directory available for the web server. Contains subdirectories for images, stylesheets, 230 | and javascripts. Also contains the dispatchers and the default HTML files. This should be 231 | set as the DOCUMENT_ROOT of your web server. 232 | 233 | script 234 | Helper scripts for automation and generation. 235 | 236 | test 237 | Unit and functional tests along with fixtures. When using the script/generate scripts, template 238 | test files will be generated for you and placed in this directory. 239 | 240 | vendor 241 | External libraries that the application depends on. Also includes the plugins subdirectory. 242 | If the app has frozen rails, those gems also go here, under vendor/rails/. 243 | This directory is in the load path. 244 | -------------------------------------------------------------------------------- /examples/host/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require(File.join(File.dirname(__FILE__), 'config', 'boot')) 5 | 6 | require 'rake' 7 | require 'rake/testtask' 8 | require 'rake/rdoctask' 9 | 10 | require 'tasks/rails' 11 | -------------------------------------------------------------------------------- /examples/host/app/controllers/api_controller.rb: -------------------------------------------------------------------------------- 1 | class ApiController < Apify::ApiController 2 | 3 | authenticate :user => 'api', :password => 'secret' 4 | 5 | end 6 | -------------------------------------------------------------------------------- /examples/host/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | # Filters added to this controller apply to all controllers in the application. 2 | # Likewise, all the methods added will be available for all controllers. 3 | 4 | class ApplicationController < ActionController::Base 5 | helper :all # include all helpers, all the time 6 | protect_from_forgery # See ActionController::RequestForgeryProtection for details 7 | 8 | # Scrub sensitive parameters from your log 9 | # filter_parameter_logging :password 10 | end 11 | -------------------------------------------------------------------------------- /examples/host/app/helpers/api_helper.rb: -------------------------------------------------------------------------------- 1 | module ApiHelper 2 | end 3 | -------------------------------------------------------------------------------- /examples/host/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | # Methods added to this helper will be available to all templates in the application. 2 | module ApplicationHelper 3 | end 4 | -------------------------------------------------------------------------------- /examples/host/app/models/api.rb: -------------------------------------------------------------------------------- 1 | class Api < Apify::Api 2 | 3 | post :hello do 4 | 5 | description 'Greets the given name.' 6 | 7 | schema :args do 8 | object('name' => string) 9 | end 10 | 11 | schema :value do 12 | object('message' => string) 13 | end 14 | 15 | respond do 16 | { 'message' => "Hello, #{args['name']}"} 17 | end 18 | 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /examples/host/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Don't change this file! 2 | # Configure your app in config/environment.rb and config/environments/*.rb 3 | 4 | RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT) 5 | 6 | module Rails 7 | class << self 8 | def boot! 9 | unless booted? 10 | preinitialize 11 | pick_boot.run 12 | end 13 | end 14 | 15 | def booted? 16 | defined? Rails::Initializer 17 | end 18 | 19 | def pick_boot 20 | (vendor_rails? ? VendorBoot : GemBoot).new 21 | end 22 | 23 | def vendor_rails? 24 | File.exist?("#{RAILS_ROOT}/vendor/rails") 25 | end 26 | 27 | def preinitialize 28 | load(preinitializer_path) if File.exist?(preinitializer_path) 29 | end 30 | 31 | def preinitializer_path 32 | "#{RAILS_ROOT}/config/preinitializer.rb" 33 | end 34 | end 35 | 36 | class Boot 37 | def run 38 | load_initializer 39 | Rails::Initializer.run(:set_load_path) 40 | end 41 | end 42 | 43 | class VendorBoot < Boot 44 | def load_initializer 45 | require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" 46 | Rails::Initializer.run(:install_gem_spec_stubs) 47 | Rails::GemDependency.add_frozen_gem_path 48 | end 49 | end 50 | 51 | class GemBoot < Boot 52 | def load_initializer 53 | self.class.load_rubygems 54 | load_rails_gem 55 | require 'initializer' 56 | end 57 | 58 | def load_rails_gem 59 | if version = self.class.gem_version 60 | gem 'rails', version 61 | else 62 | gem 'rails' 63 | end 64 | rescue Gem::LoadError => load_error 65 | $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.) 66 | exit 1 67 | end 68 | 69 | class << self 70 | def rubygems_version 71 | Gem::RubyGemsVersion rescue nil 72 | end 73 | 74 | def gem_version 75 | if defined? RAILS_GEM_VERSION 76 | RAILS_GEM_VERSION 77 | elsif ENV.include?('RAILS_GEM_VERSION') 78 | ENV['RAILS_GEM_VERSION'] 79 | else 80 | parse_gem_version(read_environment_rb) 81 | end 82 | end 83 | 84 | def load_rubygems 85 | min_version = '1.3.2' 86 | require 'rubygems' 87 | unless rubygems_version >= min_version 88 | $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.) 89 | exit 1 90 | end 91 | 92 | rescue LoadError 93 | $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org) 94 | exit 1 95 | end 96 | 97 | def parse_gem_version(text) 98 | $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/ 99 | end 100 | 101 | private 102 | def read_environment_rb 103 | File.read("#{RAILS_ROOT}/config/environment.rb") 104 | end 105 | end 106 | end 107 | end 108 | 109 | # All that for this: 110 | Rails.boot! 111 | -------------------------------------------------------------------------------- /examples/host/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3-ruby (not necessary on OS X Leopard) 3 | development: 4 | adapter: sqlite3 5 | database: db/development.sqlite3 6 | pool: 5 7 | timeout: 5000 8 | 9 | # Warning: The database defined as "test" will be erased and 10 | # re-generated from your development database when you run "rake". 11 | # Do not set this db to the same as development or production. 12 | test: 13 | adapter: sqlite3 14 | database: db/test.sqlite3 15 | pool: 5 16 | timeout: 5000 17 | 18 | production: 19 | adapter: sqlite3 20 | database: db/production.sqlite3 21 | pool: 5 22 | timeout: 5000 23 | -------------------------------------------------------------------------------- /examples/host/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file 2 | 3 | # Specifies gem version of Rails to use when vendor/rails is not present 4 | RAILS_GEM_VERSION = '2.3.8' unless defined? RAILS_GEM_VERSION 5 | 6 | # Bootstrap the Rails environment, frameworks, and default configuration 7 | require File.join(File.dirname(__FILE__), 'boot') 8 | 9 | Rails::Initializer.run do |config| 10 | # Settings in config/environments/* take precedence over those specified here. 11 | # Application configuration should go into files in config/initializers 12 | # -- all .rb files in that directory are automatically loaded. 13 | 14 | # Add additional load paths for your own custom dirs 15 | # config.load_paths += %W( #{RAILS_ROOT}/extras ) 16 | 17 | # Specify gems that this application depends on and have them installed with rake gems:install 18 | # config.gem "bj" 19 | # config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net" 20 | # config.gem "sqlite3-ruby", :lib => "sqlite3" 21 | # config.gem "aws-s3", :lib => "aws/s3" 22 | 23 | # Only load the plugins named here, in the order given (default is alphabetical). 24 | # :all can be used as a placeholder for all plugins not explicitly named 25 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 26 | 27 | # Skip frameworks you're not going to use. To use Rails without a database, 28 | # you must remove the Active Record framework. 29 | # config.frameworks -= [ :active_record, :active_resource, :action_mailer ] 30 | 31 | # Activate observers that should always be running 32 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 33 | 34 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 35 | # Run "rake -D time" for a list of tasks for finding time zone names. 36 | config.time_zone = 'UTC' 37 | config.gem 'apify' 38 | 39 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 40 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')] 41 | # config.i18n.default_locale = :de 42 | end -------------------------------------------------------------------------------- /examples/host/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | # Settings specified here will take precedence over those in config/environment.rb 2 | 3 | # In the development environment your application's code is reloaded on 4 | # every request. This slows down response time but is perfect for development 5 | # since you don't have to restart the webserver when you make code changes. 6 | config.cache_classes = false 7 | 8 | # Log error messages when you accidentally call methods on nil. 9 | config.whiny_nils = true 10 | 11 | # Show full error reports and disable caching 12 | config.action_controller.consider_all_requests_local = true 13 | config.action_view.debug_rjs = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send 17 | config.action_mailer.raise_delivery_errors = false -------------------------------------------------------------------------------- /examples/host/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | # Settings specified here will take precedence over those in config/environment.rb 2 | 3 | # The production environment is meant for finished, "live" apps. 4 | # Code is not reloaded between requests 5 | config.cache_classes = true 6 | 7 | # Full error reports are disabled and caching is turned on 8 | config.action_controller.consider_all_requests_local = false 9 | config.action_controller.perform_caching = true 10 | config.action_view.cache_template_loading = true 11 | 12 | # See everything in the log (default is :info) 13 | # config.log_level = :debug 14 | 15 | # Use a different logger for distributed setups 16 | # config.logger = SyslogLogger.new 17 | 18 | # Use a different cache store in production 19 | # config.cache_store = :mem_cache_store 20 | 21 | # Enable serving of images, stylesheets, and javascripts from an asset server 22 | # config.action_controller.asset_host = "http://assets.example.com" 23 | 24 | # Disable delivery errors, bad email addresses will be ignored 25 | # config.action_mailer.raise_delivery_errors = false 26 | 27 | # Enable threaded mode 28 | # config.threadsafe! -------------------------------------------------------------------------------- /examples/host/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # Settings specified here will take precedence over those in config/environment.rb 2 | 3 | # The test environment is used exclusively to run your application's 4 | # test suite. You never need to work with it otherwise. Remember that 5 | # your test database is "scratch space" for the test suite and is wiped 6 | # and recreated between test runs. Don't rely on the data there! 7 | config.cache_classes = true 8 | 9 | # Log error messages when you accidentally call methods on nil. 10 | config.whiny_nils = true 11 | 12 | # Show full error reports and disable caching 13 | config.action_controller.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | config.action_view.cache_template_loading = true 16 | 17 | # Disable request forgery protection in test environment 18 | config.action_controller.allow_forgery_protection = false 19 | 20 | # Tell Action Mailer not to deliver emails to the real world. 21 | # The :test delivery method accumulates sent emails in the 22 | # ActionMailer::Base.deliveries array. 23 | config.action_mailer.delivery_method = :test 24 | 25 | # Use SQL instead of Active Record's schema dumper when creating the test database. 26 | # This is necessary if your schema can't be completely dumped by the schema dumper, 27 | # like if you have constraints or database-specific column types 28 | # config.active_record.schema_format = :sql -------------------------------------------------------------------------------- /examples/host/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying do debug a problem that might steem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! -------------------------------------------------------------------------------- /examples/host/config/initializers/cookie_verification_secret.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | ActionController::Base.cookie_verifier_secret = '7705dd1c649fa8754fbfa473f251976cbb2021a359e94f9e4a5cbd39c1fa6fdff61fd85e501de515760da8245a2fcce8b1b682aadbe5edd6290107ac3aa6796d'; 8 | -------------------------------------------------------------------------------- /examples/host/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | -------------------------------------------------------------------------------- /examples/host/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /examples/host/config/initializers/new_rails_defaults.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # These settings change the behavior of Rails 2 apps and will be defaults 4 | # for Rails 3. You can remove this initializer when Rails 3 is released. 5 | 6 | if defined?(ActiveRecord) 7 | # Include Active Record class name as root for JSON serialized output. 8 | ActiveRecord::Base.include_root_in_json = true 9 | 10 | # Store the full class name (including module namespace) in STI type column. 11 | ActiveRecord::Base.store_full_sti_class = true 12 | end 13 | 14 | ActionController::Routing.generate_best_match = false 15 | 16 | # Use ISO 8601 format for JSON serialized times and dates. 17 | ActiveSupport.use_standard_json_time_format = true 18 | 19 | # Don't escape HTML entities in JSON, leave that for the #json_escape helper. 20 | # if you're including raw json in an HTML page. 21 | ActiveSupport.escape_html_entities_in_json = false -------------------------------------------------------------------------------- /examples/host/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying cookie session data integrity. 4 | # If you change this key, all old sessions will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | ActionController::Base.session = { 8 | :key => '_apify-test_session', 9 | :secret => 'bddecc0f5cf89605f636b60d93fc7afe99a1c98025b0349dd98e75728fcda44eea74b349d86af242509cccfd09481fc4f145eb100460c60678feb2a74444f131' 10 | } 11 | 12 | # Use the database for sessions instead of the cookie-based default, 13 | # which shouldn't be used to store highly confidential information 14 | # (create the session table with "rake db:sessions:create") 15 | # ActionController::Base.session_store = :active_record_store 16 | -------------------------------------------------------------------------------- /examples/host/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" -------------------------------------------------------------------------------- /examples/host/config/routes.rb: -------------------------------------------------------------------------------- 1 | ActionController::Routing::Routes.draw do |map| 2 | Api.draw_routes(map) 3 | end 4 | -------------------------------------------------------------------------------- /examples/host/db/development.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makandra/apify/76bf424f72a223fa93f230529a4a4c9a251a3a67/examples/host/db/development.sqlite3 -------------------------------------------------------------------------------- /examples/host/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }]) 7 | # Major.create(:name => 'Daley', :city => cities.first) 8 | -------------------------------------------------------------------------------- /examples/host/log/development.log: -------------------------------------------------------------------------------- 1 | 2 | 3 | Processing ApplicationController#index (for 127.0.0.1 at 2010-08-28 14:35:50) [GET] 4 | 5 | ActionController::RoutingError (No route matches "/api/docs" with {:method=>:get}): 6 | /usr/lib/ruby/1.8/mongrel.rb:159:in `process_client' 7 | /usr/lib/ruby/1.8/mongrel.rb:158:in `each' 8 | /usr/lib/ruby/1.8/mongrel.rb:158:in `process_client' 9 | /usr/lib/ruby/1.8/mongrel.rb:285:in `run' 10 | /usr/lib/ruby/1.8/mongrel.rb:285:in `initialize' 11 | /usr/lib/ruby/1.8/mongrel.rb:285:in `new' 12 | /usr/lib/ruby/1.8/mongrel.rb:285:in `run' 13 | /usr/lib/ruby/1.8/mongrel.rb:268:in `initialize' 14 | /usr/lib/ruby/1.8/mongrel.rb:268:in `new' 15 | /usr/lib/ruby/1.8/mongrel.rb:268:in `run' 16 | -e:1:in `load' 17 | -e:1 18 | 19 | Rendering rescues/layout (not_found) 20 | 21 | 22 | Processing ApplicationController#docs (for 127.0.0.1 at 2010-08-28 14:36:02) [GET] 23 | 24 | NameError (uninitialized constant ApiController): 25 | /usr/lib/ruby/1.8/mongrel.rb:159:in `process_client' 26 | /usr/lib/ruby/1.8/mongrel.rb:158:in `each' 27 | /usr/lib/ruby/1.8/mongrel.rb:158:in `process_client' 28 | /usr/lib/ruby/1.8/mongrel.rb:285:in `run' 29 | /usr/lib/ruby/1.8/mongrel.rb:285:in `initialize' 30 | /usr/lib/ruby/1.8/mongrel.rb:285:in `new' 31 | /usr/lib/ruby/1.8/mongrel.rb:285:in `run' 32 | /usr/lib/ruby/1.8/mongrel.rb:268:in `initialize' 33 | /usr/lib/ruby/1.8/mongrel.rb:268:in `new' 34 | /usr/lib/ruby/1.8/mongrel.rb:268:in `run' 35 | -e:1:in `load' 36 | -e:1 37 | 38 | Rendered rescues/_trace (27.3ms) 39 | Rendered rescues/_request_and_response (1.2ms) 40 | Rendering rescues/layout (internal_server_error) 41 | 42 | 43 | Processing ApiController#docs (for 127.0.0.1 at 2010-08-28 14:36:30) [GET] 44 | Rendering apify/api/docs 45 | Rendered apify/api/_overview (0.5ms) 46 | Rendered apify/api/_protocol (1.1ms) 47 | Rendered apify/api/_client (0.7ms) 48 | Rendered apify/api/_actions (18.1ms) 49 | Completed in 44ms (View: 42, DB: 0) | 200 OK [http://localhost/api/docs] 50 | 51 | 52 | Processing ApiController#docs (for 127.0.0.1 at 2010-08-28 14:36:50) [GET] 53 | Rendering apify/api/docs 54 | Rendered apify/api/_overview (0.1ms) 55 | Rendered apify/api/_protocol (0.1ms) 56 | Rendered apify/api/_client (0.1ms) 57 | Rendered apify/api/_actions (12.4ms) 58 | Completed in 16ms (View: 14, DB: 0) | 200 OK [http://localhost/api/docs] 59 | 60 | 61 | Processing ApplicationController#index (for 127.0.0.1 at 2010-08-28 14:36:55) [GET] 62 | 63 | ActionController::MethodNotAllowed (Only post requests are allowed.): 64 | /usr/lib/ruby/1.8/mongrel.rb:159:in `process_client' 65 | /usr/lib/ruby/1.8/mongrel.rb:158:in `each' 66 | /usr/lib/ruby/1.8/mongrel.rb:158:in `process_client' 67 | /usr/lib/ruby/1.8/mongrel.rb:285:in `run' 68 | /usr/lib/ruby/1.8/mongrel.rb:285:in `initialize' 69 | /usr/lib/ruby/1.8/mongrel.rb:285:in `new' 70 | /usr/lib/ruby/1.8/mongrel.rb:285:in `run' 71 | /usr/lib/ruby/1.8/mongrel.rb:268:in `initialize' 72 | /usr/lib/ruby/1.8/mongrel.rb:268:in `new' 73 | /usr/lib/ruby/1.8/mongrel.rb:268:in `run' 74 | -e:1:in `load' 75 | -e:1 76 | 77 | Rendered rescues/_trace (22.7ms) 78 | Rendered rescues/_request_and_response (0.3ms) 79 | Rendering rescues/layout (method_not_allowed) 80 | 81 | 82 | Processing ApplicationController#index (for 127.0.0.1 at 2010-08-28 14:42:54) [GET] 83 | 84 | ActionController::RoutingError (No route matches "/" with {:method=>:get}): 85 | /usr/lib/ruby/1.8/mongrel.rb:159:in `process_client' 86 | /usr/lib/ruby/1.8/mongrel.rb:158:in `each' 87 | /usr/lib/ruby/1.8/mongrel.rb:158:in `process_client' 88 | /usr/lib/ruby/1.8/mongrel.rb:285:in `run' 89 | /usr/lib/ruby/1.8/mongrel.rb:285:in `initialize' 90 | /usr/lib/ruby/1.8/mongrel.rb:285:in `new' 91 | /usr/lib/ruby/1.8/mongrel.rb:285:in `run' 92 | /usr/lib/ruby/1.8/mongrel.rb:268:in `initialize' 93 | /usr/lib/ruby/1.8/mongrel.rb:268:in `new' 94 | /usr/lib/ruby/1.8/mongrel.rb:268:in `run' 95 | -e:1:in `load' 96 | -e:1 97 | 98 | Rendering rescues/layout (not_found) 99 | 100 | 101 | Processing ApiController#docs (for 127.0.0.1 at 2010-08-28 14:42:58) [GET] 102 | Filter chain halted as [:require_api_authentication] rendered_or_redirected. 103 | Completed in 1ms (View: 1, DB: 0) | 401 Unauthorized [http://localhost/api/docs] 104 | 105 | 106 | Processing ApiController#docs (for 127.0.0.1 at 2010-08-28 14:43:01) [GET] 107 | Filter chain halted as [:require_api_authentication] rendered_or_redirected. 108 | Completed in 1ms (View: 1, DB: 0) | 401 Unauthorized [http://localhost/api/docs] 109 | 110 | 111 | Processing ApiController#docs (for 127.0.0.1 at 2010-08-28 14:43:06) [GET] 112 | Rendering apify/api/docs 113 | Rendered apify/api/_overview (0.5ms) 114 | Rendered apify/api/_protocol (1.2ms) 115 | Rendered apify/api/_client (0.8ms) 116 | Rendered apify/api/_actions (16.1ms) 117 | Completed in 92ms (View: 90, DB: 0) | 200 OK [http://localhost/api/docs] 118 | 119 | 120 | Processing ApiController#hello (for 127.0.0.1 at 2010-08-28 15:10:04) [POST] 121 | Parameters: {"args"=>"{\"name\":\"Jack\"}"} 122 | Filter chain halted as [:require_api_authentication] rendered_or_redirected. 123 | Completed in 1ms (View: 1, DB: 0) | 401 Unauthorized [http://localhost/api/hello] 124 | 125 | 126 | Processing ApiController#hello (for 127.0.0.1 at 2010-08-28 15:11:31) [POST] 127 | Parameters: {"args"=>"{\"name\":\"Jack\"}"} 128 | Filter chain halted as [:require_api_authentication] rendered_or_redirected. 129 | Completed in 1ms (View: 1, DB: 0) | 401 Unauthorized [http://localhost/api/hello] 130 | 131 | 132 | Processing ApiController#hello (for 127.0.0.1 at 2010-08-28 15:11:55) [POST] 133 | Parameters: {"args"=>"{\"name\":\"Jack\"}"} 134 | 135 | ActionController::UnknownAction (No action responded to hello. Actions: api, authentication_configured?, and docs): 136 | /usr/lib/ruby/1.8/mongrel.rb:159:in `process_client' 137 | /usr/lib/ruby/1.8/mongrel.rb:158:in `each' 138 | /usr/lib/ruby/1.8/mongrel.rb:158:in `process_client' 139 | /usr/lib/ruby/1.8/mongrel.rb:285:in `run' 140 | /usr/lib/ruby/1.8/mongrel.rb:285:in `initialize' 141 | /usr/lib/ruby/1.8/mongrel.rb:285:in `new' 142 | /usr/lib/ruby/1.8/mongrel.rb:285:in `run' 143 | /usr/lib/ruby/1.8/mongrel.rb:268:in `initialize' 144 | /usr/lib/ruby/1.8/mongrel.rb:268:in `new' 145 | /usr/lib/ruby/1.8/mongrel.rb:268:in `run' 146 | -e:1:in `load' 147 | -e:1 148 | 149 | Rendering rescues/layout (not_found) 150 | 151 | 152 | Processing ApiController#hello (for 127.0.0.1 at 2010-08-28 15:13:09) [POST] 153 | Parameters: {"args"=>"{\"name\":\"Jack\"}"} 154 | Sending data hello.json 155 | Completed in 5ms (View: 1, DB: 0) | 200 [http://localhost/api/hello] 156 | 157 | 158 | Processing ApiController#hello (for 127.0.0.1 at 2010-08-28 15:13:17) [POST] 159 | Parameters: {"args"=>"{\"name\":\"Jack\"}"} 160 | 161 | ActionController::UnknownAction (No action responded to hello. Actions: api, authentication_configured?, and docs): 162 | /usr/lib/ruby/1.8/mongrel.rb:159:in `process_client' 163 | /usr/lib/ruby/1.8/mongrel.rb:158:in `each' 164 | /usr/lib/ruby/1.8/mongrel.rb:158:in `process_client' 165 | /usr/lib/ruby/1.8/mongrel.rb:285:in `run' 166 | /usr/lib/ruby/1.8/mongrel.rb:285:in `initialize' 167 | /usr/lib/ruby/1.8/mongrel.rb:285:in `new' 168 | /usr/lib/ruby/1.8/mongrel.rb:285:in `run' 169 | /usr/lib/ruby/1.8/mongrel.rb:268:in `initialize' 170 | /usr/lib/ruby/1.8/mongrel.rb:268:in `new' 171 | /usr/lib/ruby/1.8/mongrel.rb:268:in `run' 172 | -e:1:in `load' 173 | -e:1 174 | 175 | Rendering rescues/layout (not_found) 176 | -------------------------------------------------------------------------------- /examples/host/log/production.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makandra/apify/76bf424f72a223fa93f230529a4a4c9a251a3a67/examples/host/log/production.log -------------------------------------------------------------------------------- /examples/host/log/server.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makandra/apify/76bf424f72a223fa93f230529a4a4c9a251a3a67/examples/host/log/server.log -------------------------------------------------------------------------------- /examples/host/log/test.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makandra/apify/76bf424f72a223fa93f230529a4a4c9a251a3a67/examples/host/log/test.log -------------------------------------------------------------------------------- /examples/host/public/404.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | The page you were looking for doesn't exist (404) 9 | 21 | 22 | 23 | 24 | 25 |
26 |

The page you were looking for doesn't exist.

27 |

You may have mistyped the address or the page may have moved.

28 |
29 | 30 | -------------------------------------------------------------------------------- /examples/host/public/422.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | The change you wanted was rejected (422) 9 | 21 | 22 | 23 | 24 | 25 |
26 |

The change you wanted was rejected.

27 |

Maybe you tried to change something you didn't have access to.

28 |
29 | 30 | -------------------------------------------------------------------------------- /examples/host/public/500.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | We're sorry, but something went wrong (500) 9 | 21 | 22 | 23 | 24 | 25 |
26 |

We're sorry, but something went wrong.

27 |

We've been notified about this issue and we'll take a look at it shortly.

28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /examples/host/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makandra/apify/76bf424f72a223fa93f230529a4a4c9a251a3a67/examples/host/public/favicon.ico -------------------------------------------------------------------------------- /examples/host/public/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // Place your application-specific JavaScript functions and classes here 2 | // This file is automatically included by javascript_include_tag :defaults 3 | -------------------------------------------------------------------------------- /examples/host/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /examples/host/script/about: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../config/boot', __FILE__) 3 | $LOAD_PATH.unshift "#{RAILTIES_PATH}/builtin/rails_info" 4 | require 'commands/about' 5 | -------------------------------------------------------------------------------- /examples/host/script/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../config/boot', __FILE__) 3 | require 'commands/console' 4 | -------------------------------------------------------------------------------- /examples/host/script/dbconsole: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../config/boot', __FILE__) 3 | require 'commands/dbconsole' 4 | -------------------------------------------------------------------------------- /examples/host/script/destroy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../config/boot', __FILE__) 3 | require 'commands/destroy' 4 | -------------------------------------------------------------------------------- /examples/host/script/generate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../config/boot', __FILE__) 3 | require 'commands/generate' 4 | -------------------------------------------------------------------------------- /examples/host/script/performance/benchmarker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../../config/boot', __FILE__) 3 | require 'commands/performance/benchmarker' 4 | -------------------------------------------------------------------------------- /examples/host/script/performance/profiler: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../../config/boot', __FILE__) 3 | require 'commands/performance/profiler' 4 | -------------------------------------------------------------------------------- /examples/host/script/plugin: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../config/boot', __FILE__) 3 | require 'commands/plugin' 4 | -------------------------------------------------------------------------------- /examples/host/script/runner: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../config/boot', __FILE__) 3 | require 'commands/runner' 4 | -------------------------------------------------------------------------------- /examples/host/script/server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../config/boot', __FILE__) 3 | require 'commands/server' 4 | -------------------------------------------------------------------------------- /lib/apify.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'jsonschema' 3 | 4 | require 'apify/errors' 5 | require 'apify/patterns' 6 | require 'apify/action' 7 | require 'apify/exchange' 8 | require 'apify/api' 9 | require 'apify/schema_helper' 10 | require 'apify/api_controller' 11 | require 'apify/client' 12 | -------------------------------------------------------------------------------- /lib/apify/action.rb: -------------------------------------------------------------------------------- 1 | module Apify 2 | class Action 3 | 4 | attr_reader :name, :description, :method 5 | 6 | def initialize(method, name, &block) 7 | @method = method 8 | @name = name 9 | @schemas = Hash.new({}.freeze) 10 | instance_eval(&block) 11 | end 12 | 13 | def schema(nature, &block) 14 | nature = nature.to_sym 15 | if block 16 | @schemas[nature] = eval_schema(block) 17 | else 18 | @schemas[nature] 19 | end 20 | end 21 | 22 | def respond(args = nil, &block) 23 | if block 24 | @responder = block 25 | else 26 | Apify::Exchange.new.respond(args, self) 27 | end 28 | end 29 | 30 | def description(description = nil, &block) 31 | if description || block 32 | @description = description ? description : block.call 33 | else 34 | @description 35 | end 36 | end 37 | 38 | def takes_args? 39 | schema(:args) != {} 40 | end 41 | 42 | def returns_value? 43 | schema(:value) != {} 44 | end 45 | 46 | def responder 47 | @responder || lambda {} 48 | end 49 | 50 | def example(schema_nature) 51 | example_for_schema(schema(schema_nature)) 52 | end 53 | 54 | def uid 55 | "#{method}_#{name}" 56 | end 57 | 58 | private 59 | 60 | def example_for_schema(schema) 61 | case schema['type'] 62 | when 'object' 63 | {}.tap do |object| 64 | if properties = schema['properties'] 65 | properties.each do |key, value| 66 | object[key] = example_for_schema(value) 67 | end 68 | end 69 | end 70 | when 'array' 71 | [].tap do |array| 72 | if items = schema['items'] 73 | array.concat [example_for_schema(items)] * 2 74 | end 75 | end 76 | when 'boolean' 77 | true 78 | when 'number' 79 | 2.5 80 | when 'integer' 81 | 123 82 | when 'string' 83 | 'string' 84 | else 85 | raise "Unknown schema type: #{schema['type']}" 86 | end 87 | end 88 | 89 | def eval_schema(schema) 90 | lathe = Object.new 91 | lathe.extend Apify::SchemaHelper 92 | lathe.instance_eval(&schema) 93 | end 94 | 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /lib/apify/api.rb: -------------------------------------------------------------------------------- 1 | module Apify 2 | class Api 3 | class << self 4 | 5 | def action(method, name, args = {}, &block) 6 | method = method.to_sym 7 | name = name.to_sym 8 | if block 9 | action = Apify::Action.new(method, name, &block) 10 | indexed_actions[name][method] = action 11 | actions << action 12 | else 13 | action = indexed_actions[name][method] or raise "Unknown API action: #{name}" 14 | action.respond(args) 15 | end 16 | end 17 | 18 | def get(*args, &block) 19 | action(:get, *args, &block) 20 | end 21 | 22 | def post(*args, &block) 23 | action(:post, *args, &block) 24 | end 25 | 26 | def put(*args, &block) 27 | action(:put, *args, &block) 28 | end 29 | 30 | def delete(*args, &block) 31 | action(:delete, *args, &block) 32 | end 33 | 34 | def actions 35 | @actions ||= [] 36 | end 37 | 38 | def indexed_actions 39 | @indexed_actions ||= Hash.new { |hash, k| hash[k] = {} } 40 | end 41 | 42 | def draw_routes(map, options = {}) 43 | options[:base_path] ||= 'api' 44 | options[:controller] ||= 'api' 45 | indexed_actions.each do |name, methods| 46 | methods.each do |method, action| 47 | connect_route(map, name, method, options) 48 | end 49 | end 50 | connect_route(map, 'docs', :get, options) 51 | end 52 | 53 | private 54 | 55 | def connect_route(map, name, method, options) 56 | options = options.dup 57 | base_path = options.delete :base_path 58 | map.connect( 59 | base_path ? "#{base_path}/#{name}" : name, 60 | options.merge(:action => name.to_s, :conditions => { :method => method }) 61 | ) 62 | end 63 | 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/apify/api_controller.rb: -------------------------------------------------------------------------------- 1 | module Apify 2 | class ApiController < ActionController::Base 3 | 4 | helper ApifyHelper 5 | 6 | class << self 7 | 8 | def authenticate(options) 9 | @user = options[:user] or raise "Missing user for authentication" 10 | @password = options[:password] or raise "Missing password for authentication" 11 | @authenticate_condition = options[:if] || lambda { true } 12 | end 13 | 14 | def api(api) 15 | @api = api 16 | @api.indexed_actions.each do |name, methods| 17 | define_method name do 18 | if action = methods[request.method] 19 | if params[:schema].present? 20 | render_schema(params[:schema], action) 21 | elsif params[:example].present? 22 | render_example(params[:example], action) 23 | else 24 | respond_with_action(action) 25 | end 26 | else 27 | render_method_not_allowed(request.method) 28 | end 29 | end 30 | end 31 | @api 32 | end 33 | 34 | end 35 | 36 | skip_before_filter :verify_authenticity_token 37 | 38 | before_filter :require_api_authentication 39 | 40 | def docs 41 | render 'apify/api/docs', :layout => false 42 | end 43 | 44 | def api 45 | configuration(:api) 46 | end 47 | 48 | def authentication_configured? 49 | !!@authenticate_condition 50 | end 51 | 52 | helper_method :api, :authentication_configured? 53 | 54 | private 55 | 56 | def configuration(symb) 57 | self.class.instance_variable_get("@#{symb}") 58 | end 59 | 60 | def respond_with_action(action) 61 | args = params[:args].present? ? JSON.parse(params[:args]) : {} 62 | render_successful_request(action.respond(args), "#{action.name}.json") 63 | rescue Exception => e 64 | render_failed_request(e.message) 65 | end 66 | 67 | def render_successful_request(value, filename) 68 | send_data value.to_json, :status => '200', :type => 'application/json', :filename => filename 69 | end 70 | 71 | def render_failed_request(message) 72 | send_data message, :status => '500', :type => 'text/plain' 73 | end 74 | 75 | def render_method_not_allowed(method) 76 | send_data "Method not allowed: #{method}", :status => 405, :type => 'text/plain' 77 | end 78 | 79 | def render_schema(nature, action) 80 | send_data JSON.pretty_generate(action.schema(nature)), :status => 200, :type => "application/schema+json", :filename => "#{action.name}.schema.json" 81 | end 82 | 83 | def render_example(nature, action) 84 | send_data JSON.pretty_generate(action.example(nature)), :status => 200, :type => "application/json", :filename => "#{action.name}.example.json" 85 | end 86 | 87 | def require_api_authentication 88 | condition = configuration(:authenticate_condition) 89 | if condition && instance_eval(&condition) 90 | authenticate_or_request_with_http_basic do |user, password| 91 | required_user = configuration(:user) 92 | required_password = configuration(:password) 93 | required_user.present? && required_password.present? && user.strip == required_user && password.strip == required_password 94 | end 95 | end 96 | end 97 | 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /lib/apify/client.rb: -------------------------------------------------------------------------------- 1 | # Require these dependencies here, so code that just uses the client 2 | # can just require 'apify/client' and is not dependent on Rails. 3 | require 'json' 4 | require 'restclient' 5 | require 'apify/errors' 6 | require 'cgi' 7 | 8 | module Apify 9 | class Client 10 | 11 | def initialize(options) 12 | @host = options[:host] or raise "Missing :host parameter" 13 | @port = options[:port] 14 | @protocol = options[:protocol] || 'http' 15 | @user = options[:user] 16 | @password = options[:password] 17 | end 18 | 19 | def get(*args) 20 | request(:get, *args) 21 | end 22 | 23 | def post(*args) 24 | request(:post, *args) 25 | end 26 | 27 | def put(*args) 28 | request(:put, *args) 29 | end 30 | 31 | def delete(*args) 32 | request(:delete, *args) 33 | end 34 | 35 | private 36 | 37 | def request(method, path, args = nil) 38 | params = args ? { :args => args.to_json } : {} 39 | if [:get, :delete].include?(method) 40 | url = build_url(path, params) 41 | params = {} 42 | else 43 | url = build_url(path) 44 | end 45 | json = RestClient.send(method, url, params) 46 | JSON.parse(json) 47 | rescue RestClient::Unauthorized => e 48 | raise Apify::Unauthorized.new("Unauthorized") 49 | rescue RestClient::ExceptionWithResponse => e 50 | raise Apify::RequestFailed.new("API request failed with status #{e.http_code}", e.http_body) 51 | end 52 | 53 | def build_url(path, params = {}) 54 | url = "" 55 | url << @protocol 56 | url << '://' 57 | url << "#{@user}:#{@password}@" if @user 58 | url << @host 59 | url << ":#{@port}" if @port 60 | url << path 61 | unless params.empty? 62 | url << '?' 63 | url << params.collect { |k, v| "#{k}=#{CGI::escape(v.to_s)}" }.join('&') 64 | end 65 | url 66 | end 67 | 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/apify/errors.rb: -------------------------------------------------------------------------------- 1 | module Apify 2 | 3 | class RequestFailed < StandardError 4 | 5 | attr_reader :response_body 6 | 7 | def initialize(message, response_body = nil) 8 | super(message) 9 | @response_body = response_body 10 | end 11 | 12 | end 13 | 14 | class Unauthorized < Apify::RequestFailed 15 | end 16 | 17 | class Invalid < Apify::RequestFailed 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /lib/apify/exchange.rb: -------------------------------------------------------------------------------- 1 | 2 | module Apify 3 | class Exchange 4 | 5 | attr_reader :args, :value 6 | 7 | def initialize 8 | @value = nil 9 | @args = nil 10 | end 11 | 12 | def respond(args, action) 13 | # hash_class = defined?(HashWithIndifferentAccess) ? HashWithIndifferentAccess : ActiveSupport::HashWithIndifferentAccess 14 | @args = args.stringify_keys 15 | validate(@args, action.schema(:args), 'Invalid request args') 16 | @value = instance_eval(&action.responder) || {} 17 | @value.stringify_keys! 18 | validate(@value, action.schema(:value), 'Invalid response value') 19 | @value 20 | end 21 | 22 | private 23 | 24 | attr_writer :value 25 | 26 | def validate(object, schema, message_prefix) 27 | JSON::Schema.validate(object, schema) if schema 28 | rescue JSON::Schema::ValueError => e 29 | @value = nil 30 | raise Apify::Invalid.new("#{message_prefix}: #{e.message}") 31 | end 32 | 33 | def sql_datetime(time) 34 | time.present? ? time.strftime("%Y-%m-%d %H:%M:%S") : nil 35 | end 36 | 37 | def sql_date(date) 38 | date.present? ? date.strftime("%Y-%m-%d") : nil 39 | end 40 | 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/apify/patterns.rb: -------------------------------------------------------------------------------- 1 | module Apify 2 | class Patterns 3 | 4 | EMAIL = /\A[a-z0-9\+\-_\.]+@[a-z0-9]+[a-z0-9\-\.]*[a-z0-9]+\z/i 5 | URL = /(http|https):\/\/[\w\-_]+(\.[\w\-_]+)*([\w\-\.,@?^=%&:\/~\+#]*[\w\-\@?^=%&\/~\+#])?/ 6 | SQL_DATE = /\A\d{4}-\d{2}-\d{2}\z/ 7 | SQL_DATETIME = /\A\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\z/ 8 | 9 | end 10 | end -------------------------------------------------------------------------------- /lib/apify/schema_helper.rb: -------------------------------------------------------------------------------- 1 | module Apify 2 | module SchemaHelper 3 | 4 | def optional(schema) 5 | schema.merge('optional' => true) 6 | end 7 | 8 | def object(properties = nil) 9 | { 'type' => 'object' }.tap do |schema| 10 | schema['properties'] = properties if properties 11 | end 12 | end 13 | 14 | def array(items = nil) 15 | { 'type' => 'array' }.tap do |schema| 16 | schema['items'] = items if items 17 | end 18 | end 19 | 20 | def string 21 | { 'type' => 'string' } 22 | end 23 | 24 | def boolean 25 | { 'type' => 'boolean' } 26 | end 27 | 28 | def enum(schema, allowed_values) 29 | schema.merge('enum' => allowed_values) 30 | end 31 | 32 | def number 33 | { 'type' => 'number' } 34 | end 35 | 36 | def integer 37 | { 'type' => 'integer' } 38 | end 39 | 40 | def sql_date 41 | { 'type' => 'string', 42 | 'pattern' => Apify::Patterns::SQL_DATE.source } 43 | end 44 | 45 | def sql_datetime 46 | { 'type' => 'string', 47 | 'pattern' => Apify::Patterns::SQL_DATETIME.source } 48 | end 49 | 50 | def email 51 | { 'type' => 'string', 52 | 'pattern' => Apify::Patterns::EMAIL.source } 53 | end 54 | 55 | def url 56 | { 'type' => 'string', 57 | 'pattern' => Apify::Patterns::URL.source } 58 | end 59 | 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /rails/init.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), '..', 'app', 'helpers', 'apify_helper') -------------------------------------------------------------------------------- /spec/apify/action_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Apify::Action do 4 | 5 | describe '#uid' do 6 | 7 | it "should return a string uniquely describing the action within an API" do 8 | action = Apify::Action.new(:post, :hello_world) {} 9 | action.uid.should == 'post_hello_world' 10 | end 11 | 12 | end 13 | 14 | describe '#example' do 15 | 16 | it "should return an example for a JSON object matching the schema" do 17 | action = Apify::Action.new(:post, :hello_world) do 18 | schema :value do 19 | object( 20 | 'string_property' => string, 21 | 'integer_property' => integer 22 | ) 23 | end 24 | end 25 | 26 | action.example(:value).should == { 27 | 'string_property' => 'string', 28 | 'integer_property' => 123 29 | } 30 | 31 | end 32 | 33 | end 34 | 35 | end -------------------------------------------------------------------------------- /spec/apify/client_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Apify::Client do 4 | 5 | it "raise an exception if authorization is missing" do 6 | client = Apify::Client.new(:host => 'host') 7 | stub_request(:post, 'http://host/api/method').to_return(:status => 401, :body => 'the body') 8 | expect { client.post '/api/method' }.to raise_error(Apify::RequestFailed) 9 | end 10 | 11 | it "raise an exception if there is a server error" do 12 | client = Apify::Client.new(:host => 'host') 13 | stub_request(:post, 'http://host/api/method').to_return(:status => 500, :body => 'the body') 14 | expect { client.post '/api/method' }.to raise_error(Apify::RequestFailed) 15 | end 16 | 17 | it "should return the parsed JSON object if the request went through" do 18 | client = Apify::Client.new(:host => 'host') 19 | stub_request(:post, 'http://host/api/method').to_return(:status => 200, :body => '{ "key": "value" }') 20 | client.post('/api/method').should == { 'key' => 'value' } 21 | end 22 | 23 | it "should call API methods with arguments" do 24 | client = Apify::Client.new(:host => 'host') 25 | stub_request(:post, 'http://host/api/hello').to_return(:body => '{}') 26 | args = { :name => 'Jack' } 27 | client.post('/api/hello', args) 28 | WebMock.should have_requested(:post, "http://host/api/hello").with(:body => { :args => args.to_json }) 29 | end 30 | 31 | it 'should not transmit an args parameter if the arguments are blank' do 32 | client = Apify::Client.new(:host => 'host') 33 | stub_request(:get, 'http://host/api/ping').to_return(:body => '{}') 34 | client.get('/api/ping') 35 | WebMock.should have_requested(:get, "http://host/api/ping") 36 | end 37 | 38 | it "should serialize params into the URL for GET requests" do 39 | client = Apify::Client.new(:host => 'host') 40 | args = { :name => 'Jack' } 41 | stub_request(:get, 'http://host/api/hello').with(:query => { :args => args.to_json }).to_return(:body => '{}') 42 | client.get('/api/hello', args) 43 | WebMock.should have_requested(:get, "http://host/api/hello").with(:query => { :args => args.to_json }) 44 | end 45 | 46 | it "should serialize params into the URL for DELETE requests" do 47 | client = Apify::Client.new(:host => 'host') 48 | args = { :name => 'Jack' } 49 | stub_request(:delete, 'http://host/api/terminate').with(:query => { :args => args.to_json }).to_return(:body => '{}') 50 | client.delete('/api/terminate', args) 51 | WebMock.should have_requested(:delete, "http://host/api/terminate").with(:query => { :args => args.to_json }) 52 | end 53 | 54 | it "should connect using SSL" do 55 | client = Apify::Client.new(:host => 'host', :protocol => 'https') 56 | stub_request(:get, 'https://host/api/ping').to_return(:body => '{}') 57 | client.get('/api/ping') 58 | WebMock.should have_requested(:get, "https://host/api/ping") 59 | end 60 | 61 | it 'should allow to use a non-standard port' do 62 | client = Apify::Client.new(:host => 'host', :port => '8080') 63 | stub_request(:get, 'http://host:8080/api/ping').to_return(:body => '{}') 64 | client.get('/api/ping') 65 | WebMock.should have_requested(:get, "http://host:8080/api/ping") 66 | end 67 | 68 | end 69 | 70 | -------------------------------------------------------------------------------- /spec/app_root/app/controllers/api_controller.rb: -------------------------------------------------------------------------------- 1 | class ApiController < Apify::ApiController 2 | 3 | attr_accessor :skip_authentication 4 | 5 | api Api 6 | authenticate :user => 'user', :password => 'password', :if => lambda { !skip_authentication } 7 | 8 | end 9 | -------------------------------------------------------------------------------- /spec/app_root/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | 3 | end 4 | -------------------------------------------------------------------------------- /spec/app_root/app/models/api.rb: -------------------------------------------------------------------------------- 1 | class Api < Apify::Api 2 | 3 | post :ping do 4 | respond do 5 | { 'message' => 'pong' } 6 | end 7 | end 8 | 9 | post :fail do 10 | respond do 11 | raise "error message" 12 | end 13 | end 14 | 15 | post :hello do 16 | schema :args do 17 | object('name' => string) 18 | end 19 | schema :value do 20 | object('message' => string) 21 | end 22 | respond do 23 | { 'message' => "Hello #{args['name']}" } 24 | end 25 | end 26 | 27 | post :echo_args do 28 | respond do 29 | args 30 | end 31 | end 32 | 33 | post :with_args_schema do 34 | schema :args do 35 | object("string_arg" => string) 36 | end 37 | end 38 | 39 | post :with_value_schema do 40 | schema :value do 41 | object("string_value" => string) 42 | end 43 | respond do 44 | args 45 | end 46 | end 47 | 48 | get :schema_with_sql_date do 49 | schema :args do 50 | object('date' => sql_date) 51 | end 52 | end 53 | 54 | get :schema_with_sql_datetime do 55 | schema :args do 56 | object('datetime' => sql_datetime) 57 | end 58 | end 59 | 60 | get :schema_with_email do 61 | schema :args do 62 | object('email' => email) 63 | end 64 | end 65 | 66 | get :schema_with_url do 67 | schema :args do 68 | object('url' => url) 69 | end 70 | end 71 | 72 | end 73 | -------------------------------------------------------------------------------- /spec/app_root/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Allow customization of the rails framework path 2 | RAILS_FRAMEWORK_ROOT = (ENV['RAILS_FRAMEWORK_ROOT'] || "#{File.dirname(__FILE__)}/../../../../../../vendor/rails") unless defined?(RAILS_FRAMEWORK_ROOT) 3 | 4 | # Don't change this file! 5 | # Configure your app in config/environment.rb and config/environments/*.rb 6 | 7 | RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT) 8 | 9 | module Rails 10 | class << self 11 | def boot! 12 | unless booted? 13 | preinitialize 14 | pick_boot.run 15 | end 16 | end 17 | 18 | def booted? 19 | defined? Rails::Initializer 20 | end 21 | 22 | def pick_boot 23 | (vendor_rails? ? VendorBoot : GemBoot).new 24 | end 25 | 26 | def vendor_rails? 27 | File.exist?(RAILS_FRAMEWORK_ROOT) 28 | end 29 | 30 | def preinitialize 31 | load(preinitializer_path) if File.exist?(preinitializer_path) 32 | end 33 | 34 | def preinitializer_path 35 | "#{RAILS_ROOT}/config/preinitializer.rb" 36 | end 37 | end 38 | 39 | class Boot 40 | def run 41 | load_initializer 42 | Rails::Initializer.run(:set_load_path) 43 | end 44 | end 45 | 46 | class VendorBoot < Boot 47 | def load_initializer 48 | require "#{RAILS_FRAMEWORK_ROOT}/railties/lib/initializer" 49 | Rails::Initializer.run(:install_gem_spec_stubs) 50 | end 51 | end 52 | 53 | class GemBoot < Boot 54 | def load_initializer 55 | self.class.load_rubygems 56 | load_rails_gem 57 | require 'initializer' 58 | end 59 | 60 | def load_rails_gem 61 | if version = self.class.gem_version 62 | gem 'rails', version 63 | else 64 | gem 'rails' 65 | end 66 | rescue Gem::LoadError => load_error 67 | $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.) 68 | exit 1 69 | end 70 | 71 | class << self 72 | def rubygems_version 73 | Gem::RubyGemsVersion rescue nil 74 | end 75 | 76 | def gem_version 77 | if defined? RAILS_GEM_VERSION 78 | RAILS_GEM_VERSION 79 | elsif ENV.include?('RAILS_GEM_VERSION') 80 | ENV['RAILS_GEM_VERSION'] 81 | else 82 | parse_gem_version(read_environment_rb) 83 | end 84 | end 85 | 86 | def load_rubygems 87 | require 'rubygems' 88 | min_version = '1.1.1' 89 | unless rubygems_version >= min_version 90 | $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.) 91 | exit 1 92 | end 93 | 94 | rescue LoadError 95 | $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org) 96 | exit 1 97 | end 98 | 99 | def parse_gem_version(text) 100 | $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/ 101 | end 102 | 103 | private 104 | def read_environment_rb 105 | environment_rb = "#{RAILS_ROOT}/config/environment.rb" 106 | environment_rb = "#{HELPER_RAILS_ROOT}/config/environment.rb" unless File.exists?(environment_rb) 107 | File.read(environment_rb) 108 | end 109 | end 110 | end 111 | end 112 | 113 | # All that for this: 114 | Rails.boot! 115 | -------------------------------------------------------------------------------- /spec/app_root/config/database.yml: -------------------------------------------------------------------------------- 1 | in_memory: 2 | adapter: sqlite3 3 | database: ":memory:" 4 | verbosity: quiet 5 | sqlite: 6 | adapter: sqlite 7 | dbfile: plugin_test.sqlite.db 8 | sqlite3: 9 | adapter: sqlite3 10 | dbfile: plugin_test.sqlite3.db 11 | postgresql: 12 | adapter: postgresql 13 | username: postgres 14 | password: postgres 15 | database: plugin_test 16 | mysql: 17 | adapter: mysql 18 | host: localhost 19 | username: root 20 | password: 21 | database: plugin_test 22 | -------------------------------------------------------------------------------- /spec/app_root/config/environment.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'boot') 2 | 3 | Rails::Initializer.run do |config| 4 | config.cache_classes = false 5 | config.whiny_nils = true 6 | config.action_controller.session = { :key => "_myapp_session", :secret => "gwirofjweroijger8924rt2zfwehfuiwehb1378rifowenfoqwphf23" } 7 | config.plugin_locators.unshift( 8 | Class.new(Rails::Plugin::Locator) do 9 | def plugins 10 | [Rails::Plugin.new(File.expand_path('.'))] 11 | end 12 | end 13 | ) unless defined?(PluginTestHelper::PluginLocator) 14 | end 15 | -------------------------------------------------------------------------------- /spec/app_root/config/environments/in_memory.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makandra/apify/76bf424f72a223fa93f230529a4a4c9a251a3a67/spec/app_root/config/environments/in_memory.rb -------------------------------------------------------------------------------- /spec/app_root/config/environments/mysql.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makandra/apify/76bf424f72a223fa93f230529a4a4c9a251a3a67/spec/app_root/config/environments/mysql.rb -------------------------------------------------------------------------------- /spec/app_root/config/environments/postgresql.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makandra/apify/76bf424f72a223fa93f230529a4a4c9a251a3a67/spec/app_root/config/environments/postgresql.rb -------------------------------------------------------------------------------- /spec/app_root/config/environments/sqlite.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makandra/apify/76bf424f72a223fa93f230529a4a4c9a251a3a67/spec/app_root/config/environments/sqlite.rb -------------------------------------------------------------------------------- /spec/app_root/config/environments/sqlite3.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makandra/apify/76bf424f72a223fa93f230529a4a4c9a251a3a67/spec/app_root/config/environments/sqlite3.rb -------------------------------------------------------------------------------- /spec/app_root/config/routes.rb: -------------------------------------------------------------------------------- 1 | ActionController::Routing::Routes.draw do |map| 2 | 3 | Api.draw_routes(map) 4 | 5 | # map.connect 'api/hello', :controller => 'api', :action => 'hello', :conditions => { :method => :get } 6 | # map.connect 'api/fail', :controller => 'api', :action => 'fail', :conditions => { :method => :get } 7 | # map.connect 'api/echo_args', :controller => 'api', :action => 'echo_args', :conditions => { :method => :get } 8 | # map.connect 'api/with_args_schema', :controller => 'api', :action => 'with_args_schema', :conditions => { :method => :get } 9 | # map.connect 'api/with_value_schema', :controller => 'api', :action => 'with_value_schema', :conditions => { :method => :get } 10 | # map.connect 'api/songs', :controller => 'api', :action => 'index_songs', :conditions => { :method => :get } 11 | 12 | end 13 | -------------------------------------------------------------------------------- /spec/app_root/lib/console_with_fixtures.rb: -------------------------------------------------------------------------------- 1 | # Loads fixtures into the database when running the test app via the console 2 | (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(Rails.root, '../fixtures/*.{yml,csv}'))).each do |fixture_file| 3 | Fixtures.create_fixtures(File.join(Rails.root, '../fixtures'), File.basename(fixture_file, '.*')) 4 | end 5 | -------------------------------------------------------------------------------- /spec/app_root/script/console: -------------------------------------------------------------------------------- 1 | irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb' 2 | libs = " -r irb/completion" 3 | libs << " -r test/test_helper" 4 | libs << " -r console_app" 5 | libs << " -r console_with_helpers" 6 | libs << " -r console_with_fixtures" 7 | exec "#{irb} #{libs} --simple-prompt" 8 | -------------------------------------------------------------------------------- /spec/controllers/api_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ApiController do 4 | 5 | authenticate = lambda do |*args| 6 | user = args[0] || 'user' 7 | password = args[1] || 'password' 8 | request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(user, password) 9 | end 10 | 11 | describe 'authentication' do 12 | 13 | it "should deny access with the wrong user" do 14 | instance_exec('wrong-user', 'password', &authenticate) 15 | post :ping 16 | response.code.should == '401' 17 | end 18 | 19 | it "should deny access with the wrong password" do 20 | instance_exec('user', 'wrong-password', &authenticate) 21 | post :ping 22 | response.code.should == '401' 23 | end 24 | 25 | it "should grant access with the correct user and password" do 26 | instance_exec('user', 'password', &authenticate) 27 | post :ping 28 | response.code.should == '200' 29 | end 30 | 31 | it "should allow an option to skip authentication" do 32 | controller.skip_authentication = true 33 | post :ping 34 | response.code.should == '200' 35 | controller.skip_authentication = false 36 | post :ping 37 | response.code.should == '401' 38 | end 39 | 40 | end 41 | 42 | describe 'error handling' do 43 | 44 | it 'should fill exception messages into the response body and return a 500 error code' do 45 | instance_exec(&authenticate) 46 | post :fail 47 | response.code.should == '500' 48 | response.body == 'error message' 49 | end 50 | 51 | end 52 | 53 | describe 'args' do 54 | 55 | it 'should have access to the args' do 56 | instance_exec(&authenticate) 57 | args = {'foo' => 'bar'} 58 | post :echo_args, :args => args.to_json 59 | response.code.should == '200' 60 | JSON.parse(response.body).should == args 61 | end 62 | 63 | end 64 | 65 | describe 'argument schemas' do 66 | 67 | before :each do 68 | instance_exec(&authenticate) 69 | end 70 | 71 | it "should validate the presence of a property" do 72 | post :with_args_schema, :args => {}.to_json 73 | response.code.should == '500' 74 | response.body.should include('Invalid request args') 75 | response.body.should include('string_arg is missing') 76 | end 77 | 78 | it "should validate value types" do 79 | post :with_args_schema, :args => {'string_arg' => 123}.to_json 80 | response.code.should == '500' 81 | response.body.should include('Invalid request args') 82 | response.body.should include('a string is required') 83 | end 84 | 85 | it "should allow requests that fit the schema" do 86 | post :with_args_schema, :args => {'string_arg' => 'a string'}.to_json 87 | response.code.should == '200' 88 | end 89 | 90 | it "should render the schema if requested" do 91 | post :with_args_schema, :schema => 'args' 92 | response.code.should == '200' 93 | JSON.parse(response.body).should == { 94 | "type" => "object", 95 | "properties" => { 96 | "string_arg" => { "type" => "string" } 97 | }} 98 | end 99 | 100 | end 101 | 102 | describe 'value schemas' do 103 | 104 | before :each do 105 | instance_exec(&authenticate) 106 | end 107 | 108 | it "should validate the presence of a property" do 109 | post :with_value_schema, :args => {}.to_json 110 | response.code.should == '500' 111 | response.body.should include('Invalid response value') 112 | response.body.should include('string_value is missing') 113 | end 114 | 115 | it "should validate value types" do 116 | post :with_value_schema, :args => {'string_value' => 123}.to_json 117 | response.code.should == '500' 118 | response.body.should include('Invalid response value') 119 | response.body.should include('a string is required') 120 | end 121 | 122 | it "should return responses that fit the schema" do 123 | post :with_value_schema, :args => {'string_value' => 'a string'}.to_json 124 | response.code.should == '200' 125 | JSON.parse(response.body).should == {'string_value' => 'a string'} 126 | end 127 | 128 | it "should render the schema if requested" do 129 | post :with_value_schema, :schema => 'value' 130 | response.code.should == '200' 131 | JSON.parse(response.body).should == { 132 | "type" => "object", 133 | "properties" => { 134 | "string_value" => { "type" => "string" } 135 | }} 136 | end 137 | 138 | end 139 | 140 | describe 'schema helpers' do 141 | 142 | before :each do 143 | instance_exec(&authenticate) 144 | end 145 | 146 | describe '#sql_date helper' do 147 | 148 | it 'should match a date as seen in SQL' do 149 | get :schema_with_sql_date, :args => {'date' => '2011-05-01'}.to_json 150 | response.code.should == '200' 151 | end 152 | 153 | it 'should not match an invalid string' do 154 | get :schema_with_sql_date, :args => {'date' => '01.05.2011'}.to_json 155 | response.code.should == '500' 156 | end 157 | 158 | end 159 | 160 | describe '#sql_datetime helper' do 161 | 162 | it 'should match a timestamp as seen in SQL' do 163 | get :schema_with_sql_datetime, :args => {'datetime' => '2011-05-01 12:10:59'}.to_json 164 | response.code.should == '200' 165 | end 166 | 167 | it 'should not match an invalid string' do 168 | get :schema_with_sql_datetime, :args => {'datetime' => '2011-05-01'}.to_json 169 | response.code.should == '500' 170 | end 171 | 172 | end 173 | 174 | describe '#email helper' do 175 | 176 | it 'should match an email address' do 177 | get :schema_with_email, :args => {'email' => 'some.guy@some.domain.tld'}.to_json 178 | response.code.should == '200' 179 | end 180 | 181 | it 'should not match an invalid string' do 182 | get :schema_with_email, :args => {'email' => 'some.guy'}.to_json 183 | response.code.should == '500' 184 | end 185 | 186 | end 187 | 188 | describe '#url_helper' do 189 | 190 | it 'should match a http address' do 191 | get :schema_with_url, :args => {'url' => 'http://some.domain.tld/path/to?query=value'}.to_json 192 | response.code.should == '200' 193 | end 194 | 195 | it 'should match a https address' do 196 | get :schema_with_url, :args => {'url' => 'https://some.domain.tld/path/to?query=value'}.to_json 197 | response.code.should == '200' 198 | end 199 | 200 | it 'should not match an invalid string' do 201 | get :schema_with_url, :args => {'url' => 'foo:bar:bam'}.to_json 202 | response.code.should == '500' 203 | end 204 | 205 | end 206 | 207 | end 208 | 209 | describe 'auto-generated documentation' do 210 | integrate_views 211 | 212 | it 'should render auto-generated API documentation' do 213 | instance_exec(&authenticate) 214 | get :docs 215 | response.code.should == '200' 216 | response.body.should include('ping') 217 | response.body.should include('fail') 218 | response.body.should include('echo_args') 219 | end 220 | 221 | end 222 | 223 | end 224 | 225 | -------------------------------------------------------------------------------- /spec/models/api_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Convenient testing of Api models' do 4 | 5 | it 'should allow to test response objects directly' do 6 | Api.post(:hello, :name => 'Jack').should == { 'message' => 'Hello Jack' } 7 | end 8 | 9 | it 'should raise an exception on errors' do 10 | expect { Api.post(:hello) }.to raise_error(Apify::Invalid) 11 | expect { Api.post(:fail) }.to raise_error(StandardError) 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /spec/rcov.opts: -------------------------------------------------------------------------------- 1 | --exclude "spec/*,gems/*" 2 | --rails -------------------------------------------------------------------------------- /spec/spec.opts: -------------------------------------------------------------------------------- 1 | --colour 2 | --format progress 3 | --loadby mtime 4 | --reverse 5 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $: << File.join(File.dirname(__FILE__), "/../lib" ) 2 | 3 | # Set the default environment to sqlite3's in_memory database 4 | ENV['RAILS_ENV'] ||= 'in_memory' 5 | 6 | # Load the Rails environment and testing framework 7 | require "#{File.dirname(__FILE__)}/app_root/config/environment" 8 | require "#{File.dirname(__FILE__)}/../lib/apify" 9 | 10 | require 'spec/rails' 11 | require 'webmock/rspec' 12 | 13 | # Undo changes to RAILS_ENV 14 | silence_warnings {RAILS_ENV = ENV['RAILS_ENV']} 15 | 16 | # Run the migrations 17 | ActiveRecord::Migrator.migrate("#{Rails.root}/db/migrate") 18 | 19 | Spec::Runner.configure do |config| 20 | config.use_transactional_fixtures = true 21 | config.use_instantiated_fixtures = false 22 | config.include WebMock::API 23 | end 24 | --------------------------------------------------------------------------------