praxis 0.10.1 → 0.11pre

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,20 @@
1
+ module Praxis
2
+ module BootloaderStages
3
+ class PluginConfigLoad < Stage
4
+
5
+ def execute
6
+ application.plugins.each do |config_key, plugin|
7
+ value = plugin.load_config!
8
+ object = plugin.config_attribute.load(value)
9
+
10
+ if object
11
+ application.config.send("#{config_key}=", object)
12
+ end
13
+
14
+ plugin.config = application.config.send("#{config_key}")
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ module Praxis
2
+ module BootloaderStages
3
+ class PluginConfigPrepare < Stage
4
+
5
+ def execute
6
+ application.plugins.each do |config_key, plugin|
7
+ attribute = Attributor::Attribute.new(Attributor::Struct) {}
8
+
9
+ plugin.config_attribute = attribute
10
+ plugin.prepare_config!(attribute.type)
11
+
12
+ application.config.class.attributes[config_key] = plugin.config_attribute
13
+ end
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ module Praxis
2
+
3
+
4
+ module BootloaderStages
5
+
6
+ class PluginLoader < Stage
7
+
8
+ def initialize(name, context,**opts)
9
+ super
10
+
11
+ stages << PluginConfigPrepare.new(:prepare, context, parent: self)
12
+ stages << PluginConfigLoad.new(:load, context, parent: self)
13
+ stages << PluginSetup.new(:setup, context, parent: self)
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ module Praxis
2
+ module BootloaderStages
3
+ class PluginSetup < Stage
4
+
5
+ def execute
6
+ application.plugins.each do |config_key, plugin|
7
+ plugin.setup!
8
+ end
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -4,7 +4,22 @@ module Praxis
4
4
  module BootloaderStages
5
5
 
6
6
  class Routing < Stage
7
+ class Target
8
+ attr_reader :action
9
+ def initialize(application, controller, action)
10
+ @application = application
11
+ @controller = controller
12
+ @action = action
13
+ end
14
+
15
+ def call(request)
16
+ request.action = @action
17
+ dispatcher = Dispatcher.current( application: @application)
7
18
 
19
+ dispatcher.dispatch(@controller, @action, request)
20
+ end
21
+ end
22
+
8
23
  def execute
9
24
  application.controllers.each do |controller|
10
25
  controller.definition.actions.each do |action_name, action|
@@ -20,12 +35,7 @@ module Praxis
20
35
  def target_factory(controller, action_name)
21
36
  action = controller.definition.actions.fetch(action_name)
22
37
 
23
- Proc.new do |request|
24
- request.action = action
25
- dispatcher = Dispatcher.current( application: application)
26
-
27
- dispatcher.dispatch(controller, action, request)
28
- end
38
+ Target.new(application, controller, action)
29
39
  end
30
40
 
31
41
  end
@@ -1,5 +1,3 @@
1
- require 'active_support/concern'
2
-
3
1
  module Praxis
4
2
  module Callbacks
5
3
  extend ::ActiveSupport::Concern
data/lib/praxis/config.rb CHANGED
@@ -28,6 +28,7 @@ module Praxis
28
28
  # attribute :two String
29
29
  # end
30
30
  # end
31
+
31
32
  # ...or using this way too (equivalent)
32
33
  # define(:app) do
33
34
  # attribute :two, String
@@ -69,7 +70,7 @@ module Praxis
69
70
  context = ['Application', 'config']
70
71
 
71
72
  begin
72
- @value = @attribute.load(config, context)
73
+ @value = @attribute.load(config, context, recurse: true)
73
74
  rescue Attributor::AttributorException => e
74
75
  raise Exceptions::ConfigLoad.new(exception: e)
75
76
  end
@@ -84,7 +85,7 @@ module Praxis
84
85
  def get
85
86
  @value ||= begin
86
87
  context = ['Application','config'].freeze
87
- @attribute.load({},context)
88
+ @attribute.load({},context, recurse: true)
88
89
  end
89
90
  end
90
91
 
@@ -7,7 +7,10 @@ module Praxis
7
7
  }.freeze
8
8
 
9
9
  class Dispatcher
10
- attr_reader :controller, :action, :request, :application
10
+ attr_reader :controller
11
+ attr_reader :action
12
+ attr_reader :request
13
+ attr_reader :application
11
14
 
12
15
  @deferred_callbacks = Hash.new do |hash,stage|
13
16
  hash[stage] = {before: [], after:[]}
@@ -70,26 +73,35 @@ module Praxis
70
73
  @action = action
71
74
  @request = request
72
75
 
73
- @stages.each do |stage|
74
- result = stage.run
75
- case result
76
- when Response
77
- return result.finish
76
+ payload = {request: request, response: nil}
77
+
78
+ Notifications.instrument 'praxis.request.all'.freeze, payload do
79
+ # the response stage must be the final stage in the list
80
+ *stages, response_stage = @stages
81
+
82
+ stages.each do |stage|
83
+ result = stage.run
84
+ case result
85
+ when Response
86
+ controller.response = result
87
+ break
88
+ end
78
89
  end
79
- end
80
90
 
81
- controller.response.finish
91
+ response_stage.run
92
+
93
+ payload[:response] = controller.response
94
+ controller.response.finish
95
+ end
82
96
  rescue => e
83
- response = Responses::InternalServerError.new(error: e)
84
- response.request = controller.request
85
- response.finish
97
+ @application.error_handler.handle!(request, e)
86
98
  ensure
87
99
  @controller = nil
88
100
  @action = nil
89
101
  @request = nil
90
102
  end
91
-
92
-
103
+
104
+
93
105
  def reset_cache!
94
106
  return unless Praxis::Blueprint.caching_enabled?
95
107
 
@@ -0,0 +1,16 @@
1
+ module Praxis
2
+ class ErrorHandler
3
+
4
+ def handle!(request, error)
5
+ Application.instance.logger.error error.inspect
6
+ error.backtrace.each do |line|
7
+ Application.instance.logger.error line
8
+ end
9
+
10
+ response = Responses::InternalServerError.new(error: error)
11
+ response.request = request
12
+ response.finish
13
+ end
14
+
15
+ end
16
+ end
data/lib/praxis/links.rb CHANGED
@@ -63,7 +63,7 @@ module Praxis
63
63
  end
64
64
 
65
65
  define_method(name) do
66
- value = @object.send(using)
66
+ value = @object.__send__(using)
67
67
  return value if value.nil? || value.kind_of?(attribute.type)
68
68
  attribute.type.new(value)
69
69
  end
@@ -72,13 +72,13 @@ module Praxis
72
72
  unless name == using
73
73
  @attribute.type.instance_eval do
74
74
  define_method(using) do
75
- self.send(name)
75
+ self.__send__(name)
76
76
  end
77
77
  end
78
78
 
79
79
  @reference.attribute.type.instance_eval do
80
80
  define_method(using) do
81
- self.send(name)
81
+ self.__send__(name)
82
82
  end
83
83
  end
84
84
  end
@@ -103,12 +103,17 @@ module Praxis
103
103
  @reference.attribute.type.instance_eval do
104
104
  define_method(using) do
105
105
  return nil unless attributes[:links]
106
- attributes[:links].send(name)
106
+ attributes[:links].__send__(name)
107
107
  end
108
108
  end
109
109
  end
110
110
  end
111
111
 
112
+ def self.validate(*args)
113
+ # FIXME: what to validate for links?
114
+ []
115
+ end
116
+
112
117
  end
113
118
 
114
119
  end
@@ -83,7 +83,7 @@ module Praxis
83
83
  when Hash
84
84
  # Need to parse/deserialize first
85
85
  self.new(self.attribute.load(value,context, **options))
86
- when Array
86
+ when Array, Praxis::Mapper::ResourceDecorator
87
87
  object = self.attribute.load({})
88
88
  object._members = value.collect { |subvalue| @member_attribute.load(subvalue) }
89
89
  self.new(object)
@@ -116,8 +116,7 @@ module Praxis
116
116
 
117
117
  def validate(context=Attributor::DEFAULT_ROOT_CONTEXT)
118
118
  errors = super
119
-
120
- @object.each_with_object(errors) do |member, errors|
119
+ self.each_with_object(errors) do |member, errors|
121
120
  errors.push(*member.validate(context))
122
121
  end
123
122
  end
@@ -0,0 +1,41 @@
1
+ require 'active_support/notifications'
2
+
3
+ require 'singleton'
4
+
5
+ module Praxis
6
+
7
+ module Notifications
8
+ include Praxis::PluginConcern
9
+
10
+ class Plugin < Praxis::Plugin
11
+ include Singleton
12
+
13
+ def config_key
14
+ :notifications # 'praxis.notifications'
15
+ end
16
+
17
+ end
18
+
19
+ def self.publish(name, *args)
20
+ ActiveSupport::Notifications.publish(name, *args)
21
+ end
22
+
23
+ def self.instrument(name, payload = {}, &block)
24
+ ActiveSupport::Notifications.instrument(name, payload, &block)
25
+ end
26
+
27
+ def self.subscribe(*args, &block)
28
+ ActiveSupport::Notifications.subscribe(*args, &block)
29
+ end
30
+
31
+ def self.subscribed(callback, *args, &block)
32
+ ActiveSupport::Notifications.subscribed(callback, *args, &block)
33
+ end
34
+
35
+ def self.unsubscribe(subscriber_or_name)
36
+ ActiveSupport::Notifications.unsubscribe(subscriber_or_name)
37
+ end
38
+
39
+
40
+ end
41
+ end
data/lib/praxis/plugin.rb CHANGED
@@ -3,15 +3,26 @@ module Praxis
3
3
  # one instance is created per use.
4
4
  class Plugin
5
5
 
6
- attr_reader :application, :block
6
+ attr_accessor :application
7
+ attr_accessor :block
8
+ attr_accessor :config
9
+ attr_accessor :config_attribute
7
10
 
8
- def initialize(application, &block)
9
- @application = application
10
- @block = block
11
+ def options
12
+ @options ||= {}
11
13
  end
12
14
 
13
- def config
14
- @application.config
15
+ def config_key
16
+ end
17
+
18
+ def prepare_config!(node)
19
+ end
20
+
21
+ def load_config!
22
+ return unless options.has_key?(:config_file)
23
+ return {} unless (application.root + options[:config_file]).exist?
24
+
25
+ YAML.load_file(application.root + options[:config_file])
15
26
  end
16
27
 
17
28
  def setup!
@@ -25,6 +36,5 @@ module Praxis
25
36
  application.bootloader.before(stage,&block)
26
37
  end
27
38
 
28
-
29
39
  end
30
- end
40
+ end
@@ -0,0 +1,40 @@
1
+ module Praxis
2
+
3
+ module PluginConcern
4
+ extend ::ActiveSupport::Concern
5
+
6
+ included do
7
+ @setup = false
8
+ end
9
+
10
+ module ClassMethods
11
+ PLUGIN_CLASSES = [
12
+ :Request,
13
+ :Controller,
14
+ :ResourceDefinition,
15
+ :ActionDefinition
16
+ ]
17
+
18
+ def setup!
19
+ return if @setup
20
+
21
+ PLUGIN_CLASSES.each do |name|
22
+ if self.constants.include?(name)
23
+ inject!(name)
24
+ end
25
+ end
26
+
27
+ @setup = true
28
+ end
29
+
30
+ def inject!(name)
31
+ plugin = self.const_get(name)
32
+ praxis = Praxis.const_get(name)
33
+
34
+ praxis.include(plugin)
35
+ end
36
+
37
+ end
38
+ end
39
+
40
+ end
@@ -1,15 +1,14 @@
1
1
  module Praxis
2
2
 
3
3
  class Request
4
- attr_reader :env, :query, :version
4
+ attr_reader :env, :query
5
5
  attr_accessor :route_params, :action
6
6
 
7
-
8
7
  def initialize(env)
9
8
  @env = env
10
9
  @query = Rack::Utils.parse_nested_query(env['QUERY_STRING'.freeze])
11
10
  @route_params = {}
12
- load_version
11
+ @path_version_matcher = path_version_matcher
13
12
  end
14
13
 
15
14
  def content_type
@@ -65,10 +64,31 @@ module Praxis
65
64
  self.raw_params
66
65
  self.raw_payload
67
66
  end
68
-
69
- def load_version
70
- @version = env.fetch("HTTP_X_API_VERSION".freeze,
71
- @query.fetch('api_version'.freeze, 'n/a'.freeze))
67
+
68
+ def self.path_version_prefix
69
+ "/v".freeze
70
+ end
71
+
72
+ def path_version_matcher
73
+ %r{^#{Request.path_version_prefix}(?<version>[^\/]+)\/}.freeze
74
+ end
75
+
76
+ def version(using: [:header,:params].freeze)
77
+ result = nil
78
+ Array(using).find do |mode|
79
+ case mode
80
+ when :header ;
81
+ result = env["HTTP_X_API_VERSION".freeze]
82
+ when :params ;
83
+ result = @query['api_version'.freeze]
84
+ when :path ;
85
+ m = self.path.match(@path_version_matcher)
86
+ result = m[:version] unless m.nil?
87
+ else
88
+ raise "Unknown method for retrieving the API version: #{mode}"
89
+ end
90
+ end
91
+ return result || 'n/a'.freeze
72
92
  end
73
93
 
74
94
  def load_headers(context)