praxis 0.21 → 2.0.pre.3
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.
- checksums.yaml +4 -4
- data/.travis.yml +8 -15
- data/CHANGELOG.md +328 -299
- data/CONTRIBUTING.md +4 -4
- data/README.md +11 -9
- data/lib/api_browser/app/js/directives/attribute_table.js +2 -1
- data/lib/api_browser/app/js/directives/conditional_requirements.js +13 -0
- data/lib/api_browser/app/js/directives/type_placeholder.js +10 -1
- data/lib/api_browser/app/js/factories/normalize_attributes.js +4 -2
- data/lib/api_browser/app/js/factories/template_for.js +5 -2
- data/lib/api_browser/app/js/filters/has_requirement.js +14 -0
- data/lib/api_browser/app/js/filters/tag_requirement.js +13 -0
- data/lib/api_browser/app/sass/praxis.scss +11 -0
- data/lib/api_browser/app/views/action.html +2 -2
- data/lib/api_browser/app/views/directives/attribute_description/member_options.html +2 -2
- data/lib/api_browser/app/views/directives/attribute_table.html +1 -1
- data/lib/api_browser/app/views/type.html +1 -1
- data/lib/api_browser/app/views/type/details.html +2 -2
- data/lib/api_browser/app/views/types/embedded/array.html +2 -0
- data/lib/api_browser/app/views/types/embedded/default.html +3 -1
- data/lib/api_browser/app/views/types/embedded/requirements.html +6 -0
- data/lib/api_browser/app/views/types/embedded/single_req.html +9 -0
- data/lib/api_browser/app/views/types/embedded/struct.html +14 -2
- data/lib/api_browser/app/views/types/standalone/array.html +1 -1
- data/lib/api_browser/app/views/types/standalone/struct.html +2 -1
- data/lib/api_browser/package.json +1 -1
- data/lib/praxis.rb +9 -3
- data/lib/praxis/action_definition.rb +1 -1
- data/lib/praxis/action_definition/headers_dsl_compiler.rb +1 -1
- data/lib/praxis/application.rb +1 -9
- data/lib/praxis/bootloader.rb +1 -4
- data/lib/praxis/config.rb +1 -1
- data/lib/praxis/dispatcher.rb +10 -6
- data/lib/praxis/docs/generator.rb +2 -1
- data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +180 -0
- data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +273 -0
- data/lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb +125 -0
- data/lib/praxis/extensions/field_selection.rb +1 -9
- data/lib/praxis/extensions/field_selection/active_record_query_selector.rb +51 -0
- data/lib/praxis/extensions/field_selection/sequel_query_selector.rb +61 -0
- data/lib/praxis/extensions/rails_compat.rb +2 -0
- data/lib/praxis/extensions/rails_compat/request_methods.rb +19 -0
- data/lib/praxis/handlers/xml.rb +1 -1
- data/lib/praxis/mapper/active_model_compat.rb +98 -0
- data/lib/praxis/mapper/resource.rb +242 -0
- data/lib/praxis/mapper/selector_generator.rb +149 -0
- data/lib/praxis/mapper/sequel_compat.rb +76 -0
- data/lib/praxis/media_type_identifier.rb +2 -1
- data/lib/praxis/middleware_app.rb +20 -2
- data/lib/praxis/multipart/parser.rb +14 -2
- data/lib/praxis/notifications.rb +1 -1
- data/lib/praxis/plugins/mapper_plugin.rb +64 -0
- data/lib/praxis/plugins/rails_plugin.rb +104 -0
- data/lib/praxis/request.rb +7 -1
- data/lib/praxis/request_superclassing.rb +11 -0
- data/lib/praxis/resource_definition.rb +5 -5
- data/lib/praxis/response.rb +1 -1
- data/lib/praxis/route.rb +1 -1
- data/lib/praxis/routing_config.rb +1 -1
- data/lib/praxis/trait.rb +1 -1
- data/lib/praxis/types/media_type_common.rb +2 -2
- data/lib/praxis/types/multipart.rb +1 -1
- data/lib/praxis/types/multipart_array.rb +2 -2
- data/lib/praxis/types/multipart_array/part_definition.rb +1 -1
- data/lib/praxis/version.rb +1 -1
- data/praxis.gemspec +14 -13
- data/spec/functional_spec.rb +4 -7
- data/spec/praxis/action_definition_spec.rb +1 -1
- data/spec/praxis/application_spec.rb +1 -1
- data/spec/praxis/collection_spec.rb +3 -2
- data/spec/praxis/config_spec.rb +2 -2
- data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +106 -0
- data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +147 -0
- data/spec/praxis/extensions/field_selection/support/spec_resources_active_model.rb +130 -0
- data/spec/praxis/extensions/field_selection/support/spec_resources_sequel.rb +106 -0
- data/spec/praxis/handlers/xml_spec.rb +2 -2
- data/spec/praxis/mapper/resource_spec.rb +169 -0
- data/spec/praxis/mapper/selector_generator_spec.rb +293 -0
- data/spec/praxis/media_type_spec.rb +0 -10
- data/spec/praxis/middleware_app_spec.rb +29 -9
- data/spec/praxis/request_stages/action_spec.rb +8 -1
- data/spec/praxis/response_definition_spec.rb +7 -4
- data/spec/praxis/response_spec.rb +1 -1
- data/spec/praxis/responses/internal_server_error_spec.rb +2 -2
- data/spec/praxis/responses/validation_error_spec.rb +2 -2
- data/spec/praxis/router_spec.rb +1 -1
- data/spec/spec_app/app/controllers/instances.rb +1 -1
- data/spec/spec_app/config/environment.rb +3 -21
- data/spec/spec_helper.rb +11 -15
- data/spec/support/be_deep_equal_matcher.rb +39 -0
- data/spec/support/spec_resources.rb +124 -0
- data/tasks/thor/templates/generator/empty_app/Gemfile +3 -3
- metadata +102 -77
- data/.ruby-version +0 -1
- data/lib/praxis/extensions/mapper_selectors.rb +0 -16
- data/lib/praxis/media_type_collection.rb +0 -127
- data/lib/praxis/plugins/praxis_mapper_plugin.rb +0 -246
- data/lib/praxis/stats.rb +0 -113
- data/spec/praxis/media_type_collection_spec.rb +0 -157
- data/spec/praxis/plugins/praxis_mapper_plugin_spec.rb +0 -142
- data/spec/praxis/stats_spec.rb +0 -9
- data/spec/spec_app/app/models/person.rb +0 -3
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
module Praxis::Mapper
|
|
2
|
+
|
|
3
|
+
class SelectorGeneratorNode
|
|
4
|
+
attr_reader :select, :model, :resource, :tracks
|
|
5
|
+
|
|
6
|
+
def initialize(resource)
|
|
7
|
+
@resource = resource
|
|
8
|
+
|
|
9
|
+
@select = Set.new
|
|
10
|
+
@select_star = false
|
|
11
|
+
@tracks = Hash.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def add(fields)
|
|
15
|
+
fields.each do |name, field|
|
|
16
|
+
map_property(name, field)
|
|
17
|
+
end
|
|
18
|
+
self
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def map_property(name, fields)
|
|
22
|
+
if resource.properties.key?(name)
|
|
23
|
+
add_property(name, fields)
|
|
24
|
+
elsif resource.model._praxis_associations.key?(name)
|
|
25
|
+
add_association(name, fields)
|
|
26
|
+
else
|
|
27
|
+
add_select(name)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def add_association(name, fields)
|
|
32
|
+
|
|
33
|
+
association = resource.model._praxis_associations.fetch(name) do
|
|
34
|
+
raise "missing association for #{resource} with name #{name}"
|
|
35
|
+
end
|
|
36
|
+
associated_resource = resource.model_map[association[:model]]
|
|
37
|
+
unless associated_resource
|
|
38
|
+
raise "Whoops! could not find a resource associated with model #{association[:model]} (root resource #{resource})"
|
|
39
|
+
end
|
|
40
|
+
# Add the required columns in this model to make sure the association can be loaded
|
|
41
|
+
association[:local_key_columns].each {|col| add_select(col) }
|
|
42
|
+
|
|
43
|
+
node = SelectorGeneratorNode.new(associated_resource)
|
|
44
|
+
if association[:remote_key_columns].nil?
|
|
45
|
+
binding.pry
|
|
46
|
+
puts association
|
|
47
|
+
end
|
|
48
|
+
unless association[:remote_key_columns].empty?
|
|
49
|
+
# Make sure we add the required columns for this association to the remote model query
|
|
50
|
+
fields = {} if fields == true
|
|
51
|
+
new_fields_as_hash = association[:remote_key_columns].each_with_object({}) do|name, hash|
|
|
52
|
+
hash[name] = true
|
|
53
|
+
end
|
|
54
|
+
fields.merge!(new_fields_as_hash)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
node.add(fields) unless fields == true
|
|
58
|
+
|
|
59
|
+
self.merge_track(name, node)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def add_select(name)
|
|
63
|
+
return @select_star = true if name == :*
|
|
64
|
+
return if @select_star
|
|
65
|
+
@select.add name
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def add_property(name, fields)
|
|
69
|
+
dependencies = resource.properties[name][:dependencies]
|
|
70
|
+
# Always add the underlying association if we're overriding the name...
|
|
71
|
+
add_association(name, fields) if resource.model._praxis_associations.key?(name)
|
|
72
|
+
if dependencies
|
|
73
|
+
dependencies.each do |dependency|
|
|
74
|
+
# To detect recursion, let's allow mapping depending fields to the same name of the property
|
|
75
|
+
# but properly detecting if it's a real association...in which case we've already added it above
|
|
76
|
+
if dependency == name
|
|
77
|
+
add_select(name) unless resource.model._praxis_associations.key?(name)
|
|
78
|
+
else
|
|
79
|
+
apply_dependency(dependency)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
head, *tail = resource.properties[name][:through]
|
|
85
|
+
return if head.nil?
|
|
86
|
+
|
|
87
|
+
new_fields = tail.reverse.inject(fields) do |thing, step|
|
|
88
|
+
{step => thing}
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
add_association(head, new_fields)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def apply_dependency(dependency)
|
|
95
|
+
case dependency
|
|
96
|
+
when Symbol
|
|
97
|
+
map_property(dependency, true)
|
|
98
|
+
when String
|
|
99
|
+
head, tail = dependency.split('.').collect(&:to_sym)
|
|
100
|
+
raise "String dependencies can not be singular" if tail.nil?
|
|
101
|
+
|
|
102
|
+
add_association(head, {tail => true})
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def merge_track( track_name, node )
|
|
107
|
+
raise "Cannot merge another node for association #{track_name}: incompatible model" unless node.model == self.model
|
|
108
|
+
|
|
109
|
+
existing = self.tracks[track_name]
|
|
110
|
+
if existing
|
|
111
|
+
node.select.each do|col_name|
|
|
112
|
+
existing.add_select(col_name)
|
|
113
|
+
end
|
|
114
|
+
node.tracks.each do |name, n|
|
|
115
|
+
existing.merge(name, n)
|
|
116
|
+
end
|
|
117
|
+
else
|
|
118
|
+
self.tracks[track_name] = node
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def dump
|
|
124
|
+
hash = {}
|
|
125
|
+
hash[:model] = resource.model
|
|
126
|
+
if !@select.empty? || @select_star
|
|
127
|
+
hash[:columns] = @select_star ? [ :* ] : @select.to_a
|
|
128
|
+
end
|
|
129
|
+
unless @tracks.empty?
|
|
130
|
+
hash[:tracks] = @tracks.each_with_object({}) {|(name, node), hash| hash[name] = node.dump }
|
|
131
|
+
end
|
|
132
|
+
hash
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Generates a set of selectors given a resource and
|
|
137
|
+
# list of resource attributes.
|
|
138
|
+
class SelectorGenerator
|
|
139
|
+
# Entry point
|
|
140
|
+
def add(resource, fields)
|
|
141
|
+
@root = SelectorGeneratorNode.new(resource)
|
|
142
|
+
@root.add(fields)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def selectors
|
|
146
|
+
@root
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require 'active_support/concern'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module Praxis::Mapper
|
|
5
|
+
module SequelCompat
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
attr_accessor :_resource
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module ClassMethods
|
|
13
|
+
def _filter_query_builder_class
|
|
14
|
+
Praxis::Extensions::SequelFilterQueryBuilder
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def _field_selector_query_builder_class
|
|
18
|
+
Praxis::Extensions::FieldSelection::SequelQuerySelector
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def _praxis_associations
|
|
22
|
+
orig = self.association_reflections.clone
|
|
23
|
+
orig.each do |k,v|
|
|
24
|
+
v[:model] = v.associated_class
|
|
25
|
+
v[:local_key_columns] = local_columns_used_for_the_association(v[:type], v)
|
|
26
|
+
v[:remote_key_columns] = remote_columns_used_for_the_association(v[:type], v)
|
|
27
|
+
if v.respond_to?(:primary_key)
|
|
28
|
+
v[:primary_key] = v.primary_key
|
|
29
|
+
else
|
|
30
|
+
# FIXME: figure out exactly what to do here.
|
|
31
|
+
# not super critical, as we can't track these associations
|
|
32
|
+
# directly, but it would be nice to traverse these
|
|
33
|
+
# properly.
|
|
34
|
+
v[:primary_key] = :unsupported
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
orig
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
def local_columns_used_for_the_association(type, assoc_reflection)
|
|
42
|
+
case type
|
|
43
|
+
when :one_to_many
|
|
44
|
+
# The associated table (or middle table if many to many) will point to us by PK
|
|
45
|
+
assoc_reflection[:primary_key_columns]
|
|
46
|
+
when :many_to_one
|
|
47
|
+
# We have the FKs to the associated model
|
|
48
|
+
assoc_reflection[:keys]
|
|
49
|
+
when :many_to_many
|
|
50
|
+
# The middle table if many to many) will point to us by key (usually the PK, but not always)
|
|
51
|
+
assoc_reflection[:left_primary_keys]
|
|
52
|
+
else
|
|
53
|
+
raise "association type #{type} not supported"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def remote_columns_used_for_the_association(type, assoc_reflection)
|
|
58
|
+
case type
|
|
59
|
+
when :one_to_many
|
|
60
|
+
# The columns in the associated table that will point back to the original association
|
|
61
|
+
assoc_reflection[:keys]
|
|
62
|
+
when :many_to_one
|
|
63
|
+
# The columns in the associated table that the children will point to (usually the PK, but not always) ??
|
|
64
|
+
[assoc_reflection.associated_class.primary_key]
|
|
65
|
+
when :many_to_many
|
|
66
|
+
# The middle table if many to many will point to us by key (usually the PK, but not always) ??
|
|
67
|
+
[assoc_reflection.associated_class.primary_key]
|
|
68
|
+
else
|
|
69
|
+
raise "association type #{type} not supported"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -146,7 +146,8 @@ module Praxis
|
|
|
146
146
|
if self.parameters.empty?
|
|
147
147
|
self
|
|
148
148
|
else
|
|
149
|
-
|
|
149
|
+
val = {type: self.type, subtype: self.subtype, suffix: self.suffix}
|
|
150
|
+
MediaTypeIdentifier.load(val)
|
|
150
151
|
end
|
|
151
152
|
end
|
|
152
153
|
|
|
@@ -5,15 +5,33 @@ module Praxis
|
|
|
5
5
|
|
|
6
6
|
# Initialize the application instance with the desired args, and return the wrapping class.
|
|
7
7
|
def self.for( **args )
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
Class.new(self) do
|
|
9
|
+
@args = args
|
|
10
|
+
@setup_done = false
|
|
11
|
+
def self.name
|
|
12
|
+
'MiddlewareApp'
|
|
13
|
+
end
|
|
14
|
+
def self.args
|
|
15
|
+
@args
|
|
16
|
+
end
|
|
17
|
+
def self.setup_done
|
|
18
|
+
@setup_done
|
|
19
|
+
end
|
|
20
|
+
def self.setup
|
|
21
|
+
@setup_done = true
|
|
22
|
+
Praxis::Application.instance.setup(**@args)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
10
25
|
end
|
|
11
26
|
|
|
12
27
|
def initialize( inner )
|
|
13
28
|
@target = inner
|
|
29
|
+
@setup_done = false
|
|
14
30
|
end
|
|
15
31
|
|
|
16
32
|
def call(env)
|
|
33
|
+
self.class.setup unless self.class.setup_done
|
|
34
|
+
|
|
17
35
|
result = Praxis::Application.instance.call(env)
|
|
18
36
|
|
|
19
37
|
unless ( [404,405].include?(result[0].to_i) && result[1]['X-Cascade'] == 'pass' )
|
|
@@ -22,6 +22,7 @@ module Praxis
|
|
|
22
22
|
TERMINAL_CRLF = /\r\n$/.freeze
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
PARAMS_BUF_SIZE = 65536 # Same as implicitly in rack 1.x
|
|
25
26
|
BUFSIZE = 16384
|
|
26
27
|
|
|
27
28
|
def self.parse(headers,body)
|
|
@@ -87,9 +88,9 @@ module Praxis
|
|
|
87
88
|
|
|
88
89
|
@buf = ""
|
|
89
90
|
|
|
90
|
-
@params =
|
|
91
|
+
@params = new_params
|
|
91
92
|
|
|
92
|
-
@boundary_size =
|
|
93
|
+
@boundary_size = @boundary.bytesize + EOL.size
|
|
93
94
|
|
|
94
95
|
if @content_length = @headers['Content-Length']
|
|
95
96
|
@content_length = @content_length.to_i
|
|
@@ -98,6 +99,17 @@ module Praxis
|
|
|
98
99
|
true
|
|
99
100
|
end
|
|
100
101
|
|
|
102
|
+
if Rack.const_defined?(:RELEASE) && Rack::RELEASE[0] == '2'
|
|
103
|
+
# Rack 2 requires the buffer size
|
|
104
|
+
def new_params
|
|
105
|
+
Rack::Utils::KeySpaceConstrainedParams.new(PARAMS_BUF_SIZE)
|
|
106
|
+
end
|
|
107
|
+
else
|
|
108
|
+
def new_params
|
|
109
|
+
Rack::Utils::KeySpaceConstrainedParams.new
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
101
113
|
def full_boundary
|
|
102
114
|
@boundary + EOL
|
|
103
115
|
end
|
data/lib/praxis/notifications.rb
CHANGED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
require 'singleton'
|
|
2
|
+
require 'praxis/extensions/attribute_filtering/filtering_params'
|
|
3
|
+
|
|
4
|
+
module Praxis
|
|
5
|
+
module Plugins
|
|
6
|
+
module MapperPlugin
|
|
7
|
+
include Praxis::PluginConcern
|
|
8
|
+
|
|
9
|
+
class Plugin < Praxis::Plugin
|
|
10
|
+
include Singleton
|
|
11
|
+
|
|
12
|
+
def config_key
|
|
13
|
+
:mapper
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def load_config!
|
|
17
|
+
{} # override the default one, since we don't necessarily want to configure it via a yaml file.
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def prepare_config!(node)
|
|
21
|
+
node.attributes do
|
|
22
|
+
attribute :debug_queries, Attributor::Boolean, default: false,
|
|
23
|
+
description: 'Weather or not to log debug information about queries executed in the build_query automation module'
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
module Controller
|
|
29
|
+
extend ActiveSupport::Concern
|
|
30
|
+
|
|
31
|
+
included do
|
|
32
|
+
include Praxis::Extensions::FieldExpansion
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def set_selectors
|
|
36
|
+
return unless self.media_type.respond_to?(:domain_model) &&
|
|
37
|
+
self.media_type.domain_model < Praxis::Mapper::Resource
|
|
38
|
+
|
|
39
|
+
resolved = Praxis::MediaType::FieldResolver.resolve(self.media_type, self.expanded_fields)
|
|
40
|
+
selector_generator.add(self.media_type.domain_model, resolved)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def build_query(base_query) # rubocop:disable Metrics/AbcSize
|
|
44
|
+
domain_model = self.media_type&.domain_model
|
|
45
|
+
raise "No domain model defined for #{self.name}. Cannot use the attribute filtering helpers without it" unless domain_model
|
|
46
|
+
|
|
47
|
+
filters = request.params.filters if request.params&.respond_to?(:filters)
|
|
48
|
+
base_query = domain_model.craft_filter_query( base_query , filters: filters )
|
|
49
|
+
|
|
50
|
+
base_query = domain_model.craft_field_selection_query(base_query, selectors: selector_generator.selectors)
|
|
51
|
+
|
|
52
|
+
# TODO: handle pagination and ordering
|
|
53
|
+
base_query
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def selector_generator
|
|
57
|
+
@selector_generator ||= Praxis::Mapper::SelectorGenerator.new
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
require 'praxis/plugin'
|
|
2
|
+
require 'praxis/plugin_concern'
|
|
3
|
+
|
|
4
|
+
module Praxis
|
|
5
|
+
module Plugins
|
|
6
|
+
module RailsPlugin
|
|
7
|
+
include Praxis::PluginConcern
|
|
8
|
+
|
|
9
|
+
class Plugin < Praxis::Plugin
|
|
10
|
+
|
|
11
|
+
def setup!
|
|
12
|
+
require 'praxis/dispatcher'
|
|
13
|
+
enable_action_controller_instrumentation
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
def enable_action_controller_instrumentation
|
|
18
|
+
Praxis::Dispatcher.class_eval do
|
|
19
|
+
# Wrap the original action dispatch with a method that instruments rails-expected bits...
|
|
20
|
+
alias_method :orig_instrumented_dispatch, :instrumented_dispatch
|
|
21
|
+
|
|
22
|
+
def instrumented_dispatch( praxis_payload )
|
|
23
|
+
rails_payload = {
|
|
24
|
+
:controller => controller.class.name,
|
|
25
|
+
:action => action.name,
|
|
26
|
+
:params => ( (request.params) ? request.params.dump : {} ),
|
|
27
|
+
:method => request.verb,
|
|
28
|
+
:path => (request.fullpath rescue "unknown")
|
|
29
|
+
}
|
|
30
|
+
Praxis::Notifications.instrument("start_processing.action_controller", rails_payload.dup)
|
|
31
|
+
|
|
32
|
+
Praxis::Notifications.instrument 'process_action.action_controller' do |data|
|
|
33
|
+
begin
|
|
34
|
+
res = orig_instrumented_dispatch(praxis_payload)
|
|
35
|
+
# TODO: also add the db_runtime and view_runtime values...
|
|
36
|
+
data[:status] = res[0]
|
|
37
|
+
res
|
|
38
|
+
ensure
|
|
39
|
+
# Append DB runtime to payload
|
|
40
|
+
#data[:db_runtime] = 999
|
|
41
|
+
# Append rendering time to payload
|
|
42
|
+
#data[:view_runtime] = 123
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
module Request
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
module Controller
|
|
54
|
+
extend ActiveSupport::Concern
|
|
55
|
+
|
|
56
|
+
# Throw in some basic and expected controller methods
|
|
57
|
+
|
|
58
|
+
# Expose a rails-version of params from the controller
|
|
59
|
+
# Avoid using them explicitly in your controllers though. Use request.params object instead, as they are
|
|
60
|
+
# the Praxis ones that have been validated and coerced into the types you've defined.
|
|
61
|
+
def params
|
|
62
|
+
self.request.parameters
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Allow accessing the response headers from the controller
|
|
66
|
+
def headers
|
|
67
|
+
self.response.headers
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def session
|
|
71
|
+
self.request.session
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Allow setting the status and body of the response from the controller itself.
|
|
75
|
+
def status=(code)
|
|
76
|
+
self.response.status = code
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def response_body=(body)
|
|
80
|
+
#TODO: @_rendered = true # Necessary to know if to stop filter chain or not...
|
|
81
|
+
self.response.body = body
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def head(status, options = {})
|
|
85
|
+
options, status = status, nil if status.is_a?(Hash)
|
|
86
|
+
status ||= options.delete(:status) || :ok
|
|
87
|
+
location = options.delete(:location)
|
|
88
|
+
content_type = options.delete(:content_type)
|
|
89
|
+
|
|
90
|
+
code = Rack::Utils::SYMBOL_TO_STATUS_CODE[status]
|
|
91
|
+
response = Praxis::Response.new(status: code, body: status.to_s, location: location)
|
|
92
|
+
|
|
93
|
+
options.each do |key, value|
|
|
94
|
+
response.headers[key.to_s.dasherize.split('-').each { |v| v[0] = v[0].chr.upcase }.join('-')] = value.to_s
|
|
95
|
+
end
|
|
96
|
+
response.content_type = content_type if content_type
|
|
97
|
+
response
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|