praxis 0.22.pre.2 → 2.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +323 -324
  3. data/lib/praxis/action_definition.rb +7 -9
  4. data/lib/praxis/api_definition.rb +27 -44
  5. data/lib/praxis/api_general_info.rb +2 -3
  6. data/lib/praxis/application.rb +14 -141
  7. data/lib/praxis/bootloader.rb +1 -2
  8. data/lib/praxis/bootloader_stages/environment.rb +13 -0
  9. data/lib/praxis/controller.rb +0 -2
  10. data/lib/praxis/dispatcher.rb +4 -6
  11. data/lib/praxis/docs/generator.rb +8 -18
  12. data/lib/praxis/docs/link_builder.rb +1 -1
  13. data/lib/praxis/error_handler.rb +5 -5
  14. data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +1 -1
  15. data/lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb +125 -0
  16. data/lib/praxis/extensions/field_selection/active_record_query_selector.rb +16 -18
  17. data/lib/praxis/extensions/field_selection/sequel_query_selector.rb +5 -5
  18. data/lib/praxis/extensions/field_selection.rb +1 -12
  19. data/lib/praxis/extensions/rendering.rb +1 -1
  20. data/lib/praxis/file_group.rb +1 -1
  21. data/lib/praxis/handlers/xml.rb +1 -1
  22. data/lib/praxis/mapper/active_model_compat.rb +63 -0
  23. data/lib/praxis/mapper/resource.rb +242 -0
  24. data/lib/praxis/mapper/selector_generator.rb +126 -0
  25. data/lib/praxis/mapper/sequel_compat.rb +37 -0
  26. data/lib/praxis/middleware_app.rb +13 -15
  27. data/lib/praxis/multipart/part.rb +3 -5
  28. data/lib/praxis/plugins/mapper_plugin.rb +50 -0
  29. data/lib/praxis/request.rb +14 -7
  30. data/lib/praxis/request_stages/response.rb +2 -3
  31. data/lib/praxis/resource_definition.rb +10 -14
  32. data/lib/praxis/response.rb +6 -5
  33. data/lib/praxis/response_definition.rb +5 -7
  34. data/lib/praxis/response_template.rb +3 -4
  35. data/lib/praxis/responses/http.rb +36 -0
  36. data/lib/praxis/responses/internal_server_error.rb +12 -3
  37. data/lib/praxis/responses/multipart_ok.rb +11 -4
  38. data/lib/praxis/responses/validation_error.rb +10 -1
  39. data/lib/praxis/router.rb +3 -3
  40. data/lib/praxis/tasks/api_docs.rb +2 -10
  41. data/lib/praxis/tasks/routes.rb +0 -1
  42. data/lib/praxis/version.rb +1 -1
  43. data/lib/praxis.rb +13 -9
  44. data/praxis.gemspec +2 -3
  45. data/spec/functional_spec.rb +0 -1
  46. data/spec/praxis/action_definition_spec.rb +15 -26
  47. data/spec/praxis/api_definition_spec.rb +8 -13
  48. data/spec/praxis/api_general_info_spec.rb +8 -3
  49. data/spec/praxis/application_spec.rb +7 -13
  50. data/spec/praxis/handlers/xml_spec.rb +2 -2
  51. data/spec/praxis/mapper/resource_spec.rb +169 -0
  52. data/spec/praxis/mapper/selector_generator_spec.rb +301 -0
  53. data/spec/praxis/middleware_app_spec.rb +15 -9
  54. data/spec/praxis/request_spec.rb +7 -17
  55. data/spec/praxis/request_stages/validate_spec.rb +1 -1
  56. data/spec/praxis/resource_definition_spec.rb +10 -12
  57. data/spec/praxis/response_definition_spec.rb +5 -22
  58. data/spec/praxis/response_spec.rb +5 -12
  59. data/spec/praxis/responses/internal_server_error_spec.rb +5 -2
  60. data/spec/praxis/router_spec.rb +4 -8
  61. data/spec/spec_app/app/models/person.rb +3 -3
  62. data/spec/spec_app/config/environment.rb +3 -21
  63. data/spec/spec_app/config.ru +6 -1
  64. data/spec/spec_helper.rb +2 -17
  65. data/spec/support/spec_resources.rb +131 -0
  66. metadata +19 -31
  67. data/lib/praxis/extensions/attribute_filtering/query_builder.rb +0 -39
  68. data/lib/praxis/extensions/attribute_filtering.rb +0 -28
  69. data/lib/praxis/extensions/mapper_selectors.rb +0 -16
  70. data/lib/praxis/media_type_collection.rb +0 -127
  71. data/lib/praxis/plugins/praxis_mapper_plugin.rb +0 -246
  72. data/spec/praxis/media_type_collection_spec.rb +0 -157
  73. data/spec/praxis/plugins/praxis_mapper_plugin_spec.rb +0 -142
@@ -1,246 +0,0 @@
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.current_instance.logger app log. Possible
37
- # values are: "detailed", "short", and "skip" (i.e. do not print the stats
38
- # at all).
39
- # 3. stats_log_level: the logging level with which the statistics should be logged.
40
- #
41
- # See http://praxis-framework.io/reference/plugins/ for further details on how
42
- # to use a plugin and pass it options.
43
- #
44
- module Praxis
45
- module Plugins
46
- module PraxisMapperPlugin
47
- include Praxis::PluginConcern
48
-
49
- class RepositoryConfig < Attributor::Hash
50
- self.key_type = String
51
-
52
- keys allow_extra: true do
53
- key 'type', String, default: 'sequel'
54
- extra 'connection_settings'
55
- end
56
-
57
- end
58
-
59
-
60
- class Plugin < Praxis::Plugin
61
- include Singleton
62
-
63
- def initialize
64
- @options = {
65
- config_file: 'config/praxis_mapper.yml',
66
- config_data: {
67
- repositories: {}
68
- }
69
- }
70
- end
71
-
72
- def config_key
73
- :praxis_mapper
74
- end
75
-
76
- def prepare_config!(node)
77
- node.attributes do
78
- attribute :log_stats, String, values: ['detailed', 'short', 'skip'], default: 'detailed'
79
- attribute :stats_log_level, Symbol, values: [:fatal,:error,:warn,:info,:debug], default: :info
80
- attribute :repositories, Attributor::Hash.of(key: String, value: RepositoryConfig)
81
- end
82
- end
83
-
84
- # Make our own custom load_config! method
85
- def load_config!
86
- config_file_path = application.root + options[:config_file]
87
- result = config_file_path.exist? ? YAML.load_file(config_file_path) : {}
88
- result.merge(@options[:config_data])
89
- end
90
-
91
- def setup!
92
- self.config.repositories.each do |repository_name, repository_config|
93
- type = repository_config['type']
94
- connection_settings = repository_config['connection_settings']
95
-
96
- case type
97
- when 'sequel'
98
- self.setup_sequel_repository(repository_name, connection_settings)
99
- else
100
- raise "unsupported repository type: #{type}"
101
- end
102
- end
103
-
104
- log_stats = PraxisMapperPlugin::Plugin.instance.config.log_stats
105
- unless log_stats == 'skip'
106
- Praxis::Notifications.subscribe 'praxis.request.all' do |name, *junk, payload|
107
- if (payload[:request].identity_map?)
108
- identity_map = payload[:request].identity_map
109
- PraxisMapperPlugin::Statistics.log(payload[:request], identity_map, log_stats)
110
- end
111
- end
112
- end
113
- end
114
-
115
- def setup_sequel_repository(name, settings)
116
- db = Sequel.connect(settings.dump.symbolize_keys)
117
-
118
- Praxis::Mapper::ConnectionManager.setup do
119
- repository(name.to_sym) { db }
120
- end
121
- end
122
- end
123
-
124
- module Request
125
- def identity_map
126
- @identity_map ||= Praxis::Mapper::IdentityMap.new
127
- end
128
-
129
- def identity_map=(map)
130
- @identity_map = map
131
- end
132
-
133
- def identity_map?
134
- !@identity_map.nil?
135
- end
136
-
137
- def silence_mapper_stats
138
- @silence_mapper_stats
139
- end
140
-
141
- def silence_mapper_stats=(value)
142
- @silence_mapper_stats = value
143
- end
144
-
145
- end
146
-
147
- module Controller
148
- extend ActiveSupport::Concern
149
-
150
- included do
151
- # Ensure we call #release on any identity map
152
- # that may be set by the controller after the action
153
- # completes.
154
- around :action do |controller, callee|
155
- begin
156
- callee.call
157
- ensure
158
- if controller.request.identity_map?
159
- controller.request.identity_map.release
160
- end
161
- end
162
- end
163
- end
164
-
165
- def identity_map
166
- request.identity_map
167
- end
168
-
169
- end
170
-
171
- module Statistics
172
-
173
- def self.log(request, identity_map, log_stats)
174
- return if identity_map.nil?
175
- return if request.silence_mapper_stats == true
176
- if identity_map.queries.empty?
177
- self.to_logger "No database interactions observed."
178
- return
179
- end
180
-
181
-
182
- case log_stats
183
- when 'detailed'
184
- self.detailed(identity_map)
185
- when 'short'
186
- self.short(identity_map)
187
- when 'skip'
188
- # Shouldn't receive this. But anyway...no-op.
189
- end
190
- end
191
-
192
- def self.detailed(identity_map)
193
- stats_by_model = identity_map.query_statistics.sum_totals_by_model
194
- stats_total = identity_map.query_statistics.sum_totals
195
- fields = [ :query_count, :records_loaded, :datastore_interactions, :datastore_interaction_time]
196
- rows = []
197
-
198
- total_models_loaded = 0
199
- # stats per model
200
- stats_by_model.each do |model, totals|
201
- total_values = totals.values_at(*fields)
202
- self.round_fields_at( total_values , [fields.index(:datastore_interaction_time)])
203
- row = [ model ] + total_values
204
- models_loaded = identity_map.all(model).size
205
- total_models_loaded += models_loaded
206
- row << models_loaded
207
- rows << row
208
- end
209
-
210
- rows << :separator
211
-
212
- # totals for all models
213
- stats_total_values = stats_total.values_at(*fields)
214
- self.round_fields_at(stats_total_values , [fields.index(:datastore_interaction_time)])
215
- rows << [ "All Models" ] + stats_total_values + [total_models_loaded]
216
-
217
- table = Terminal::Table.new \
218
- :rows => rows,
219
- :title => "Praxis::Mapper Statistics",
220
- :headings => [ "Model", "# Queries", "Records Loaded", "Interactions", "Time(sec)", "Models Loaded" ]
221
-
222
- table.align_column(1, :right)
223
- table.align_column(2, :right)
224
- table.align_column(3, :right)
225
- table.align_column(4, :right)
226
- table.align_column(5, :right)
227
- self.to_logger "\n#{table.to_s}"
228
- end
229
-
230
- def self.round_fields_at(values, indices)
231
- indices.each do |idx|
232
- values[idx] = "%.3f" % values[idx]
233
- end
234
- end
235
-
236
- def self.short(identity_map)
237
- self.to_logger identity_map.query_statistics.sum_totals.to_s
238
- end
239
-
240
- def self.to_logger(message)
241
- Praxis::Application.current_instance.logger.__send__(Plugin.instance.config.stats_log_level, "Praxis::Mapper Statistics: #{message}")
242
- end
243
- end
244
- end
245
- end
246
- end
@@ -1,157 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe Praxis::MediaTypeCollection do
4
-
5
- subject!(:media_type_collection) do
6
- silence_warnings do
7
- klass = Class.new(Praxis::MediaTypeCollection) do
8
- member_type VolumeSnapshot
9
- description 'A container for a collection of Volumes'
10
- display_name 'Volumes Collection'
11
-
12
- attributes do
13
- attribute :name, String, regexp: /snapshots-(\w+)/
14
- attribute :size, Integer
15
- attribute :href, String
16
- end
17
-
18
- view :link do
19
- attribute :name
20
- attribute :size
21
- attribute :href
22
- end
23
-
24
- member_view :default, using: :default
25
- end
26
-
27
- klass.finalize!
28
- klass
29
- end
30
- end
31
-
32
- context '.member_type' do
33
- its(:member_type){ should be(VolumeSnapshot) }
34
- its(:member_attribute){ should be_kind_of(Attributor::Attribute) }
35
- its('member_attribute.type'){ should be(VolumeSnapshot) }
36
- end
37
-
38
- context '.load' do
39
- context 'with a hash' do
40
- let(:snapshots_data) { {name: 'snapshots', href: '/bob/snapshots' } }
41
- subject(:snapshots) { media_type_collection.load(snapshots_data) }
42
-
43
- its(:name) { should eq(snapshots_data[:name]) }
44
- its(:href) { should eq(snapshots_data[:href]) }
45
-
46
- it 'has no members set' do
47
- expect(snapshots.to_a).to eq([])
48
- end
49
- end
50
-
51
-
52
- context 'loading an array' do
53
- let(:snapshots_data) do
54
- [{id: 1, name: 'snapshot-1'},
55
- {id: 2, name: 'snapshot-2'}]
56
- end
57
-
58
- let(:snapshots) { media_type_collection.load(snapshots_data) }
59
- subject(:members) { snapshots.to_a }
60
-
61
- it 'sets the collection members' do
62
- expect(members).to have(2).items
63
-
64
- expect(members[0].id).to eq(1)
65
- expect(members[0].name).to eq('snapshot-1')
66
- expect(members[1].id).to eq(2)
67
- expect(members[1].name).to eq('snapshot-2')
68
- end
69
-
70
- it 'has no attributes set' do
71
- expect(snapshots.name).to be(nil)
72
- expect(snapshots.size).to be(nil)
73
- expect(snapshots.href).to be(nil)
74
- end
75
-
76
- end
77
- end
78
-
79
- context '#render' do
80
- context 'for standard views' do
81
- let(:snapshots_data) { {name: 'snapshots', href: '/bob/snapshots' } }
82
- let(:snapshots) { media_type_collection.load(snapshots_data) }
83
- subject(:output) { snapshots.render(view: :link) }
84
-
85
- its([:name]) { should eq(snapshots.name)}
86
- its([:size]) { should eq(snapshots.size)}
87
- its([:href]) { should eq(snapshots.href)}
88
- end
89
-
90
- context 'for members' do
91
- let(:snapshots_data) do
92
- [{id: 1, name: 'snapshot-1'},
93
- {id: 2, name: 'snapshot-2'}]
94
- end
95
-
96
- let(:snapshots) { media_type_collection.load(snapshots_data) }
97
-
98
- subject(:output) { media_type_collection.dump(snapshots, view: :default) }
99
-
100
- it { should eq(snapshots.collect(&:render)) }
101
- end
102
-
103
- end
104
-
105
- context '#validate' do
106
-
107
-
108
- context 'with a hash' do
109
- let(:snapshots_data) { {name: 'snapshots-1', href: '/bob/snapshots' } }
110
- subject(:snapshots) { media_type_collection.load(snapshots_data) }
111
-
112
-
113
- it 'validates' do
114
- expect(snapshots.validate).to be_empty
115
- end
116
-
117
- context 'with invalid attributes' do
118
- let(:snapshots_data) { {name: 'notsnapshots', href: '/bob/snapshots' } }
119
- it 'returns the error' do
120
- expect(snapshots.validate).to have(1).item
121
- expect(snapshots.validate[0]).to match(/value \(notsnapshots\) does not match regexp/)
122
- end
123
- end
124
- end
125
-
126
- context 'for an array' do
127
- let(:snapshots_data) do
128
- [{id: 1, name: 'snapshot-1'},
129
- {id: 2, name: 'snapshot-2'}]
130
- end
131
-
132
- subject(:snapshots) { media_type_collection.load(snapshots_data) }
133
-
134
- it 'validates' do
135
- expect(snapshots.validate).to be_empty
136
- end
137
-
138
- context 'with invalid members' do
139
- let(:snapshots_data) do
140
- [{id: 1, name: 'invalid-1'},
141
- {id: 2, name: 'snapshot-2'}]
142
- end
143
-
144
- it 'returns the error' do
145
- expect(snapshots.validate).to have(1).item
146
- expect(snapshots.validate[0]).to match(/value \(invalid-1\) does not match regexp/)
147
- end
148
- end
149
- end
150
- end
151
-
152
- context '#describe' do
153
- subject(:described) { media_type_collection.describe }
154
- its([:description]){ should be(media_type_collection.description)}
155
- its([:display_name]){ should be(media_type_collection.display_name)}
156
- end
157
- end
@@ -1,142 +0,0 @@
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
- context 'Plugin' do
8
- context 'configuration' do
9
- subject { config }
10
- its(:log_stats) { should eq 'detailed' }
11
- its(:stats_log_level) { should eq :info }
12
- its(:repositories) { should have_key("default") }
13
-
14
- context 'default repository' do
15
- subject(:default) { config.repositories['default'] }
16
- its(['type']) { should eq 'sequel' }
17
- it 'has the right connection settings' do
18
- if RUBY_PLATFORM !~ /java/
19
- expect(subject['connection_settings'].dump).to eq( 'adapter' => 'sqlite','database' => ':memory:' )
20
- else
21
- expect(subject['connection_settings'].dump).to eq( 'adapter' => 'jdbc', 'uri' => 'jdbc:sqlite::memory:' )
22
- end
23
-
24
-
25
- end
26
- end
27
-
28
- end
29
- end
30
-
31
- context 'Request' do
32
-
33
- it 'should have identity_map accessors' do
34
- expect(Praxis::Plugins::PraxisMapperPlugin::Request.instance_methods).to include(:identity_map,:identity_map=)
35
- end
36
-
37
- it 'should have silence_mapper_stats accessors' do
38
- expect(Praxis::Plugins::PraxisMapperPlugin::Request.instance_methods)
39
- .to include(:silence_mapper_stats,:silence_mapper_stats=)
40
- end
41
- end
42
- context 'functional test' do
43
-
44
- def app
45
- Praxis::Application.instance
46
- end
47
-
48
- let(:session) { double("session", valid?: true)}
49
-
50
- around(:each) do |example|
51
- orig_level = Praxis::Application.instance.logger.level
52
- Praxis::Application.instance.logger.level = 2
53
- example.run
54
- Praxis::Application.instance.logger.level = orig_level
55
- end
56
-
57
- context 'with no identity_map set in the request' do
58
- it 'does not log stats' do
59
- expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to_not receive(:log)
60
- the_body = StringIO.new("{}") # This is a funny, GET request expecting a body
61
- get '/api/clouds/1/instances/2?api_version=1.0', nil, 'rack.input' => the_body,'CONTENT_TYPE' => "application/json", 'global_session' => session
62
-
63
- expect(last_response.status).to eq(200)
64
- end
65
- end
66
- context 'with an identity_map set in the request' do
67
- it 'logs stats' do
68
- expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to receive(:log).
69
- with(kind_of(Praxis::Request),kind_of(Praxis::Mapper::IdentityMap), 'detailed').
70
- and_call_original
71
- the_body = StringIO.new("{}") # This is a funny, GET request expecting a body
72
- get '/api/clouds/1/instances/2?create_identity_map=true&api_version=1.0', nil, 'rack.input' => the_body,'CONTENT_TYPE' => "application/json", 'global_session' => session
73
-
74
- expect(last_response.status).to eq(200)
75
- end
76
- end
77
-
78
- end
79
-
80
- context 'Statistics' do
81
- context '.log' do
82
- let(:queries){ { some: :queries } }
83
- let(:identity_map) { double('identity_map', queries: queries) }
84
- let(:log_stats){ 'detailed' }
85
- let(:request){ double('request', silence_mapper_stats: false ) }
86
-
87
- after do
88
- Praxis::Plugins::PraxisMapperPlugin::Statistics.log(request, identity_map, log_stats)
89
- end
90
-
91
- context 'when the request silences mapper stats' do
92
- let(:request){ double('request', silence_mapper_stats: true ) }
93
- it 'should not log anything' do
94
- expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to_not receive(:to_logger)
95
- end
96
- end
97
-
98
- context 'without the request silencing mapper stats' do
99
- context 'when log_stats = detailed' do
100
- it 'should call the detailed method' do
101
- expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to receive(:detailed).with(identity_map)
102
- end
103
- end
104
-
105
- context 'when log_stats = short' do
106
- let(:log_stats){ 'short' }
107
- it 'should call the short method' do
108
- expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to receive(:short).with(identity_map)
109
- end
110
- end
111
-
112
- context 'when log_stats = skip' do
113
- let(:log_stats){ 'skip' }
114
-
115
- it 'should not log anything' do
116
- expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to_not receive(:to_logger)
117
- end
118
- end
119
-
120
- context 'when there is no identity map' do
121
- let(:identity_map) { nil }
122
- it 'should not log anything' do
123
- expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to_not receive(:to_logger)
124
- end
125
- end
126
-
127
- context 'when no queries are logged in the identity map' do
128
- let(:queries){ {} }
129
- it 'should log a special message' do
130
- expect(Praxis::Plugins::PraxisMapperPlugin::Statistics).to receive(:to_logger)
131
- .with("No database interactions observed.")
132
- end
133
- end
134
-
135
- end
136
- end
137
-
138
- it 'has specs for testing the detailed log output'
139
- it 'has specs for testing the short log output'
140
- end
141
-
142
- end