praxis 0.10.1 → 0.11pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -1
  3. data/CHANGELOG.md +47 -10
  4. data/Gemfile +1 -1
  5. data/Guardfile +1 -0
  6. data/bin/praxis +33 -4
  7. data/lib/api_browser/app/css/main.css +0 -3
  8. data/lib/praxis.rb +16 -0
  9. data/lib/praxis/action_definition.rb +16 -18
  10. data/lib/praxis/application.rb +31 -2
  11. data/lib/praxis/bootloader.rb +37 -4
  12. data/lib/praxis/bootloader_stages/environment.rb +3 -7
  13. data/lib/praxis/bootloader_stages/plugin_config_load.rb +20 -0
  14. data/lib/praxis/bootloader_stages/plugin_config_prepare.rb +18 -0
  15. data/lib/praxis/bootloader_stages/plugin_loader.rb +19 -0
  16. data/lib/praxis/bootloader_stages/plugin_setup.rb +13 -0
  17. data/lib/praxis/bootloader_stages/routing.rb +16 -6
  18. data/lib/praxis/callbacks.rb +0 -2
  19. data/lib/praxis/config.rb +3 -2
  20. data/lib/praxis/dispatcher.rb +25 -13
  21. data/lib/praxis/error_handler.rb +16 -0
  22. data/lib/praxis/links.rb +9 -4
  23. data/lib/praxis/media_type_collection.rb +2 -3
  24. data/lib/praxis/notifications.rb +41 -0
  25. data/lib/praxis/plugin.rb +18 -8
  26. data/lib/praxis/plugin_concern.rb +40 -0
  27. data/lib/praxis/request.rb +27 -7
  28. data/lib/praxis/request_stages/action.rb +7 -2
  29. data/lib/praxis/request_stages/response.rb +7 -3
  30. data/lib/praxis/request_stages/validate_payload.rb +7 -1
  31. data/lib/praxis/resource_definition.rb +37 -16
  32. data/lib/praxis/response.rb +1 -0
  33. data/lib/praxis/responses/internal_server_error.rb +13 -8
  34. data/lib/praxis/responses/validation_error.rb +10 -7
  35. data/lib/praxis/restful_doc_generator.rb +312 -0
  36. data/lib/praxis/router.rb +7 -5
  37. data/lib/praxis/skeletor/restful_routing_config.rb +12 -5
  38. data/lib/praxis/stage.rb +5 -1
  39. data/lib/praxis/stats.rb +106 -0
  40. data/lib/praxis/tasks/api_docs.rb +8 -314
  41. data/lib/praxis/version.rb +1 -1
  42. data/praxis.gemspec +4 -1
  43. data/spec/functional_spec.rb +87 -32
  44. data/spec/praxis/action_definition_spec.rb +13 -12
  45. data/spec/praxis/bootloader_spec.rb +12 -5
  46. data/spec/praxis/notifications_spec.rb +23 -0
  47. data/spec/praxis/plugin_concern_spec.rb +21 -0
  48. data/spec/praxis/request_spec.rb +56 -1
  49. data/spec/praxis/request_stages_validate_spec.rb +3 -3
  50. data/spec/praxis/resource_definition_spec.rb +44 -60
  51. data/spec/praxis/responses/internal_server_error_spec.rb +32 -16
  52. data/spec/praxis/restful_routing_config_spec.rb +15 -2
  53. data/spec/praxis/router_spec.rb +5 -3
  54. data/spec/praxis/stats_spec.rb +9 -0
  55. data/spec/praxis_mapper_plugin_spec.rb +71 -0
  56. data/spec/spec_app/app/controllers/instances.rb +12 -0
  57. data/spec/spec_app/app/controllers/volumes.rb +5 -0
  58. data/spec/spec_app/app/models/person.rb +3 -0
  59. data/spec/spec_app/config/active_record.yml +2 -0
  60. data/spec/spec_app/config/authentication.yml +3 -0
  61. data/spec/spec_app/config/authorization.yml +4 -0
  62. data/spec/spec_app/config/environment.rb +28 -1
  63. data/spec/spec_app/config/praxis_mapper.yml +6 -0
  64. data/spec/spec_app/config/sequel_model.yml +2 -0
  65. data/spec/spec_app/config/stats.yml +8 -0
  66. data/spec/spec_app/config/stats.yml.dis +8 -0
  67. data/spec/spec_app/design/resources/instances.rb +53 -16
  68. data/spec/spec_app/design/resources/volumes.rb +13 -2
  69. data/spec/spec_helper.rb +14 -0
  70. data/spec/support/spec_authentication_plugin.rb +126 -0
  71. data/spec/support/spec_authorization_plugin.rb +95 -0
  72. data/spec/support/spec_praxis_mapper_plugin.rb +157 -0
  73. data/tasks/loader.thor +6 -0
  74. data/tasks/thor/app.rb +48 -0
  75. data/tasks/thor/example.rb +283 -0
  76. data/tasks/thor/templates/generator/empty_app/.gitignore +3 -0
  77. data/tasks/thor/templates/generator/empty_app/.rspec +1 -0
  78. data/tasks/thor/templates/generator/empty_app/Gemfile +29 -0
  79. data/tasks/thor/templates/generator/empty_app/Guardfile +3 -0
  80. data/tasks/thor/templates/generator/empty_app/README.md +4 -0
  81. data/tasks/thor/templates/generator/empty_app/Rakefile +25 -0
  82. data/tasks/thor/templates/generator/empty_app/app/models/.empty_directory +0 -0
  83. data/tasks/thor/templates/generator/empty_app/app/models/.gitkeep +0 -0
  84. data/tasks/thor/templates/generator/empty_app/app/responses/.empty_directory +0 -0
  85. data/tasks/thor/templates/generator/empty_app/app/responses/.gitkeep +0 -0
  86. data/tasks/thor/templates/generator/empty_app/app/v1/controllers/.empty_directory +0 -0
  87. data/tasks/thor/templates/generator/empty_app/app/v1/controllers/.gitkeep +0 -0
  88. data/tasks/thor/templates/generator/empty_app/config.ru +7 -0
  89. data/tasks/thor/templates/generator/empty_app/config/environment.rb +17 -0
  90. data/tasks/thor/templates/generator/empty_app/config/rainbows.rb +57 -0
  91. data/tasks/thor/templates/generator/empty_app/design/api.rb +0 -0
  92. data/tasks/thor/templates/generator/empty_app/design/response_templates/.empty_directory +0 -0
  93. data/tasks/thor/templates/generator/empty_app/design/response_templates/.gitkeep +0 -0
  94. data/tasks/thor/templates/generator/empty_app/design/v1/media_types/.empty_directory +0 -0
  95. data/tasks/thor/templates/generator/empty_app/design/v1/media_types/.gitkeep +0 -0
  96. data/tasks/thor/templates/generator/empty_app/design/v1/resources/.empty_directory +0 -0
  97. data/tasks/thor/templates/generator/empty_app/design/v1/resources/.gitkeep +0 -0
  98. data/tasks/thor/templates/generator/empty_app/spec/spec_helper.rb +18 -0
  99. metadata +97 -6
  100. data/tasks/praxis_app_generator.thor +0 -307
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eb59e46b853495a60984f953e66b32484735bbe2
4
- data.tar.gz: 70a1dac56fae430f1ef83fca9879d6f1e2e85227
3
+ metadata.gz: 5925b03e5020e28fc79530a6991c3f030897996d
4
+ data.tar.gz: f301a81c0628df629ae32e805debbb653b0c2477
5
5
  SHA512:
6
- metadata.gz: faa1f7c49da063995145e55ccfefd4f74687b4522f35613783aae4990b88cf82ad8c6da79192f12d43eda39c4057c8f387435513d8ff47b837a04a3685081422
7
- data.tar.gz: aa5c73efd69e49cdde0129c7d5afcb245975b631a90313c5eb2240d50c5727c673a31a5cda3452e62e6a7e7632ba0734580ddb21c09fffc65c18fece9a8e28f9
6
+ metadata.gz: f573ece40063959ee3df1a11db4a36563acd44c68bc7ad70192f90bb8268501b4c128e504ba8080406037303a6fa5dbbf4a854d741843c80aae559df6b2a0f63
7
+ data.tar.gz: 3adb04192e7414e607482a44f2a8f9f83618dee6b0202094ee77c77d4d46ae0dd63e5579dd55e87ed76aa6212a0a294229108d8c5bd8d8bd841de417cf73fb63
data/.gitignore CHANGED
@@ -8,4 +8,3 @@ _site/
8
8
  .tmp/
9
9
  node_modules/
10
10
  *.css.map
11
-
data/CHANGELOG.md CHANGED
@@ -1,8 +1,49 @@
1
1
  # praxis changelog
2
2
 
3
- ## next
3
+ ## 0.11
4
4
 
5
- * next thing here
5
+ * `MediaTypeCollection`:
6
+ * Added support for loading `decorate`ed `Resource` associations.
7
+ * Refined and enhanced support for API versioning:
8
+ * version DSL now can take a `using` option which specifies and array of the methods are allowed: `:header`,`:params`,`:path`(new)
9
+ * if not specified, it will default to `using: [:header, :params]` (so that the version can be passed to the header OR the params)
10
+ * the new `:path` option will build the action routes by prefixing the version given a common pattern (i.e., "/v1.0/...")
11
+ * The effects of path versioning will be visible through `rake praxis:routes`
12
+ * the default api prefix pattern is ("/v(version)/") but can changed by either
13
+ * overriding ``Praxis::Request.path_version_prefix` and return the appropriate string prefix (i.e., by default this returns "/v")
14
+ * or overriding `Praxis::Request.path_version_matcher` and providing the fully custom matching regexp. This regexp must have a capture (named `version`) that would return matched version value.
15
+ * Enhanced praxis generator:
16
+ * Added a new generator (available through `praxis new app_name`) which creates a blank new app, with enough basic structure and setup to start building an API.
17
+ * Changed the example hello world generation command. Instead of `praxis generate app_name`, it is now available through `praxis example app_name`
18
+ * Changed the path lookup for the praxis directory (to not use installed gems, which could be multiple). [Issue #67]
19
+ * `ResourceDefinition`:
20
+ * Added: `action_defaults` method, to define default options for actions. May be called more than once.
21
+ * Removed: `params`, `payload`, `headers`, and `response`. Specify these inside `action_defaults` instead.
22
+ * `Application`:
23
+ * Added `middleware` method to use Rack middleware.
24
+ * `ErrorHandler`
25
+ * It is now possible to register the error handler class to be invoked when an uncaught exception is thrown by setting `Application#error_handler`.
26
+ * The default error handler writes the error and backtrace into the Praxis logger, and returns an `InternalServerError` response
27
+ * Added `Praxis::Notifications` framework backed by ActiveSupport::Notifications
28
+ * Its interface is the same as AS::Notifications (.publish, .instrument, .subscribe, and etc.)
29
+ * Each incoming rack request is instrumented as `rack.request.all`, with a payload of `{response: response}`, where `response` is the `Response` object that will be returned to the client. Internally, Praxis subscribes to this to generate timing statistics with `Praxis::Stats`.
30
+ * Additionally, each request that is dispatched to an action is instrumented as `praxis.request.all`, with a payload of `{request: request, response: response}`, where `response` is as above, and `request` is the `Request` object for the request.
31
+ * Added `Praxis::Stats` framework backed by `Harness` (i.e. a statsd interface)
32
+ * Can be configured with a collector type (fake, Statsd) and an asynchronous queue + thread
33
+ * Wraps the statsd interface: count, increment, decrement, time ...
34
+ * Added a new `decorate_docs` method to enhance generated JSON docs for actions in `ResourceDefinitions`
35
+ * Using this hook, anybody can register a block that can change/enhance the JSON structure of generated documents for any given action
36
+ * Added a brand new Plugins architecture
37
+ * Plugins can easily inject code in the Request, Controller, ResourceDefinition or ActionDefinition
38
+ * Can be instances or singletons (and will be initialized correspondingly)
39
+ * Plugins can be easily configured under a unique "config key" in the Praxis config
40
+ * See the [Plugins](http://praxis-framework.io/reference/plugins/) section in the documentation for more information.
41
+ * Added a Plugin for using the Praxis::Mapper gem
42
+ * Configurable through a simple `praxis_mapper.yml` file
43
+ * Its supports several repositories (by name)
44
+ * Each repository can be of a different type (default is sequel)
45
+ * `praxis:doc_browser` rake task now takes a port argument for specifying the port to run on, e.g. `rake praxis:doc_browser[9000]` to run on port 9000.
46
+ * Added `show_exceptions` configuration option to to control default ErrorHandler behavior.
6
47
 
7
48
  ## 0.10.0
8
49
 
@@ -22,7 +63,7 @@
22
63
  * Fixes [Issue #21](https://github.com/rightscale/praxis/issues/21)
23
64
  * Introduced `around` filters using blocks:
24
65
  * Around filters can be set wrapping any of the request stages (load, validate, action...) and might apply to only certain actions (i.e. exactly the same as the before/after filters)
25
- * Therefore they supports the same attributes as `before` and `after` filters. The only difference is that an around filter block will get an extra parameter with the block to call to continue the chain.
66
+ * Therefore they supports the same attributes as `before` and `after` filters. The only difference is that an around filter block will get an extra parameter with the block to call to continue the chain.
26
67
  * See the [Instances](https://github.com/rightscale/praxis/blob/master/spec/spec_app/app/controllers/instances.rb) controller for examples.
27
68
  * Fix: Change :created response template to take an optiona ‘location’ parameter (instead of a media_type one, since it doesn’t make sense for a 201 type response) [Issue #26](https://github.com/rightscale/praxis/issues/23)
28
69
  * Make the system be more robust in error reporting when controllers do not return a String or a Response
@@ -41,11 +82,7 @@
41
82
  * `MediaTypeCollection`. See [volume_snapshot](spec/spec_app/design/media_types/volume_snapshot.rb) in the specs for an example.
42
83
  * Added `member_view` DSL to define a view that renders the collection's members with the given view.
43
84
  * Change: Now requires all views to be explicitly defined (and will not automatically use the underlying member view if it exists). To define a view for member element (wrapping it in a collection) one can use the new member_view.
44
- * Application.setup now returns the application instance so it can be chained to other application methods.
45
- * This allows a more compact way to rackup the application: `run Praxis::Application.instance.setup`.
46
- * Request headers are now backed by `Attributor::Hash` rather than (Structs)
47
- ** Header keys are case sensitive now (although Praxis gracefully allows loading the uppercased RACK names )
48
- * Added tests for using `Attributor::Hash` attributes that can accept "extra" keys:
49
- ** See the `bulk_create` action for an example applied to `Praxis::Multipart` (which derives from `Attributor::Hash`) payload [here](https://github.com/rightscale/praxis/blob/master/spec/spec_app/design/resources/instances.rb).
50
-
85
+ *
86
+
87
+
51
88
  ## 0.9 Initial release
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gemspec
3
+ gemspec
data/Guardfile CHANGED
@@ -5,4 +5,5 @@ guard 'rspec', cmd: 'bundle exec rspec' do
5
5
  watch('spec/functional_spec.rb') { "spec/" }
6
6
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/functional_spec.rb" }
7
7
  watch(%r{^app/(.+)\.rb$}) { "spec/" }
8
+ watch(%r{^spec/support}) { 'spec/' }
8
9
  end
data/bin/praxis CHANGED
@@ -2,9 +2,38 @@
2
2
 
3
3
  require 'bundler'
4
4
 
5
- path_to_praxis = Bundler.rubygems.find_name('praxis').first.full_gem_path
6
- path_to_generator = '%s/tasks/praxis_app_generator.thor' % path_to_praxis
5
+ path_to_praxis = File.expand_path(File.dirname(File.dirname(__FILE__)))
6
+ path_to_loader = '%s/tasks/loader.thor' % path_to_praxis
7
7
 
8
- load path_to_generator
8
+ load path_to_loader
9
9
 
10
- PraxisAppGenerator.start(ARGV)
10
+ class PraxisGenerator < Thor
11
+
12
+ # Simple helper to go get the existing description for the real action
13
+ # Usage must still be provided rather than retrieved (since it is not a
14
+ # straight "usage" from the remote action when arguments are defined )
15
+ def self.desc_for( usage_string, klass, action_name, description_prefix="")
16
+ action_name = action_name.to_s
17
+ cmd = klass.commands[action_name]
18
+ raise "Error, could not find description for #{klass.name}##{action_name}" if cmd.nil?
19
+ desc usage_string, "#{description_prefix}#{cmd.description}"
20
+ end
21
+
22
+ desc_for "new APP_NAME", ::PraxisGen::App, :new
23
+ def new(app_name)
24
+ ::PraxisGen::App.start(['new' , app_name])
25
+ end
26
+
27
+ desc_for "example APP_NAME", ::PraxisGen::Example, :new
28
+ def example(app_name)
29
+ ::PraxisGen::Example.start(['new', app_name])
30
+ end
31
+
32
+ desc_for "generate APP_NAME", ::PraxisGen::Example, :new, "DEPRECATED!: "
33
+ def generate(app_name)
34
+ warn "This is a deprecated method.\nTo generate a hello world example, please use:\n praxis example #{app_name} "
35
+ end
36
+ end
37
+
38
+
39
+ PraxisGenerator.start(ARGV)
@@ -4111,9 +4111,6 @@ button.close {
4111
4111
  display: none !important;
4112
4112
  visibility: hidden !important; }
4113
4113
 
4114
- .affix {
4115
- position: fixed; }
4116
-
4117
4114
  @-ms-viewport {
4118
4115
  width: device-width; }
4119
4116
 
data/lib/praxis.rb CHANGED
@@ -3,6 +3,8 @@ require 'attributor'
3
3
  require 'praxis-mapper'
4
4
  require 'praxis-blueprints'
5
5
 
6
+ require 'active_support/concern'
7
+
6
8
  $:.unshift File.dirname(__FILE__)
7
9
 
8
10
  module Attributor
@@ -24,6 +26,7 @@ module MIME
24
26
  end
25
27
 
26
28
  module Praxis
29
+
27
30
  autoload :ActionDefinition, 'praxis/action_definition'
28
31
  autoload :ApiDefinition, 'praxis/api_definition'
29
32
  autoload :Application, 'praxis/application'
@@ -32,9 +35,11 @@ module Praxis
32
35
  autoload :Controller, 'praxis/controller'
33
36
  autoload :Callbacks, 'praxis/callbacks'
34
37
  autoload :Dispatcher, 'praxis/dispatcher'
38
+ autoload :ErrorHandler, 'praxis/error_handler'
35
39
  autoload :Exception, 'praxis/exception'
36
40
  autoload :FileGroup,'praxis/file_group'
37
41
  autoload :Plugin, 'praxis/plugin'
42
+ autoload :PluginConcern, 'praxis/plugin_concern'
38
43
  autoload :Request, 'praxis/request'
39
44
  autoload :ResourceDefinition, 'praxis/resource_definition'
40
45
  autoload :Response, 'praxis/response'
@@ -46,6 +51,11 @@ module Praxis
46
51
  autoload :Stage, 'praxis/stage'
47
52
  autoload :ContentTypeParser, 'praxis/content_type_parser'
48
53
 
54
+ autoload :Stats, 'praxis/stats'
55
+ autoload :Notifications, 'praxis/notifications'
56
+
57
+ autoload :RestfulDocGenerator, 'praxis/restful_doc_generator'
58
+
49
59
  # types
50
60
  autoload :Links, 'praxis/links'
51
61
  autoload :MediaType, 'praxis/media_type'
@@ -79,6 +89,12 @@ module Praxis
79
89
  module BootloaderStages
80
90
  autoload :FileLoader, 'praxis/bootloader_stages/file_loader'
81
91
  autoload :Environment, 'praxis/bootloader_stages/environment'
92
+
93
+ autoload :PluginLoader, 'praxis/bootloader_stages/plugin_loader'
94
+ autoload :PluginConfigPrepare, 'praxis/bootloader_stages/plugin_config_prepare'
95
+ autoload :PluginConfigLoad, 'praxis/bootloader_stages/plugin_config_load'
96
+ autoload :PluginSetup, 'praxis/bootloader_stages/plugin_setup'
97
+
82
98
  autoload :SubgroupLoader, 'praxis/bootloader_stages/subgroup_loader'
83
99
  autoload :WarnUnloadedFiles, 'praxis/bootloader_stages/warn_unloaded_files'
84
100
  autoload :Routing, 'praxis/bootloader_stages/routing'
@@ -16,6 +16,16 @@ module Praxis
16
16
  attr_reader :named_routes
17
17
  attr_reader :responses
18
18
 
19
+ class << self
20
+ attr_accessor :doc_decorations
21
+ end
22
+
23
+ @doc_decorations = []
24
+
25
+ def self.decorate_docs(&callback)
26
+ self.doc_decorations << callback
27
+ end
28
+
19
29
  def initialize(name, resource_definition, **opts, &block)
20
30
  @name = name
21
31
  @resource_definition = resource_definition
@@ -27,23 +37,8 @@ module Praxis
27
37
  end
28
38
  end
29
39
 
30
- if resource_definition.params
31
- saved_type, saved_opts, saved_block = resource_definition.params
32
- params(saved_type, saved_opts, &saved_block)
33
- end
34
-
35
- if resource_definition.payload
36
- saved_type, saved_opts, saved_block = resource_definition.payload
37
- payload(saved_type, saved_opts, &saved_block)
38
- end
39
-
40
- if resource_definition.headers
41
- saved_opts, saved_block = resource_definition.headers
42
- headers(saved_opts, &saved_block)
43
- end
44
-
45
- resource_definition.responses.each do |name, args|
46
- response(name, args)
40
+ resource_definition.action_defaults.each do |defaults|
41
+ self.instance_eval(&defaults)
47
42
  end
48
43
 
49
44
  self.instance_eval(&block) if block_given?
@@ -141,13 +136,13 @@ module Praxis
141
136
  end
142
137
  end
143
138
 
139
+
144
140
  def description(text = nil)
145
141
  @description = text if text
146
142
  @description
147
143
  end
148
144
 
149
145
 
150
-
151
146
  def describe
152
147
  {}.tap do |hash|
153
148
  hash[:description] = description
@@ -161,6 +156,9 @@ module Praxis
161
156
  memo[response.name] = response.describe
162
157
  memo
163
158
  end
159
+ self.class.doc_decorations.each do |callback|
160
+ callback.call(self, hash)
161
+ end
164
162
  end
165
163
  end
166
164
 
@@ -9,6 +9,8 @@ module Praxis
9
9
  attr_reader :router
10
10
  attr_reader :controllers
11
11
  attr_reader :resource_definitions
12
+ attr_reader :app
13
+ attr_reader :builder
12
14
 
13
15
  attr_accessor :bootloader
14
16
  attr_accessor :file_layout
@@ -16,6 +18,8 @@ module Praxis
16
18
  attr_accessor :logger
17
19
  attr_accessor :plugins
18
20
  attr_accessor :root
21
+ attr_accessor :error_handler
22
+
19
23
 
20
24
  def self.configure
21
25
  yield(self.instance)
@@ -25,11 +29,16 @@ module Praxis
25
29
  @controllers = Set.new
26
30
  @resource_definitions = Set.new
27
31
 
32
+ @error_handler = ErrorHandler.new
33
+
28
34
  @router = Router.new(self)
29
35
 
36
+ @builder = Rack::Builder.new
37
+ @app = nil
38
+
30
39
  @bootloader = Bootloader.new(self)
31
40
  @file_layout = nil
32
- @plugins = Array.new
41
+ @plugins = Hash.new
33
42
  @loaded_files = Set.new
34
43
  @config = Config.new
35
44
  @root = nil
@@ -40,11 +49,30 @@ module Praxis
40
49
  @root = Pathname.new(root).expand_path
41
50
 
42
51
  @bootloader.setup!
52
+
53
+ @builder.run(@router)
54
+ @app = @builder.to_app
55
+
56
+ Notifications.subscribe 'rack.request.all'.freeze do |name, start, finish, _id, payload|
57
+ duration = (finish - start) * 1000
58
+ Stats.timing(name, duration)
59
+
60
+ status, _, _ = payload[:response]
61
+ Stats.increment "rack.request.#{status}"
62
+ end
63
+
43
64
  self
44
65
  end
45
66
 
67
+ def middleware(middleware, *args, &block)
68
+ @builder.use(middleware, *args, &block)
69
+ end
70
+
46
71
  def call(env)
47
- self.router.call(env)
72
+ response = []
73
+ Notifications.instrument 'rack.request.all'.freeze, response: response do
74
+ response.push(*@app.call(env))
75
+ end
48
76
  end
49
77
 
50
78
  def layout(&block)
@@ -62,5 +90,6 @@ module Praxis
62
90
  def config=(config)
63
91
  @config.set(config)
64
92
  end
93
+
65
94
  end
66
95
  end
@@ -1,9 +1,9 @@
1
1
  module Praxis
2
2
 
3
-
4
3
  class Bootloader
5
4
 
6
- attr_reader :application, :stages
5
+ attr_reader :application
6
+ attr_reader :stages
7
7
 
8
8
  def initialize(application)
9
9
  @application = application
@@ -24,6 +24,9 @@ module Praxis
24
24
  # Require environment first. they define constants and environment specific settings
25
25
  stages << BootloaderStages::Environment.new(:environment, application)
26
26
 
27
+ # then setup plugins
28
+ stages << BootloaderStages::PluginLoader.new(:plugins, application)
29
+
27
30
  # then the initializers. as it is their job to ensure monkey patches and other
28
31
  # config is in place first.
29
32
  stages << BootloaderStages::FileLoader.new(:initializers, application)
@@ -71,11 +74,41 @@ module Praxis
71
74
  stages.find { |stage| stage.name == stage_name }.after(*stage_path, &block)
72
75
  end
73
76
 
74
- def use(plugin,&block)
75
- application.plugins << plugin.new(application, &block)
77
+ def use(plugin,**options, &block)
78
+ if plugin.ancestors.include?(PluginConcern)
79
+ plugin.setup!
80
+ plugin = plugin::Plugin
81
+ end
82
+
83
+ instance = if plugin.ancestors.include?(Singleton)
84
+ plugin.instance
85
+ elsif plugin.kind_of?(Class)
86
+ plugin.new
87
+ else
88
+ plugin
89
+ end
90
+
91
+ instance.application = application
92
+ instance.options.merge!(options)
93
+ instance.block = block if block_given?
94
+
95
+ if application.plugins.key?(instance.config_key)
96
+ raise "Can not use plugin: #{plugin}, another plugin is already registered with key: #{instance.config_key}"
97
+ end
98
+
99
+ if instance.config_key.nil?
100
+ raise "Error initializing plugin: #{plugin}, config_key may not be nil."
101
+ end
102
+
103
+ application.plugins[instance.config_key] = instance
104
+ instance
76
105
  end
77
106
 
78
107
  def setup!
108
+ # use the Stats and Notifications plugins by default
109
+ use Praxis::Stats
110
+ use Praxis::Notifications
111
+
79
112
  run
80
113
  end
81
114
 
@@ -1,6 +1,5 @@
1
1
  module Praxis
2
2
 
3
-
4
3
  module BootloaderStages
5
4
 
6
5
  class Environment < Stage
@@ -9,18 +8,14 @@ module Praxis
9
8
  # 1) the environment.rb file - generic stuff for all environments
10
9
  # 2) "Deployer.environment".rb - environment specific stuff
11
10
  def execute
11
+ setup_initial_config!
12
+
12
13
  env_file = application.root + "config/environment.rb"
13
14
  require env_file if File.exists? env_file
14
15
 
15
- application.plugins.each do |plugin|
16
- plugin.setup!
17
- end
18
-
19
16
  unless application.file_layout
20
17
  setup_default_layout!
21
18
  end
22
-
23
- setup_initial_config!
24
19
  end
25
20
 
26
21
  def setup_default_layout!
@@ -47,6 +42,7 @@ module Praxis
47
42
  application.config do
48
43
  attribute :praxis do
49
44
  attribute :validate_responses, Attributor::Boolean, default: false
45
+ attribute :show_exceptions, Attributor::Boolean, default: false
50
46
  end
51
47
  end
52
48
  end