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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 565371e7aa228365c4541e10f1c685e51238174f
4
- data.tar.gz: b2c0dbf7c68329df0404aac8bad818f4e4b5fa34
3
+ metadata.gz: faa4c7b780be253c8aa05ac8f9969d2d9754efbf
4
+ data.tar.gz: 4b71e46c0337fc3c6346ca6d1da68512155d0712
5
5
  SHA512:
6
- metadata.gz: e89add23168a1c9e530c60d5cf6c6634716abe1db2a953ff066dbce3ec0a7c13b4d1eafefdafa0f512373a6f8dea90afb38f1775e29c787c78e6d4162b86c1f4
7
- data.tar.gz: 86b1179b253b50a0b40f4c01e07d3bce95cec9bae650364c9b43ad3c42120d60ed22610fbf915a07417d08e711255801a2ea966865c84e67783430c1be10616e
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
-
@@ -30,7 +30,7 @@ app.controller("MenuCtrl", function($scope, $state, Documentation) {
30
30
  links.push(link);
31
31
  });
32
32
  });
33
- $scope.selectedVersion = $scope.versions[0];
33
+ $scope.selectedVersion = $state.params.version || $scope.versions[0];
34
34
 
35
35
  });
36
36
 
@@ -1,6 +1,6 @@
1
1
  app.directive('rsAttributeTableRow', function($compile, TypeTemplates) {
2
2
  return {
3
- restrict: 'E',
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="attribute-table">
47
- <div class="row head">
48
- <div class="col-sm-2">
49
- Code
50
- </div>
51
- <div class="col-sm-2">
52
- Name
53
- </div>
54
- <div class="col-sm-4">
55
- Media Type
56
- </div>
57
- <div class="col-sm-4">
58
- Description
59
- </div>
60
- </div>
61
- <div class="content">
62
- <div class="row" ng-repeat="response in responses">
63
- <div class="col-sm-2">
64
- <span ng-if="response.isMultipart">
65
- <em>Parts Like:</em>
66
- </span>
67
- <span>{{response.status}}</span>
68
- </div>
69
- <div class="col-sm-2">
70
- {{response.name}}
71
- </div>
72
- <div class="col-sm-4">
73
- {{response.media_type.name || response.media_type.identifier}}
74
- </div>
75
- <div class="col-sm-4">
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
- <div class="attribute-table">
2
- <div class="row head">
3
- <div class="col-sm-3">
4
- Attribute
5
- </div>
6
- <div class="col-sm-2">
7
- Type
8
- </div>
9
- <div class="col-sm-7">
10
- Description
11
- </div>
12
- </div>
13
- <div class="content">
14
- <div ng-repeat="g in groups">
15
- <div class="row" ng-if="g.name && g.attributes.length">
16
- <div class="col-sm-12">
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
- <div class="row">
2
- <div class="col-sm-3" ng-bind-html="name | attributeName">
3
- </div>
4
- <div class="col-sm-2">
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
- </div>
7
- <div class="col-sm-7">
6
+ </td>
7
+ <td>
8
8
  <rs-attribute-description attribute="attribute"></rs-attribute-description>
9
- </div>
10
- </div>
9
+ </td>
10
+ </tr>
@@ -1,11 +1,11 @@
1
- <ng-include src="'views/directives/attribute_table_row/_default.html'" no-container></ng-include>
2
- <div class="row" ng-repeat="(attribute_name,attribute) in attribute.type.attributes">
3
- <div class="col-sm-3" ng-bind-html="name + '.' + attribute_name | attributeName">
4
- </div>
5
- <div class="col-sm-2">
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
- </div>
8
- <div class="col-sm-7">
7
+ </td>
8
+ <td>
9
9
  <rs-attribute-description attribute="attribute"></rs-attribute-description>
10
- </div>
11
- </div>
10
+ </td>
11
+ </tr>
@@ -1,2 +1,2 @@
1
- <ng-include src="'views/directives/attribute_table_row/_default.html'" no-container></ng-include>
2
- <rs-attribute-table-row ng-repeat="(k,v) in attribute.type.attributes" name="name + '.' + k" attribute="v"></rs-attribute-table-row>
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="attribute-table">
2
- <div class="row head">
3
- <div class="col-sm-3">
4
- Type
5
- </div>
6
- <div class="col-sm-9">
7
- Type Attributes
8
- </div>
9
- </div>
10
- <div class="content">
11
- <div class="row head">
12
- <div class="col-sm-3">
13
- {{payload.type.name}}
14
- </div>
15
- <div class="col-sm-9">
16
- <rs-attribute-description attribute="{options:{example:payload.example,typeData:payload.type}}"></rs-attribute-description>
17
- </div>
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="attribute-table">
2
- <div class="row head">
3
- <div class="col-sm-2">
4
- Name
5
- </div>
6
- <div class="col-sm-4">
7
- Route
8
- </div>
9
- <div class="col-sm-6">
10
- Description
11
- </div>
12
- </div>
13
- <div class="content">
14
- <div class="row" ng-repeat="action in controller.actions">
15
- <div class="col-sm-2">
16
- <a ui-sref="root.action({action: action.name, controller: controllerName, version: apiVersion})">{{ action.name }}</a>
17
- </div>
18
- <div class="col-sm-4">
19
- <div ng-repeat="url in action.urls">
20
- <div class="label label-default">{{ url.verb }}</div>
21
- <strong>{{ url.path }}</strong>
22
- </div>
23
- </div>
24
- <div class="col-sm-6" style="font-size: 110%;">
25
- {{ action.description }}
26
- </div>
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
@@ -34,6 +34,10 @@ module Praxis
34
34
  reference.const_set :Links, klass
35
35
  end
36
36
 
37
+ def self.constructable?
38
+ true
39
+ end
40
+
37
41
  def self.construct(constructor_block, options)
38
42
  options[:reference] = @reference
39
43
  options[:dsl_compiler_options] = {links: self.links}
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Praxis
2
- VERSION = '0.11.1'
2
+ VERSION = '0.11.2'
3
3
  end
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.3.0'
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
@@ -1,6 +1,2 @@
1
- log_stats: skip
2
- repositories:
3
- default:
4
- #type: sequel # -- default type
5
- adapter: sqlite
6
- database: ':memory:'
1
+ ---
2
+ :log_stats: skip
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.1
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.3.0
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.3.0
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