praxis 0.11.1 → 0.11.2

Sign up to get free protection for your applications and to get access to all the features.
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