praxis 0.11.1 → 0.11.2
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/CHANGELOG.md +9 -1
- data/CONTRIBUTING.md +2 -1
- data/lib/api_browser/app/js/controllers/menu.js +1 -1
- data/lib/api_browser/app/js/directives/attribute_table_row.js +3 -3
- data/lib/api_browser/app/views/action.html +30 -34
- data/lib/api_browser/app/views/directives/attribute_table.html +16 -22
- data/lib/api_browser/app/views/directives/attribute_table_row/_default.html +8 -8
- data/lib/api_browser/app/views/directives/attribute_table_row/_links.html +9 -9
- data/lib/api_browser/app/views/directives/attribute_table_row/_struct.html +2 -2
- data/lib/api_browser/app/views/directives/request_body/_default.html +17 -19
- data/lib/api_browser/app/views/resource/_actions.html +26 -28
- data/lib/praxis/links.rb +4 -0
- data/lib/praxis/plugins/praxis_mapper_plugin.rb +205 -0
- data/lib/praxis/route.rb +2 -2
- data/lib/praxis/version.rb +1 -1
- data/praxis.gemspec +1 -1
- data/spec/praxis/plugins/praxis_mapper_plugin_spec.rb +81 -0
- data/spec/spec_app/config/environment.rb +5 -1
- data/spec/spec_app/config/praxis_mapper.yml +2 -6
- data/spec/spec_helper.rb +8 -0
- metadata +5 -5
- data/spec/praxis_mapper_plugin_spec.rb +0 -71
- data/spec/support/spec_praxis_mapper_plugin.rb +0 -157
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: faa4c7b780be253c8aa05ac8f9969d2d9754efbf
|
4
|
+
data.tar.gz: 4b71e46c0337fc3c6346ca6d1da68512155d0712
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3914c19d0fdbc50d4aaa949dc0dfc4df0c8a602abd9313cc9c1bf12a17d73e667a024e2299b83d0f6b19697d808de5ab721d5089b36a658200979be680e78c6b
|
7
|
+
data.tar.gz: f692e9d7e62f5fe2ed80899f1b0c63ecbf0b01ec987a95bf157f78b4a373583a07720063c04a3bfea486cafae89340e3686f3b2f95ca4c91a5e2976461a62230
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# praxis changelog
|
2
2
|
|
3
|
+
## 0.11.2
|
4
|
+
|
5
|
+
* The Doc Browser will now not change the menu when refreshing.
|
6
|
+
* Fixes an issue where URLs in the doc browser would display JSON.
|
7
|
+
* Fixes an issue where table columns in the doc browser would be overlapping.
|
8
|
+
* Refactor Praxis Mapper plugin to be more generic.
|
9
|
+
* Update attributor dependency to 2.4.0
|
10
|
+
|
3
11
|
## 0.11.1
|
4
12
|
|
5
13
|
* Fix `Stats` plugin to handle empty `args` hashes.
|
@@ -14,7 +22,7 @@
|
|
14
22
|
* the new `:path` option will build the action routes by prefixing the version given a common pattern (i.e., "/v1.0/...")
|
15
23
|
* The effects of path versioning will be visible through `rake praxis:routes`
|
16
24
|
* the default api prefix pattern is ("/v(version)/") but can changed by either
|
17
|
-
* overriding ``Praxis::Request.path_version_prefix` and return the appropriate string prefix (i.e., by default this returns "/v")
|
25
|
+
* overriding ``Praxis::Request.path_version_prefix` and return the appropriate string prefix (i.e., by default this returns "/v")
|
18
26
|
* 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.
|
19
27
|
* Enhanced praxis generator:
|
20
28
|
* 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.
|
data/CONTRIBUTING.md
CHANGED
@@ -110,6 +110,8 @@ merged.
|
|
110
110
|
Please do not add yourself to the `AUTHORS` file unless you are on the Praxis
|
111
111
|
github team.
|
112
112
|
|
113
|
+
Add a quick summary of your change to the changelog, under the `next` heading.
|
114
|
+
|
113
115
|
### Sign your work
|
114
116
|
|
115
117
|
The sign-off is a simple line at the end of the explanation for the
|
@@ -181,4 +183,3 @@ There are several exceptions to the signing requirement. Currently these are:
|
|
181
183
|
Don't forget: being a maintainer is a time investment. Make sure you will have
|
182
184
|
time to make yourself available. You don't have to be a maintainer to make a
|
183
185
|
difference on the project!
|
184
|
-
|
@@ -1,6 +1,6 @@
|
|
1
1
|
app.directive('rsAttributeTableRow', function($compile, TypeTemplates) {
|
2
2
|
return {
|
3
|
-
restrict: '
|
3
|
+
restrict: 'EA',
|
4
4
|
scope: {
|
5
5
|
name: '=',
|
6
6
|
attribute: '='
|
@@ -8,10 +8,10 @@ app.directive('rsAttributeTableRow', function($compile, TypeTemplates) {
|
|
8
8
|
link: function(scope, element, attrs) {
|
9
9
|
// use the attribute type name to find the template
|
10
10
|
var name = (scope.attribute.type ? scope.attribute.type.name : null) || 'default';
|
11
|
-
|
11
|
+
|
12
12
|
TypeTemplates.resolve(name).then(function(template) {
|
13
13
|
element.replaceWith($compile(template)(scope));
|
14
14
|
});
|
15
15
|
}
|
16
16
|
}
|
17
|
-
});
|
17
|
+
});
|
@@ -43,40 +43,36 @@
|
|
43
43
|
<div class="row" ng-if="hasResponses()">
|
44
44
|
<div class="col-lg-12">
|
45
45
|
<h2>Responses</h2>
|
46
|
-
<div class="
|
47
|
-
<
|
48
|
-
<
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
<
|
65
|
-
|
66
|
-
</
|
67
|
-
<
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
<rs-attribute-description attribute="response"></rs-attribute-description>
|
77
|
-
</div>
|
78
|
-
</div>
|
79
|
-
</div>
|
46
|
+
<div class="table-responsive">
|
47
|
+
<table class="table table-striped">
|
48
|
+
<thead>
|
49
|
+
<tr>
|
50
|
+
<th>Code</th>
|
51
|
+
<th>Name</th>
|
52
|
+
<th>Media Type</th>
|
53
|
+
<th>Description</th>
|
54
|
+
</tr>
|
55
|
+
</thead>
|
56
|
+
<tbody>
|
57
|
+
<tr ng-repeat="response in responses">
|
58
|
+
<td>
|
59
|
+
<span ng-if="response.isMultipart">
|
60
|
+
<em>Parts Like:</em>
|
61
|
+
</span>
|
62
|
+
<span>{{response.status}}</span>
|
63
|
+
</td>
|
64
|
+
<td>
|
65
|
+
{{response.name}}
|
66
|
+
</td>
|
67
|
+
<td>
|
68
|
+
{{response.media_type.name || response.media_type.identifier}}
|
69
|
+
</td>
|
70
|
+
<td>
|
71
|
+
<rs-attribute-description attribute="response"></rs-attribute-description>
|
72
|
+
</td>
|
73
|
+
</tr>
|
74
|
+
</tbody>
|
75
|
+
</table>
|
80
76
|
</div>
|
81
77
|
</div>
|
82
78
|
</div>
|
@@ -1,23 +1,17 @@
|
|
1
|
-
|
2
|
-
<
|
3
|
-
<
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
<strong>{{g.name}}</strong>
|
18
|
-
</div>
|
19
|
-
</div>
|
20
|
-
<rs-attribute-table-row ng-repeat="item in g.attributes" name="item.name" attribute="item"></rs-attribute-table-row>
|
21
|
-
</div>
|
22
|
-
</div>
|
1
|
+
<div class="table-responsive">
|
2
|
+
<table class="table table-striped">
|
3
|
+
<thead>
|
4
|
+
<tr>
|
5
|
+
<th>Attribute</th>
|
6
|
+
<th>Type</th>
|
7
|
+
<th>Description</th>
|
8
|
+
</tr>
|
9
|
+
</thead>
|
10
|
+
<tbody ng-repeat="g in groups">
|
11
|
+
<tr ng-if="g.name && g.attributes.length">
|
12
|
+
<th colspan="3">{{g.name}}</th>
|
13
|
+
</tr>
|
14
|
+
<tr rs-attribute-table-row ng-repeat="item in g.attributes" name="item.name" attribute="item" ></tr>
|
15
|
+
</tbody>
|
16
|
+
</table>
|
23
17
|
</div>
|
@@ -1,10 +1,10 @@
|
|
1
|
-
|
2
|
-
<
|
3
|
-
</
|
4
|
-
<
|
1
|
+
<tr>
|
2
|
+
<td ng-bind-html="name | attributeName">
|
3
|
+
</td>
|
4
|
+
<td>
|
5
5
|
<rs-type-label type="attribute.type"></rs-type-label>
|
6
|
-
</
|
7
|
-
<
|
6
|
+
</td>
|
7
|
+
<td>
|
8
8
|
<rs-attribute-description attribute="attribute"></rs-attribute-description>
|
9
|
-
</
|
10
|
-
</
|
9
|
+
</td>
|
10
|
+
</tr>
|
@@ -1,11 +1,11 @@
|
|
1
|
-
<ng-include
|
2
|
-
<
|
3
|
-
<
|
4
|
-
</
|
5
|
-
<
|
1
|
+
<tr ng-include="'views/directives/attribute_table_row/_default.html'" no-container></tr>
|
2
|
+
<tr ng-repeat="(attribute_name,attribute) in attribute.type.attributes">
|
3
|
+
<td ng-bind-html="name + '.' + attribute_name | attributeName">
|
4
|
+
</td>
|
5
|
+
<td>
|
6
6
|
<rs-type-label type="attribute.type"></rs-type-label>
|
7
|
-
</
|
8
|
-
<
|
7
|
+
</td>
|
8
|
+
<td>
|
9
9
|
<rs-attribute-description attribute="attribute"></rs-attribute-description>
|
10
|
-
</
|
11
|
-
</
|
10
|
+
</td>
|
11
|
+
</tr>
|
@@ -1,2 +1,2 @@
|
|
1
|
-
<ng-include
|
2
|
-
<rs-attribute-table-row ng-repeat="(k,v) in attribute.type.attributes" name="name + '.' + k" attribute="v"></
|
1
|
+
<tr ng-include="'views/directives/attribute_table_row/_default.html'" no-container></tr>
|
2
|
+
<tr rs-attribute-table-row ng-repeat="(k,v) in attribute.type.attributes" name="name + '.' + k" attribute="v"></tr>
|
@@ -1,20 +1,18 @@
|
|
1
|
-
<div class="
|
2
|
-
<
|
3
|
-
<
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
</
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
</div>
|
19
|
-
</div>
|
1
|
+
<div class="table-responsive">
|
2
|
+
<table class="table table-striped">
|
3
|
+
<thead>
|
4
|
+
<tr>
|
5
|
+
<th>Type</th>
|
6
|
+
<th>Type Attributes</th>
|
7
|
+
</tr>
|
8
|
+
</thead>
|
9
|
+
<tbody>
|
10
|
+
<tr>
|
11
|
+
<td>{{payload.type.name}}</td>
|
12
|
+
<td>
|
13
|
+
<rs-attribute-description attribute="{options:{example:payload.example,typeData:payload.type}}"></rs-attribute-description>
|
14
|
+
</td>
|
15
|
+
</tr>
|
16
|
+
</tbody>
|
17
|
+
</table>
|
20
18
|
</div>
|
@@ -1,29 +1,27 @@
|
|
1
|
-
<div class="
|
2
|
-
<
|
3
|
-
<
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
</div>
|
28
|
-
</div>
|
1
|
+
<div class="table-responsive">
|
2
|
+
<table class="table table-striped">
|
3
|
+
<thead>
|
4
|
+
<tr>
|
5
|
+
<th>Name</th>
|
6
|
+
<th>Route</th>
|
7
|
+
<th>Description</th>
|
8
|
+
</tr>
|
9
|
+
</thead>
|
10
|
+
<tbody>
|
11
|
+
<tr ng-repeat="action in controller.actions">
|
12
|
+
<td>
|
13
|
+
<a ui-sref="root.action({action: action.name, controller: controllerName, version: apiVersion})">{{ action.name }}</a>
|
14
|
+
</td>
|
15
|
+
<td>
|
16
|
+
<div ng-repeat="url in action.urls">
|
17
|
+
<div class="label label-default">{{ url.verb }}</div>
|
18
|
+
<strong>{{ url.path }}</strong>
|
19
|
+
</div>
|
20
|
+
</td>
|
21
|
+
<td style="font-size: 110%;">
|
22
|
+
{{ action.description }}
|
23
|
+
</td>
|
24
|
+
</tr>
|
25
|
+
</tbody>
|
26
|
+
</table>
|
29
27
|
</div>
|
data/lib/praxis/links.rb
CHANGED
@@ -0,0 +1,205 @@
|
|
1
|
+
require 'praxis-mapper'
|
2
|
+
require 'singleton'
|
3
|
+
|
4
|
+
require 'terminal-table'
|
5
|
+
|
6
|
+
# Plugin for applications which use the 'praxis-mapper' gem.
|
7
|
+
#
|
8
|
+
# This plugin provides the following features:
|
9
|
+
# 1. Sets up the PraxisMapper::IdentityMap for your application and assigns
|
10
|
+
# it to the controller's request.identity_map for access from your
|
11
|
+
# application.
|
12
|
+
# 2. Connects to your database and dumps a log of database interaction stats
|
13
|
+
# (if enabled via the :log_stats option).
|
14
|
+
#
|
15
|
+
# This plugin accepts one of the following options:
|
16
|
+
# 1. config_file: A String indicating the path where this plugin's config
|
17
|
+
# file exists.
|
18
|
+
# 2. config_data: A Hash of data that is merged into the YAML hash loaded
|
19
|
+
# from config_file.
|
20
|
+
#
|
21
|
+
# The config_data Hash contains the following keys:
|
22
|
+
# 1. repositories: A Hash containing the configs for the database repositories
|
23
|
+
# queried through praxis-mapper. This parameter is a Hash where a key is
|
24
|
+
# the identifier for a repository and the value is the options one
|
25
|
+
# would give to the 'sequel' gem. For example:
|
26
|
+
# repositories: {
|
27
|
+
# default: {
|
28
|
+
# host: 127.0.0.1,
|
29
|
+
# username: root,
|
30
|
+
# password: nil,
|
31
|
+
# database: myapp_dev,
|
32
|
+
# adapter: mysql2
|
33
|
+
# }
|
34
|
+
# }
|
35
|
+
# 2. log_stats: A String indicating what kind of DB stats you would like
|
36
|
+
# output into the Praxis::Application.instance.logger app log. Possible
|
37
|
+
# values are: "detailed", "short", and "skip" (i.e. do not print the stats
|
38
|
+
# at all).
|
39
|
+
#
|
40
|
+
# See http://praxis-framework.io/reference/plugins/ for further details on how
|
41
|
+
# to use a plugin and pass it options.
|
42
|
+
#
|
43
|
+
module Praxis
|
44
|
+
module Plugins
|
45
|
+
module PraxisMapperPlugin
|
46
|
+
include Praxis::PluginConcern
|
47
|
+
|
48
|
+
class RepositoryConfig < Attributor::Hash
|
49
|
+
self.key_type = String
|
50
|
+
|
51
|
+
keys allow_extra: true do
|
52
|
+
key 'type', String, default: 'sequel'
|
53
|
+
extra 'connection_settings'
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
class Plugin < Praxis::Plugin
|
60
|
+
include Singleton
|
61
|
+
|
62
|
+
def initialize
|
63
|
+
@options = {
|
64
|
+
config_file: 'config/praxis_mapper.yml',
|
65
|
+
config_data: {
|
66
|
+
repositories: {}
|
67
|
+
}
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
def config_key
|
72
|
+
:praxis_mapper
|
73
|
+
end
|
74
|
+
|
75
|
+
def prepare_config!(node)
|
76
|
+
node.attributes do
|
77
|
+
attribute :log_stats, String, values: ['detailed', 'short', 'skip'], default: 'detailed'
|
78
|
+
attribute :repositories, Attributor::Hash.of(key: String, value: RepositoryConfig)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Make our own custom load_config! method
|
83
|
+
def load_config!
|
84
|
+
config_file_path = application.root + options[:config_file]
|
85
|
+
result = config_file_path.exist? ? YAML.load_file(config_file_path) : {}
|
86
|
+
result.merge(@options[:config_data])
|
87
|
+
end
|
88
|
+
|
89
|
+
def setup!
|
90
|
+
self.config.repositories.each do |repository_name, repository_config|
|
91
|
+
type = repository_config['type']
|
92
|
+
connection_settings = repository_config['connection_settings']
|
93
|
+
|
94
|
+
case type
|
95
|
+
when 'sequel'
|
96
|
+
self.setup_sequel_repository(repository_name, connection_settings)
|
97
|
+
else
|
98
|
+
raise "unsupported repository type: #{type}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
log_stats = PraxisMapperPlugin::Plugin.instance.config.log_stats
|
103
|
+
unless log_stats == 'skip'
|
104
|
+
Praxis::Notifications.subscribe 'praxis.request.all' do |name, *junk, payload|
|
105
|
+
if (identity_map = payload[:request].identity_map)
|
106
|
+
PraxisMapperPlugin::Statistics.log(identity_map, log_stats)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def setup_sequel_repository(name, settings)
|
113
|
+
db = Sequel.connect(settings.dump.symbolize_keys)
|
114
|
+
|
115
|
+
Praxis::Mapper::ConnectionManager.setup do
|
116
|
+
repository(name.to_sym) { db }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
module Request
|
122
|
+
def identity_map
|
123
|
+
@identity_map
|
124
|
+
end
|
125
|
+
|
126
|
+
def identity_map=(map)
|
127
|
+
@identity_map = map
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
module Controller
|
132
|
+
extend ActiveSupport::Concern
|
133
|
+
|
134
|
+
included do
|
135
|
+
before :action do |controller|
|
136
|
+
controller.request.identity_map = Praxis::Mapper::IdentityMap.setup!
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
module Statistics
|
142
|
+
|
143
|
+
def self.log(identity_map, log_stats)
|
144
|
+
return if identity_map.nil?
|
145
|
+
case log_stats
|
146
|
+
when 'detailed'
|
147
|
+
self.detailed(identity_map)
|
148
|
+
when 'short'
|
149
|
+
self.short(identity_map)
|
150
|
+
when 'skip'
|
151
|
+
# Shouldn't receive this. But anyway...no-op.
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.detailed(identity_map)
|
156
|
+
stats_by_model = identity_map.query_statistics.sum_totals_by_model
|
157
|
+
stats_total = identity_map.query_statistics.sum_totals
|
158
|
+
fields = [ :query_count, :records_loaded, :datastore_interactions, :datastore_interaction_time]
|
159
|
+
rows = []
|
160
|
+
|
161
|
+
total_models_loaded = 0
|
162
|
+
# stats per model
|
163
|
+
stats_by_model.each do |model, totals|
|
164
|
+
total_values = totals.values_at(*fields)
|
165
|
+
self.round_fields_at( total_values , [fields.index(:datastore_interaction_time)])
|
166
|
+
row = [ model ] + total_values
|
167
|
+
models_loaded = identity_map.all(model).size
|
168
|
+
total_models_loaded += models_loaded
|
169
|
+
row << models_loaded
|
170
|
+
rows << row
|
171
|
+
end
|
172
|
+
|
173
|
+
rows << :separator
|
174
|
+
|
175
|
+
# totals for all models
|
176
|
+
stats_total_values = stats_total.values_at(*fields)
|
177
|
+
self.round_fields_at(stats_total_values , [fields.index(:datastore_interaction_time)])
|
178
|
+
rows << [ "All Models" ] + stats_total_values + [total_models_loaded]
|
179
|
+
|
180
|
+
table = Terminal::Table.new \
|
181
|
+
:rows => rows,
|
182
|
+
:title => "Praxis::Mapper Statistics",
|
183
|
+
:headings => [ "Model", "# Queries", "Records Loaded", "Interactions", "Time(sec)", "Models Loaded" ]
|
184
|
+
|
185
|
+
table.align_column(1, :right)
|
186
|
+
table.align_column(2, :right)
|
187
|
+
table.align_column(3, :right)
|
188
|
+
table.align_column(4, :right)
|
189
|
+
table.align_column(5, :right)
|
190
|
+
Praxis::Application.instance.logger.info "Praxis::Mapper Statistics:\n#{table.to_s}"
|
191
|
+
end
|
192
|
+
|
193
|
+
def self.round_fields_at(values, indices)
|
194
|
+
indices.each do |idx|
|
195
|
+
values[idx] = "%.3f" % values[idx]
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def self.short(identity_map)
|
200
|
+
Praxis::Application.instance.logger.info "Praxis::Mapper Statistics: #{identity_map.query_statistics.sum_totals.to_s}"
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
data/lib/praxis/route.rb
CHANGED
@@ -14,14 +14,14 @@ module Praxis
|
|
14
14
|
def describe
|
15
15
|
result = {
|
16
16
|
verb: verb,
|
17
|
-
path: path,
|
17
|
+
path: path.to_s,
|
18
18
|
version: version
|
19
19
|
}
|
20
20
|
result[:name] = name unless name.nil?
|
21
21
|
result[:options] = options if options.any?
|
22
22
|
result
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
end
|
26
26
|
|
27
27
|
end
|
data/lib/praxis/version.rb
CHANGED
data/praxis.gemspec
CHANGED
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.add_dependency 'mime', '~> 0'
|
29
29
|
spec.add_dependency 'praxis-mapper', '~> 3.1'
|
30
30
|
spec.add_dependency 'praxis-blueprints', '~> 1.1'
|
31
|
-
spec.add_dependency 'attributor', '~> 2.
|
31
|
+
spec.add_dependency 'attributor', '~> 2.4.0'
|
32
32
|
spec.add_dependency 'thor', '~> 0.18'
|
33
33
|
spec.add_dependency 'terminal-table', '~> 1.4'
|
34
34
|
spec.add_dependency 'harness', '~> 2'
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Praxis::Plugins::PraxisMapperPlugin do
|
4
|
+
|
5
|
+
subject(:plugin) { Praxis::Plugins::PraxisMapperPlugin::Plugin.instance }
|
6
|
+
let(:config) { plugin.config }
|
7
|
+
|
8
|
+
context 'Plugin' do
|
9
|
+
context 'configuration' do
|
10
|
+
subject { config }
|
11
|
+
its(:log_stats) { should eq 'skip' }
|
12
|
+
|
13
|
+
its(:repositories) { should have_key("default") }
|
14
|
+
|
15
|
+
context 'default repository' do
|
16
|
+
subject(:default) { config.repositories['default'] }
|
17
|
+
its(['type']) { should eq 'sequel' }
|
18
|
+
its(['connection_settings']) do
|
19
|
+
should eq('adapter' => 'sqlite','database' => ':memory:')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'functional test' do
|
27
|
+
|
28
|
+
def app
|
29
|
+
Praxis::Application.instance
|
30
|
+
end
|
31
|
+
|
32
|
+
let(:session) { double("session", valid?: true)}
|
33
|
+
|
34
|
+
around(:each) do |example|
|
35
|
+
orig_level = Praxis::Application.instance.logger.level
|
36
|
+
Praxis::Application.instance.logger.level = 2
|
37
|
+
config.log_stats = 'detailed'; plugin.setup!
|
38
|
+
example.run
|
39
|
+
config.log_stats = 'skip'; plugin.setup!
|
40
|
+
Praxis::Application.instance.logger.level = orig_level
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'logs stats' do
|
44
|
+
expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to receive(:log).
|
45
|
+
with(kind_of(Praxis::Mapper::IdentityMap), 'detailed').
|
46
|
+
and_call_original
|
47
|
+
|
48
|
+
get '/clouds/1/instances/2?junk=foo&api_version=1.0', nil, 'global_session' => session
|
49
|
+
|
50
|
+
expect(last_response.status).to eq(200)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'Statistics' do
|
56
|
+
context '.log' do
|
57
|
+
let(:identity_map) { double('identity_map') }
|
58
|
+
|
59
|
+
it 'when log_stats = detailed' do
|
60
|
+
expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to receive(:detailed).with(identity_map)
|
61
|
+
Praxis::Plugins::PraxisMapperPlugin::Statistics.log(identity_map, 'detailed')
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'when log_stats = short' do
|
65
|
+
expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to receive(:short).with(identity_map)
|
66
|
+
Praxis::Plugins::PraxisMapperPlugin::Statistics.log(identity_map, 'short')
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'when log_stats = skip' do
|
70
|
+
expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to_not receive(:short)
|
71
|
+
expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to_not receive(:detailed)
|
72
|
+
Praxis::Plugins::PraxisMapperPlugin::Statistics.log(identity_map, 'skip')
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'has specs for testing the detailed log output'
|
78
|
+
it 'has specs for testing the short log output'
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
@@ -19,7 +19,11 @@ Praxis::Application.configure do |application|
|
|
19
19
|
application.bootloader.use SimpleAuthenticationPlugin, config_file: 'config/authentication.yml'
|
20
20
|
application.bootloader.use AuthorizationPlugin
|
21
21
|
|
22
|
-
application.bootloader.use PraxisMapperPlugin
|
22
|
+
application.bootloader.use Praxis::Plugins::PraxisMapperPlugin, {
|
23
|
+
config_data: {
|
24
|
+
repositories: { default: {adapter: 'sqlite', database: ':memory:'} }
|
25
|
+
}
|
26
|
+
}
|
23
27
|
|
24
28
|
# enable "development-mode" options
|
25
29
|
application.config.praxis.validate_responses = true
|
data/spec/spec_helper.rb
CHANGED
@@ -17,6 +17,10 @@ require 'rspec/collection_matchers'
|
|
17
17
|
|
18
18
|
require 'pry'
|
19
19
|
|
20
|
+
Dir["#{File.dirname(__FILE__)}/../lib/praxis/plugins/*.rb"].each do |file|
|
21
|
+
require file
|
22
|
+
end
|
23
|
+
|
20
24
|
Dir["#{File.dirname(__FILE__)}/support/*.rb"].each do |file|
|
21
25
|
require file
|
22
26
|
end
|
@@ -39,6 +43,10 @@ RSpec.configure do |config|
|
|
39
43
|
end
|
40
44
|
end
|
41
45
|
|
46
|
+
config.before(:all) do
|
47
|
+
# disable logging below warn level
|
48
|
+
Praxis::Application.instance.logger.level = 2 # warn
|
49
|
+
end
|
42
50
|
end
|
43
51
|
|
44
52
|
# create the test db schema
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: praxis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.11.
|
4
|
+
version: 0.11.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josep M. Blanquer
|
@@ -115,14 +115,14 @@ dependencies:
|
|
115
115
|
requirements:
|
116
116
|
- - "~>"
|
117
117
|
- !ruby/object:Gem::Version
|
118
|
-
version: 2.
|
118
|
+
version: 2.4.0
|
119
119
|
type: :runtime
|
120
120
|
prerelease: false
|
121
121
|
version_requirements: !ruby/object:Gem::Requirement
|
122
122
|
requirements:
|
123
123
|
- - "~>"
|
124
124
|
- !ruby/object:Gem::Version
|
125
|
-
version: 2.
|
125
|
+
version: 2.4.0
|
126
126
|
- !ruby/object:Gem::Dependency
|
127
127
|
name: thor
|
128
128
|
requirement: !ruby/object:Gem::Requirement
|
@@ -836,6 +836,7 @@ files:
|
|
836
836
|
- lib/praxis/notifications.rb
|
837
837
|
- lib/praxis/plugin.rb
|
838
838
|
- lib/praxis/plugin_concern.rb
|
839
|
+
- lib/praxis/plugins/praxis_mapper_plugin.rb
|
839
840
|
- lib/praxis/request.rb
|
840
841
|
- lib/praxis/request_stages/action.rb
|
841
842
|
- lib/praxis/request_stages/load_request.rb
|
@@ -884,6 +885,7 @@ files:
|
|
884
885
|
- spec/praxis/multipart/parser_spec.rb
|
885
886
|
- spec/praxis/notifications_spec.rb
|
886
887
|
- spec/praxis/plugin_concern_spec.rb
|
888
|
+
- spec/praxis/plugins/praxis_mapper_plugin_spec.rb
|
887
889
|
- spec/praxis/request_spec.rb
|
888
890
|
- spec/praxis/request_stage_spec.rb
|
889
891
|
- spec/praxis/request_stages_action_spec.rb
|
@@ -899,7 +901,6 @@ files:
|
|
899
901
|
- spec/praxis/stage_spec.rb
|
900
902
|
- spec/praxis/stats_spec.rb
|
901
903
|
- spec/praxis/types/multipart_spec.rb
|
902
|
-
- spec/praxis_mapper_plugin_spec.rb
|
903
904
|
- spec/spec_app/app/concerns/authenticated.rb
|
904
905
|
- spec/spec_app/app/concerns/basic_api.rb
|
905
906
|
- spec/spec_app/app/concerns/log_wrapper.rb
|
@@ -930,7 +931,6 @@ files:
|
|
930
931
|
- spec/support/spec_authorization_plugin.rb
|
931
932
|
- spec/support/spec_complex_authentication_plugin.rb
|
932
933
|
- spec/support/spec_media_types.rb
|
933
|
-
- spec/support/spec_praxis_mapper_plugin.rb
|
934
934
|
- spec/support/spec_resource_definitions.rb
|
935
935
|
- spec/support/spec_simple_authentication_plugin.rb
|
936
936
|
- tasks/loader.thor
|
@@ -1,71 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe PraxisMapperPlugin do
|
4
|
-
|
5
|
-
subject(:plugin) { PraxisMapperPlugin::Plugin.instance }
|
6
|
-
let(:config) { plugin.config }
|
7
|
-
|
8
|
-
context 'Plugin' do
|
9
|
-
context 'configuration' do
|
10
|
-
subject { config }
|
11
|
-
its(:log_stats) { should eq 'skip' }
|
12
|
-
|
13
|
-
its(:repositories) { should have_key("default") }
|
14
|
-
|
15
|
-
context 'default repository' do
|
16
|
-
subject(:default) { config.repositories['default'] }
|
17
|
-
its(['type']) { should eq 'sequel' }
|
18
|
-
its(['connection_settings']) { should eq('adapter' => 'sqlite','database' => ':memory:')}
|
19
|
-
end
|
20
|
-
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
context 'functional test' do
|
25
|
-
|
26
|
-
def app
|
27
|
-
Praxis::Application.instance
|
28
|
-
end
|
29
|
-
|
30
|
-
let(:session) { double("session", valid?: true)}
|
31
|
-
|
32
|
-
it 'logs stats' do
|
33
|
-
expect(PraxisMapperPlugin::Statistics).to receive(:log).with(kind_of(Praxis::Mapper::IdentityMap)).and_call_original
|
34
|
-
|
35
|
-
get '/clouds/1/instances/2?junk=foo&api_version=1.0', nil, 'global_session' => session
|
36
|
-
expect(last_response.status).to eq(200)
|
37
|
-
end
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
context 'Statistics' do
|
42
|
-
context '.log' do
|
43
|
-
let(:identity_map) { double('identity_map') }
|
44
|
-
|
45
|
-
after do
|
46
|
-
PraxisMapperPlugin::Statistics.log(identity_map)
|
47
|
-
end
|
48
|
-
|
49
|
-
it 'when log_stats = detailed' do
|
50
|
-
expect(config).to receive(:log_stats).and_return 'detailed'
|
51
|
-
expect(PraxisMapperPlugin::Statistics).to receive(:detailed).with(identity_map)
|
52
|
-
end
|
53
|
-
|
54
|
-
it 'when log_stats = detailed' do
|
55
|
-
expect(config).to receive(:log_stats).and_return 'short'
|
56
|
-
expect(PraxisMapperPlugin::Statistics).to receive(:short).with(identity_map)
|
57
|
-
end
|
58
|
-
|
59
|
-
it 'when log_stats = skip' do
|
60
|
-
expect(config).to receive(:log_stats).and_return 'skip'
|
61
|
-
expect(PraxisMapperPlugin::Statistics).to_not receive(:short)
|
62
|
-
expect(PraxisMapperPlugin::Statistics).to_not receive(:detailed)
|
63
|
-
end
|
64
|
-
|
65
|
-
end
|
66
|
-
|
67
|
-
it 'has specs for testing the detailed log output'
|
68
|
-
it 'has specs for testing the short log output'
|
69
|
-
end
|
70
|
-
|
71
|
-
end
|
@@ -1,157 +0,0 @@
|
|
1
|
-
require 'praxis-mapper'
|
2
|
-
require 'singleton'
|
3
|
-
|
4
|
-
require 'terminal-table'
|
5
|
-
|
6
|
-
module PraxisMapperPlugin
|
7
|
-
include Praxis::PluginConcern
|
8
|
-
|
9
|
-
class RepositoryConfig < Attributor::Hash
|
10
|
-
self.key_type = String
|
11
|
-
|
12
|
-
keys allow_extra: true do
|
13
|
-
key 'type', String, default: 'sequel'
|
14
|
-
extra 'connection_settings'
|
15
|
-
end
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
|
-
|
20
|
-
class Plugin < Praxis::Plugin
|
21
|
-
include Singleton
|
22
|
-
|
23
|
-
def initialize
|
24
|
-
@options = {config_file: 'config/praxis_mapper.yml'}
|
25
|
-
end
|
26
|
-
|
27
|
-
def config_key
|
28
|
-
:praxis_mapper
|
29
|
-
end
|
30
|
-
|
31
|
-
def prepare_config!(node)
|
32
|
-
node.attributes do
|
33
|
-
attribute :log_stats, String, values: ['detailed', 'short', 'skip'], default: 'detailed'
|
34
|
-
attribute :repositories, Attributor::Hash.of(key: String, value: RepositoryConfig)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def setup!
|
39
|
-
self.config.repositories.each do |repository_name, repository_config|
|
40
|
-
type = repository_config['type']
|
41
|
-
connection_settings = repository_config['connection_settings']
|
42
|
-
|
43
|
-
case type
|
44
|
-
when 'sequel'
|
45
|
-
self.setup_sequel_repository(repository_name, connection_settings)
|
46
|
-
else
|
47
|
-
raise "unsupported repository type: #{type}"
|
48
|
-
end
|
49
|
-
|
50
|
-
end
|
51
|
-
|
52
|
-
Praxis::Notifications.subscribe 'praxis.request.all' do |name, *junk, payload|
|
53
|
-
if (identity_map = payload[:request].identity_map)
|
54
|
-
PraxisMapperPlugin::Statistics.log(identity_map)
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
def setup_sequel_repository(name, settings)
|
61
|
-
db = Sequel.connect(settings.dump.symbolize_keys)
|
62
|
-
|
63
|
-
Praxis::Mapper::ConnectionManager.setup do
|
64
|
-
repository(name.to_sym) { db }
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
end
|
69
|
-
|
70
|
-
module Request
|
71
|
-
def identity_map
|
72
|
-
@identity_map
|
73
|
-
end
|
74
|
-
|
75
|
-
def identity_map=(map)
|
76
|
-
@identity_map = map
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
module Controller
|
81
|
-
extend ActiveSupport::Concern
|
82
|
-
|
83
|
-
included do
|
84
|
-
before :action do |controller|
|
85
|
-
controller.request.identity_map ||= Praxis::Mapper::IdentityMap.new
|
86
|
-
end
|
87
|
-
|
88
|
-
after :action do |controller|
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
end
|
93
|
-
|
94
|
-
module Statistics
|
95
|
-
|
96
|
-
def self.log(identity_map)
|
97
|
-
return if identity_map.nil?
|
98
|
-
case PraxisMapperPlugin::Plugin.instance.config.log_stats
|
99
|
-
when 'detailed'
|
100
|
-
self.detailed(identity_map)
|
101
|
-
when 'short'
|
102
|
-
self.short(identity_map)
|
103
|
-
when 'skip'
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def self.detailed(identity_map)
|
108
|
-
stats_by_model = identity_map.query_statistics.sum_totals_by_model
|
109
|
-
stats_total = identity_map.query_statistics.sum_totals
|
110
|
-
fields = [ :query_count, :records_loaded, :datastore_interactions, :datastore_interaction_time]
|
111
|
-
rows = []
|
112
|
-
|
113
|
-
total_models_loaded = 0
|
114
|
-
# stats per model
|
115
|
-
stats_by_model.each do |model, totals|
|
116
|
-
total_values = totals.values_at(*fields)
|
117
|
-
self.round_fields_at( total_values , [fields.index(:datastore_interaction_time)])
|
118
|
-
row = [ model ] + total_values
|
119
|
-
models_loaded = identity_map.all(model).size
|
120
|
-
total_models_loaded += models_loaded
|
121
|
-
row << models_loaded
|
122
|
-
rows << row
|
123
|
-
end
|
124
|
-
|
125
|
-
rows << :separator
|
126
|
-
|
127
|
-
# totals for all models
|
128
|
-
stats_total_values = stats_total.values_at(*fields)
|
129
|
-
self.round_fields_at(stats_total_values , [fields.index(:datastore_interaction_time)])
|
130
|
-
rows << [ "All Models" ] + stats_total_values + [total_models_loaded]
|
131
|
-
|
132
|
-
table = Terminal::Table.new \
|
133
|
-
:rows => rows,
|
134
|
-
:title => "Praxis::Mapper Statistics",
|
135
|
-
:headings => [ "Model", "# Queries", "Records Loaded", "Interactions", "Time(sec)", "Models Loaded" ]
|
136
|
-
|
137
|
-
table.align_column(1, :right)
|
138
|
-
table.align_column(2, :right)
|
139
|
-
table.align_column(3, :right)
|
140
|
-
table.align_column(4, :right)
|
141
|
-
table.align_column(5, :right)
|
142
|
-
Praxis::Application.instance.logger.info "Praxis::Mapper Statistics:\n#{table.to_s}"
|
143
|
-
end
|
144
|
-
|
145
|
-
def self.round_fields_at(values, indices)
|
146
|
-
indices.each do |idx|
|
147
|
-
values[idx] = "%.3f" % values[idx]
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
def self.short(identity_map)
|
152
|
-
Praxis::Application.instance.logger.info "Praxis::Mapper Statistics: #{identity_map.query_statistics.sum_totals.to_s}"
|
153
|
-
end
|
154
|
-
|
155
|
-
end
|
156
|
-
|
157
|
-
end
|