wd_sinatra 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE +22 -0
  4. data/README.md +148 -0
  5. data/Rakefile +2 -0
  6. data/bin/wd_sinatra +47 -0
  7. data/lib/wd_sinatra.rb +7 -0
  8. data/lib/wd_sinatra/app_loader.rb +120 -0
  9. data/lib/wd_sinatra/sinatra_ext.rb +117 -0
  10. data/lib/wd_sinatra/test_helpers.rb +204 -0
  11. data/lib/wd_sinatra/version.rb +5 -0
  12. data/templates/Gemfile +23 -0
  13. data/templates/Guardfile +13 -0
  14. data/templates/Rakefile +32 -0
  15. data/templates/api/hello_world.rb +29 -0
  16. data/templates/bin/console +7 -0
  17. data/templates/config.ru +5 -0
  18. data/templates/config/app.rb +5 -0
  19. data/templates/config/environments/default.rb +3 -0
  20. data/templates/config/environments/production.rb +2 -0
  21. data/templates/config/environments/test.rb +3 -0
  22. data/templates/config/hooks.rb +74 -0
  23. data/templates/config/middleware.rb +0 -0
  24. data/templates/config/sinatra_config.rb +12 -0
  25. data/templates/lib/body_parser.rb +24 -0
  26. data/templates/lib/tasks/doc.rake +39 -0
  27. data/templates/lib/tasks/doc_generator/bootstrap/.gitignore +4 -0
  28. data/templates/lib/tasks/doc_generator/bootstrap/LICENSE +176 -0
  29. data/templates/lib/tasks/doc_generator/bootstrap/Makefile +47 -0
  30. data/templates/lib/tasks/doc_generator/bootstrap/README.md +105 -0
  31. data/templates/lib/tasks/doc_generator/bootstrap/bootstrap.css +2467 -0
  32. data/templates/lib/tasks/doc_generator/bootstrap/bootstrap.min.css +356 -0
  33. data/templates/lib/tasks/doc_generator/bootstrap/docs/assets/css/docs.css +317 -0
  34. data/templates/lib/tasks/doc_generator/bootstrap/docs/assets/ico/bootstrap-apple-114x114.png +0 -0
  35. data/templates/lib/tasks/doc_generator/bootstrap/docs/assets/ico/bootstrap-apple-57x57.png +0 -0
  36. data/templates/lib/tasks/doc_generator/bootstrap/docs/assets/ico/bootstrap-apple-72x72.png +0 -0
  37. data/templates/lib/tasks/doc_generator/bootstrap/docs/assets/ico/favicon.ico +0 -0
  38. data/templates/lib/tasks/doc_generator/bootstrap/docs/assets/img/bird.png +0 -0
  39. data/templates/lib/tasks/doc_generator/bootstrap/docs/assets/img/browsers.png +0 -0
  40. data/templates/lib/tasks/doc_generator/bootstrap/docs/assets/img/example-diagram-01.png +0 -0
  41. data/templates/lib/tasks/doc_generator/bootstrap/docs/assets/img/example-diagram-02.png +0 -0
  42. data/templates/lib/tasks/doc_generator/bootstrap/docs/assets/img/example-diagram-03.png +0 -0
  43. data/templates/lib/tasks/doc_generator/bootstrap/docs/assets/img/grid-18px.png +0 -0
  44. data/templates/lib/tasks/doc_generator/bootstrap/docs/assets/img/twitter-logo-no-bird.png +0 -0
  45. data/templates/lib/tasks/doc_generator/bootstrap/docs/assets/js/application.js +52 -0
  46. data/templates/lib/tasks/doc_generator/bootstrap/docs/assets/js/google-code-prettify/prettify.css +94 -0
  47. data/templates/lib/tasks/doc_generator/bootstrap/docs/assets/js/google-code-prettify/prettify.js +28 -0
  48. data/templates/lib/tasks/doc_generator/bootstrap/docs/index.html +2037 -0
  49. data/templates/lib/tasks/doc_generator/bootstrap/docs/javascript.html +798 -0
  50. data/templates/lib/tasks/doc_generator/bootstrap/examples/container-app.html +119 -0
  51. data/templates/lib/tasks/doc_generator/bootstrap/examples/fluid.html +122 -0
  52. data/templates/lib/tasks/doc_generator/bootstrap/examples/hero.html +79 -0
  53. data/templates/lib/tasks/doc_generator/bootstrap/js/bootstrap-alerts.js +124 -0
  54. data/templates/lib/tasks/doc_generator/bootstrap/js/bootstrap-buttons.js +64 -0
  55. data/templates/lib/tasks/doc_generator/bootstrap/js/bootstrap-dropdown.js +55 -0
  56. data/templates/lib/tasks/doc_generator/bootstrap/js/bootstrap-modal.js +260 -0
  57. data/templates/lib/tasks/doc_generator/bootstrap/js/bootstrap-popover.js +90 -0
  58. data/templates/lib/tasks/doc_generator/bootstrap/js/bootstrap-scrollspy.js +107 -0
  59. data/templates/lib/tasks/doc_generator/bootstrap/js/bootstrap-tabs.js +80 -0
  60. data/templates/lib/tasks/doc_generator/bootstrap/js/bootstrap-twipsy.js +321 -0
  61. data/templates/lib/tasks/doc_generator/bootstrap/js/tests/index.html +40 -0
  62. data/templates/lib/tasks/doc_generator/bootstrap/js/tests/unit/bootstrap-alerts.js +41 -0
  63. data/templates/lib/tasks/doc_generator/bootstrap/js/tests/unit/bootstrap-buttons.js +42 -0
  64. data/templates/lib/tasks/doc_generator/bootstrap/js/tests/unit/bootstrap-dropdown.js +52 -0
  65. data/templates/lib/tasks/doc_generator/bootstrap/js/tests/unit/bootstrap-modal.js +151 -0
  66. data/templates/lib/tasks/doc_generator/bootstrap/js/tests/unit/bootstrap-popover.js +76 -0
  67. data/templates/lib/tasks/doc_generator/bootstrap/js/tests/unit/bootstrap-scrollspy.js +31 -0
  68. data/templates/lib/tasks/doc_generator/bootstrap/js/tests/unit/bootstrap-tabs.js +77 -0
  69. data/templates/lib/tasks/doc_generator/bootstrap/js/tests/unit/bootstrap-twipsy.js +81 -0
  70. data/templates/lib/tasks/doc_generator/bootstrap/js/tests/vendor/qunit.css +232 -0
  71. data/templates/lib/tasks/doc_generator/bootstrap/js/tests/vendor/qunit.js +1510 -0
  72. data/templates/lib/tasks/doc_generator/bootstrap/lib/bootstrap.less +26 -0
  73. data/templates/lib/tasks/doc_generator/bootstrap/lib/forms.less +479 -0
  74. data/templates/lib/tasks/doc_generator/bootstrap/lib/mixins.less +222 -0
  75. data/templates/lib/tasks/doc_generator/bootstrap/lib/patterns.less +1060 -0
  76. data/templates/lib/tasks/doc_generator/bootstrap/lib/reset.less +141 -0
  77. data/templates/lib/tasks/doc_generator/bootstrap/lib/scaffolding.less +139 -0
  78. data/templates/lib/tasks/doc_generator/bootstrap/lib/tables.less +224 -0
  79. data/templates/lib/tasks/doc_generator/bootstrap/lib/type.less +187 -0
  80. data/templates/lib/tasks/doc_generator/bootstrap/lib/variables.less +60 -0
  81. data/templates/lib/tasks/doc_generator/template.erb +117 -0
  82. data/templates/public/favicon.ico +0 -0
  83. data/wd-sinatra.gemspec +22 -0
  84. metadata +154 -0
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in wd-sinatra.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Matt Aimonetti
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # Weasel Diesel Sinatra
2
+
3
+ Weasel-Diesel Sinatra app gem, allowing you to generate/update sinatra apps using the Weasel Diesel DSL
4
+
5
+
6
+ ## Installation
7
+
8
+ $ gem install 'wd_sinatra'
9
+
10
+ ## Usage
11
+
12
+ ### App generation
13
+
14
+ Once the gem is installed, you can use the generator to create a new
15
+ app. Go to the location where you want to generate a new app and type
16
+ the following command (replace `<app_name>` by the name you want to
17
+ application to have):
18
+
19
+ $ wd_sinatra <app_name>
20
+
21
+ Check the newly generated app
22
+
23
+ $ cd <app_name>
24
+
25
+ You'll need bundler to install the dependencies:
26
+
27
+ $ gem install bundler
28
+ $ bundle install
29
+
30
+ ### Starting the server
31
+
32
+ The app is now ready to use, to start it you can use rack:
33
+
34
+ $ bundle exec rackup
35
+
36
+ This will start the server on port 9292 and the default GET `/hello_world` service will be available at: `http://localhost:9292/hello_world'.
37
+
38
+ Note that the code won't be reloading automatically in the server when
39
+ you make a modification to the source code. For that, you might want to
40
+ use Puma + [Guard](https://github.com/jc00ke/guard-puma) or another tool that allows you to do that.
41
+ While it's a nice feature to have, a lot of developers like to do that
42
+ differently and it seems more sensitive to let them pick the way they
43
+ like the most.
44
+
45
+ ### Console
46
+
47
+ $ bundle exec bin/console
48
+
49
+ The console mode is like the server mode but without the server only
50
+ concerns such as the sinatra routes config and Rack middleware.
51
+
52
+ ### Documentation generation
53
+
54
+ $ rake doc:services
55
+
56
+ To generate documentation for the APIs you created in the api folder.
57
+
58
+ ### Testing
59
+
60
+ TODO
61
+
62
+ ## Writing a service
63
+
64
+ TODO see Weasel Diesel for now.
65
+
66
+ ## Config and hooks
67
+
68
+ ### app.rb
69
+
70
+ The `config/app.rb` file is being required after the environment is set
71
+ but before the models are loaded. This is the perfect place to load
72
+ custom libraries and set your datastore.
73
+
74
+ This is where you will for instance load ActiveRecord and set your DB
75
+ connection.
76
+
77
+ ### Environments
78
+
79
+ The files in `config/environments` can
80
+ be used to set environment specific configuration or other.
81
+ If you add a new environment such as staging, you can add a `staging.rb`
82
+ file in the environments folder that will only get required when running
83
+ in this env mode.
84
+ Whatever the environment is, the `config/environments/default.rb` is
85
+ being required before the specific env file.
86
+
87
+ ### Hooks
88
+
89
+ The request dispatcher offers 3 hooks which you can see demonstrated in
90
+ `config/hooks.rb`.
91
+
92
+ * `params_preprocessor_hook(params)`
93
+ * `params_postprocessor_hook(params)`
94
+ * `pre_dispatch_hook`
95
+
96
+ The two first hooks are used to process the params and when implemented
97
+ are expected to return the params that will be used in the request.
98
+
99
+ The `pre_dispatch_hook` is called just before the request is dispatched
100
+ to the service implementation. This is where you might want to implement
101
+ an authentication verification system for instance.
102
+
103
+ These 3 hooks have access to the entire request context, including the
104
+ `service` being called. You can use the service `extra` option to set
105
+ some custom settings that can then be used in this pre dispatch hook.
106
+
107
+ In the default generated application, a body parser is provided to parse
108
+ JSON requests when the HTTP verb is PUT, POST or DELETE. The json parser is set by default to use the `JSON` module but you might want to change it to use Yajl for instance.
109
+ To do that, edit the `config/hooks.rb` file and change the following:
110
+
111
+ ```ruby
112
+ BodyParser.json_parser = JSON
113
+ ```
114
+
115
+ to:
116
+
117
+ ```ruby
118
+ BodyParser.json_parser = Yajl::Parser
119
+ ```
120
+
121
+ Of course, you'll need to require `Yajl` first and add it to your
122
+ Gemfile if you want to use Bundler.
123
+
124
+
125
+
126
+ ## Using an ORM
127
+
128
+ TODO: see https://github.com/mattetti/sinatra-web-api-example/ for now
129
+ for an example of setting up ActiveRecord.
130
+ Eventually the generator will take an option to generate an AR, DM or
131
+ other ORM template.
132
+
133
+ ## Update
134
+
135
+ To update your app, just update your gem dependency on `wd_sinatra`, you
136
+ can also compare the difference between your app and a freshly generated
137
+ app by trying to generate a new app named the same as your old app.
138
+ The generator will detect conflicts and let you pick an action (diff,
139
+ overwrite, ignore...)
140
+
141
+
142
+ ## Contributing
143
+
144
+ 1. Fork it
145
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
146
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
147
+ 4. Push to the branch (`git push origin my-new-feature`)
148
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/bin/wd_sinatra ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+ require "rubygems"
3
+ require "thor/group"
4
+
5
+ class WdSinatra < Thor::Group
6
+ include Thor::Actions
7
+
8
+ # Define arguments and options
9
+ # argument :type, :type => :string, :desc => "The type to generate, app by default"
10
+ argument :name, :type => :string, :desc => "The name of the app to generate"
11
+ class_option :test_framework, :default => :test_unit
12
+
13
+ def self.source_root
14
+ File.expand_path(File.join('..', 'templates'), File.dirname(__FILE__))
15
+ end
16
+
17
+ def create_lib_directory
18
+ directory "lib", "#{name}/lib"
19
+ end
20
+
21
+ def create_config_directory
22
+ directory "config", "#{name}/config"
23
+ end
24
+
25
+ def create_bin_directory
26
+ directory "bin", "#{name}/bin"
27
+ chmod "#{name}/bin/console", 0755
28
+ end
29
+
30
+ def create_public_directory
31
+ directory "public", "#{name}/public"
32
+ end
33
+
34
+ def create_api_directory
35
+ directory "api", "#{name}/api"
36
+ end
37
+
38
+ def create_files
39
+ copy_file "Rakefile", "#{name}/Rakefile"
40
+ copy_file "Gemfile", "#{name}/Gemfile"
41
+ copy_file "config.ru", "#{name}/config.ru"
42
+ copy_file "Guardfile", "#{name}/Guardfile"
43
+ end
44
+
45
+ end
46
+
47
+ WdSinatra.start
data/lib/wd_sinatra.rb ADDED
@@ -0,0 +1,7 @@
1
+ require "wd_sinatra/version"
2
+
3
+ module WD
4
+ module Sinatra
5
+ # Your code goes here...
6
+ end
7
+ end
@@ -0,0 +1,120 @@
1
+ if RUBY_VERSION =~ /1.8/
2
+ require 'rubygems'
3
+ require 'backports'
4
+ end
5
+ require 'bundler'
6
+ Bundler.setup
7
+ require 'logger'
8
+ require 'weasel_diesel'
9
+
10
+ module WDSinatra
11
+ module AppLoader
12
+ module_function
13
+
14
+ # Boot in server mode
15
+ def server(root_path)
16
+ @root = root_path
17
+ unless @booted
18
+ console(root_path)
19
+ load_middleware
20
+ set_sinatra_routes
21
+ set_sinatra_settings
22
+ end
23
+ end
24
+
25
+ # Boot in console mode
26
+ def console(root_path)
27
+ @root = root_path
28
+ unless @booted
29
+ set_env
30
+ set_loadpath
31
+ load_environment
32
+ load_lib_dependencies
33
+ load_app_config
34
+ load_models
35
+ load_apis
36
+ @booted = true
37
+ end
38
+ end
39
+
40
+ def root_path
41
+ @root
42
+ end
43
+
44
+ # PRIVATE
45
+
46
+ # Sets the environment (RACK_ENV) based on some env variables.
47
+ def set_env
48
+ if !Object.const_defined?(:RACK_ENV)
49
+ ENV['RACK_ENV'] ||= ENV['RAILS_ENV'] || 'development'
50
+ Object.const_set(:RACK_ENV, ENV['RACK_ENV'])
51
+ end
52
+ puts "Running in #{RACK_ENV} mode" if RACK_ENV == 'development'
53
+ end
54
+
55
+
56
+ # Loads an environment specific config if available, the config file is where the logger should be set
57
+ # if it was not, we are using stdout.
58
+ def load_environment(env=RACK_ENV)
59
+ # Load the default which can be overwritten or extended by specific
60
+ # env config files.
61
+ require File.join(root_path, 'config', 'environments', 'default.rb')
62
+ env_file = File.join(root_path, "config", "environments", "#{env}.rb")
63
+ if File.exist?(env_file)
64
+ require env_file
65
+ else
66
+ debug_msg = "Environment file: #{env_file} couldn't be found, using only the default environment config instead." unless env == 'development'
67
+ end
68
+ # making sure we have a LOGGER constant defined.
69
+ unless Object.const_defined?(:LOGGER)
70
+ Object.const_set(:LOGGER, Logger.new($stdout))
71
+ end
72
+ LOGGER.debug(debug_msg) if debug_msg
73
+ end
74
+
75
+ def set_loadpath(root=nil)
76
+ root ||= root_path
77
+ $: << root
78
+ $: << File.join(root, 'lib')
79
+ $: << File.join(root, 'models')
80
+ end
81
+
82
+ def load_lib_dependencies
83
+ # WeaselDiesel is the web service DSL gem used to define services.
84
+ require 'weasel_diesel'
85
+ require 'sinatra'
86
+ require 'wd_sinatra/sinatra_ext'
87
+ # TODO: hook to custom app dependencies
88
+ end
89
+
90
+ def load_app_config
91
+ require File.join(root_path, 'config', 'app')
92
+ end
93
+
94
+ def load_models
95
+ Dir.glob(File.join(root_path, "models", "**", "*.rb")).each do |model|
96
+ require model
97
+ end
98
+ end
99
+
100
+ # DSL routes are located in the api folder
101
+ def load_apis
102
+ Dir.glob(File.join(root_path, "api", "**", "*.rb")).each do |api|
103
+ require api
104
+ end
105
+ end
106
+
107
+ def set_sinatra_routes
108
+ WSList.all.sort.each{|api| api.load_sinatra_route }
109
+ end
110
+
111
+ def load_middleware
112
+ require File.join(root_path, 'config', 'middleware')
113
+ end
114
+
115
+ def set_sinatra_settings
116
+ require File.join(root_path, 'config', 'sinatra_config')
117
+ end
118
+
119
+ end
120
+ end
@@ -0,0 +1,117 @@
1
+ require 'forwardable'
2
+ require 'params_verification'
3
+ require 'json'
4
+
5
+ class WeaselDiesel
6
+
7
+ class RequestHandler
8
+ extend Forwardable
9
+
10
+ # @return [WeaselDiesel] The service served by this controller
11
+ # @api public
12
+ attr_reader :service
13
+
14
+ # @return [Sinatra::Application]
15
+ # @api public
16
+ attr_reader :app
17
+
18
+ # @return [Hash]
19
+ # @api public
20
+ attr_reader :env
21
+
22
+ # @return [Sinatra::Request]
23
+ # @see http://rubydoc.info/github/sinatra/sinatra/Sinatra/Request
24
+ # @api public
25
+ attr_reader :request
26
+
27
+ # @return [Sinatra::Response]
28
+ # @see http://rubydoc.info/github/sinatra/sinatra/Sinatra/Response
29
+ # @api public
30
+ attr_reader :response
31
+
32
+ # @return [Hash]
33
+ # @api public
34
+ attr_accessor :params
35
+
36
+ attr_accessor :current_user
37
+
38
+ # The service controller might be loaded outside of a Sinatra App
39
+ # in this case, we don't need to load the helpers
40
+ if Object.const_defined?(:Sinatra)
41
+ include Sinatra::Helpers
42
+ end
43
+
44
+ def initialize(service, &block)
45
+ @service = service
46
+ @implementation = block
47
+ end
48
+
49
+ def dispatch(app)
50
+ @app = app
51
+ @env = app.env
52
+ @request = app.request
53
+ @response = app.response
54
+ @service = service
55
+
56
+ begin
57
+ # raises an exception if the params are not valid
58
+ # otherwise update the app params with potentially new params (using default values)
59
+ # note that if a type is mentioned for a params, the object will be cast to this object type
60
+ #
61
+ # removing the fake sinatra params since v1.3 added this. (should be eventually removed)
62
+ if app.params['splat']
63
+ processed_params = app.params.dup
64
+ processed_params.delete('splat')
65
+ processed_params.delete('captures')
66
+ end
67
+ @params = (processed_params || app.params)
68
+ @params = params_preprocessor_hook(@params) if self.respond_to?(:params_preprocessor_hook)
69
+ @params = ParamsVerification.validate!(@params, service.defined_params)
70
+ @params = params_postrocessor_hook(@params) if self.respond_to?(:params_postprocessor_hook)
71
+ rescue Exception => e
72
+ LOGGER.error e.message
73
+ LOGGER.error "passed params: #{app.params.inspect}"
74
+ halt 400, {:error => e.message}.to_json
75
+ end
76
+
77
+ # Define WeaselDiesel::RequestHandler#authorization_check in your app if
78
+ # you want to use an auth check.
79
+ pre_dispatch_hook if self.respond_to?(:pre_dispatch_hook)
80
+ service_dispatch
81
+ end
82
+
83
+ # Forwarding some methods to the underlying app object
84
+ def_delegators :app, :settings, :halt, :compile_template, :session
85
+
86
+ private ##################################################
87
+
88
+ end # of RequestHandler
89
+
90
+ attr_reader :handler
91
+
92
+ def implementation(&block)
93
+ if block_given?
94
+ @handler = RequestHandler.new(self, &block)
95
+ @handler.define_singleton_method(:service_dispatch, block)
96
+ end
97
+ @handler
98
+ end
99
+
100
+ def load_sinatra_route
101
+ service = self
102
+ upcase_verb = service.verb.to_s.upcase
103
+ unless ENV['DONT_PRINT_ROUTES']
104
+ LOGGER.info "Available endpoint: #{self.http_verb.upcase} /#{self.url}"
105
+ end
106
+ raise "DSL is missing the implementation block" unless self.handler && self.handler.respond_to?(:service_dispatch)
107
+
108
+ # Define the route directly to save some object allocations on the critical path
109
+ # Note that we are using a private API to define the route and that unlike sinatra usual DSL
110
+ # we do NOT define a HEAD route for every GET route.
111
+ Sinatra::Base.send(:route, upcase_verb, "/#{self.url}") do
112
+ service.handler.dispatch(self)
113
+ end
114
+
115
+ end
116
+
117
+ end