praxis 0.21 → 2.0.pre.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +8 -15
  3. data/CHANGELOG.md +328 -299
  4. data/CONTRIBUTING.md +4 -4
  5. data/README.md +11 -9
  6. data/lib/api_browser/app/js/directives/attribute_table.js +2 -1
  7. data/lib/api_browser/app/js/directives/conditional_requirements.js +13 -0
  8. data/lib/api_browser/app/js/directives/type_placeholder.js +10 -1
  9. data/lib/api_browser/app/js/factories/normalize_attributes.js +4 -2
  10. data/lib/api_browser/app/js/factories/template_for.js +5 -2
  11. data/lib/api_browser/app/js/filters/has_requirement.js +14 -0
  12. data/lib/api_browser/app/js/filters/tag_requirement.js +13 -0
  13. data/lib/api_browser/app/sass/praxis.scss +11 -0
  14. data/lib/api_browser/app/views/action.html +2 -2
  15. data/lib/api_browser/app/views/directives/attribute_description/member_options.html +2 -2
  16. data/lib/api_browser/app/views/directives/attribute_table.html +1 -1
  17. data/lib/api_browser/app/views/type.html +1 -1
  18. data/lib/api_browser/app/views/type/details.html +2 -2
  19. data/lib/api_browser/app/views/types/embedded/array.html +2 -0
  20. data/lib/api_browser/app/views/types/embedded/default.html +3 -1
  21. data/lib/api_browser/app/views/types/embedded/requirements.html +6 -0
  22. data/lib/api_browser/app/views/types/embedded/single_req.html +9 -0
  23. data/lib/api_browser/app/views/types/embedded/struct.html +14 -2
  24. data/lib/api_browser/app/views/types/standalone/array.html +1 -1
  25. data/lib/api_browser/app/views/types/standalone/struct.html +2 -1
  26. data/lib/api_browser/package.json +1 -1
  27. data/lib/praxis.rb +9 -3
  28. data/lib/praxis/action_definition.rb +1 -1
  29. data/lib/praxis/action_definition/headers_dsl_compiler.rb +1 -1
  30. data/lib/praxis/application.rb +1 -9
  31. data/lib/praxis/bootloader.rb +1 -4
  32. data/lib/praxis/config.rb +1 -1
  33. data/lib/praxis/dispatcher.rb +10 -6
  34. data/lib/praxis/docs/generator.rb +2 -1
  35. data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +180 -0
  36. data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +273 -0
  37. data/lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb +125 -0
  38. data/lib/praxis/extensions/field_selection.rb +1 -9
  39. data/lib/praxis/extensions/field_selection/active_record_query_selector.rb +51 -0
  40. data/lib/praxis/extensions/field_selection/sequel_query_selector.rb +61 -0
  41. data/lib/praxis/extensions/rails_compat.rb +2 -0
  42. data/lib/praxis/extensions/rails_compat/request_methods.rb +19 -0
  43. data/lib/praxis/handlers/xml.rb +1 -1
  44. data/lib/praxis/mapper/active_model_compat.rb +98 -0
  45. data/lib/praxis/mapper/resource.rb +242 -0
  46. data/lib/praxis/mapper/selector_generator.rb +149 -0
  47. data/lib/praxis/mapper/sequel_compat.rb +76 -0
  48. data/lib/praxis/media_type_identifier.rb +2 -1
  49. data/lib/praxis/middleware_app.rb +20 -2
  50. data/lib/praxis/multipart/parser.rb +14 -2
  51. data/lib/praxis/notifications.rb +1 -1
  52. data/lib/praxis/plugins/mapper_plugin.rb +64 -0
  53. data/lib/praxis/plugins/rails_plugin.rb +104 -0
  54. data/lib/praxis/request.rb +7 -1
  55. data/lib/praxis/request_superclassing.rb +11 -0
  56. data/lib/praxis/resource_definition.rb +5 -5
  57. data/lib/praxis/response.rb +1 -1
  58. data/lib/praxis/route.rb +1 -1
  59. data/lib/praxis/routing_config.rb +1 -1
  60. data/lib/praxis/trait.rb +1 -1
  61. data/lib/praxis/types/media_type_common.rb +2 -2
  62. data/lib/praxis/types/multipart.rb +1 -1
  63. data/lib/praxis/types/multipart_array.rb +2 -2
  64. data/lib/praxis/types/multipart_array/part_definition.rb +1 -1
  65. data/lib/praxis/version.rb +1 -1
  66. data/praxis.gemspec +14 -13
  67. data/spec/functional_spec.rb +4 -7
  68. data/spec/praxis/action_definition_spec.rb +1 -1
  69. data/spec/praxis/application_spec.rb +1 -1
  70. data/spec/praxis/collection_spec.rb +3 -2
  71. data/spec/praxis/config_spec.rb +2 -2
  72. data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +106 -0
  73. data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +147 -0
  74. data/spec/praxis/extensions/field_selection/support/spec_resources_active_model.rb +130 -0
  75. data/spec/praxis/extensions/field_selection/support/spec_resources_sequel.rb +106 -0
  76. data/spec/praxis/handlers/xml_spec.rb +2 -2
  77. data/spec/praxis/mapper/resource_spec.rb +169 -0
  78. data/spec/praxis/mapper/selector_generator_spec.rb +293 -0
  79. data/spec/praxis/media_type_spec.rb +0 -10
  80. data/spec/praxis/middleware_app_spec.rb +29 -9
  81. data/spec/praxis/request_stages/action_spec.rb +8 -1
  82. data/spec/praxis/response_definition_spec.rb +7 -4
  83. data/spec/praxis/response_spec.rb +1 -1
  84. data/spec/praxis/responses/internal_server_error_spec.rb +2 -2
  85. data/spec/praxis/responses/validation_error_spec.rb +2 -2
  86. data/spec/praxis/router_spec.rb +1 -1
  87. data/spec/spec_app/app/controllers/instances.rb +1 -1
  88. data/spec/spec_app/config/environment.rb +3 -21
  89. data/spec/spec_helper.rb +11 -15
  90. data/spec/support/be_deep_equal_matcher.rb +39 -0
  91. data/spec/support/spec_resources.rb +124 -0
  92. data/tasks/thor/templates/generator/empty_app/Gemfile +3 -3
  93. metadata +102 -77
  94. data/.ruby-version +0 -1
  95. data/lib/praxis/extensions/mapper_selectors.rb +0 -16
  96. data/lib/praxis/media_type_collection.rb +0 -127
  97. data/lib/praxis/plugins/praxis_mapper_plugin.rb +0 -246
  98. data/lib/praxis/stats.rb +0 -113
  99. data/spec/praxis/media_type_collection_spec.rb +0 -157
  100. data/spec/praxis/plugins/praxis_mapper_plugin_spec.rb +0 -142
  101. data/spec/praxis/stats_spec.rb +0 -9
  102. data/spec/spec_app/app/models/person.rb +0 -3
@@ -1 +0,0 @@
1
- 2.3
@@ -1,16 +0,0 @@
1
- module Praxis
2
- module Extensions
3
- module MapperSelectors
4
- extend ActiveSupport::Concern
5
- include FieldExpansion
6
-
7
- def set_selectors
8
- return unless self.media_type.respond_to?(:domain_model) &&
9
- self.media_type.domain_model < Praxis::Mapper::Resource
10
-
11
- resolved = Praxis::MediaType::FieldResolver.resolve(self.media_type, self.expanded_fields)
12
- identity_map.add_selectors(self.media_type.domain_model, resolved)
13
- end
14
- end
15
- end
16
- end
@@ -1,127 +0,0 @@
1
- module Praxis
2
-
3
- module StructCollection
4
- def self.included(klass)
5
- klass.instance_eval do
6
- include(Enumerable)
7
- end
8
- end
9
-
10
- def _members=(members)
11
- @members = members
12
- end
13
-
14
- def _members
15
- @members || []
16
- end
17
-
18
- def each
19
- _members.each { |member| yield(member) }
20
- end
21
- end
22
-
23
- class MediaTypeCollection < MediaType
24
- include Enumerable
25
-
26
- class << self
27
- attr_accessor :member_attribute
28
- end
29
-
30
- def self.inherited(klass)
31
- warn "DEPRECATION: MediaTypeCollection is deprecated and will be removed by 1.0"
32
- super
33
- end
34
-
35
- def self._finalize!
36
- super
37
-
38
- if const_defined?(:Struct, false)
39
- self::Struct.instance_eval do
40
- include StructCollection
41
- end
42
- end
43
- end
44
-
45
- def self.member_type(type=nil)
46
- return ( @member_attribute ? @member_attribute.type : nil) unless type
47
- raise ArgumentError, "invalid type: #{type.name}" unless type < MediaType
48
-
49
- member_options = {}
50
- @member_attribute = Attributor::Attribute.new type, member_options
51
- end
52
-
53
- def self.example(context=nil, options: {})
54
- result = super
55
-
56
- context = case context
57
- when nil
58
- ["#{self.name}-#{values.object_id.to_s}"]
59
- when ::String
60
- [context]
61
- else
62
- context
63
- end
64
-
65
- members = []
66
- size = rand(3) + 1
67
-
68
- size.times do |i|
69
- subcontext = context + ["at(#{i})"]
70
- members << @member_attribute.example(subcontext)
71
- end
72
-
73
- result.object._members = members
74
- result
75
- end
76
-
77
- def self.load(value,context=Attributor::DEFAULT_ROOT_CONTEXT, **options)
78
- if value.kind_of?(String)
79
- value = JSON.parse(value)
80
- end
81
-
82
- case value
83
- when nil, self
84
- value
85
- when Hash
86
- # Need to parse/deserialize first
87
- self.new(self.attribute.load(value,context, **options))
88
- when Array, Praxis::Mapper::ResourceDecorator
89
- object = self.attribute.load({})
90
- object._members = value.collect { |subvalue| @member_attribute.load(subvalue) }
91
- self.new(object)
92
- else
93
- # Just wrap whatever value
94
- self.new(value)
95
- end
96
- end
97
-
98
- def self.describe(shallow = false)
99
- hash = super
100
- hash[:member_attribute] = member_attribute.describe(true)
101
- hash
102
- end
103
-
104
- def self.member_view(name, using: nil)
105
- if using
106
- member_view = self.member_type.view(using)
107
- return self.views[name] = CollectionView.new(name, self.member_type, member_view)
108
- end
109
-
110
- self.views[name]
111
- end
112
-
113
-
114
- def each
115
- @object.each { |member| yield(member) }
116
- end
117
-
118
-
119
- def validate(context=Attributor::DEFAULT_ROOT_CONTEXT)
120
- errors = super
121
- self.each_with_object(errors) do |member, errors|
122
- errors.push(*member.validate(context))
123
- end
124
- end
125
-
126
- end
127
- end
@@ -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.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.instance.logger.__send__(Plugin.instance.config.stats_log_level, "Praxis::Mapper Statistics: #{message}")
242
- end
243
- end
244
- end
245
- end
246
- end
@@ -1,113 +0,0 @@
1
- require 'singleton'
2
-
3
- require 'harness'
4
-
5
- module Praxis
6
-
7
- module Stats
8
- include Praxis::PluginConcern
9
-
10
- class Statsd < ::Statsd
11
- def initialize(host: '127.0.0.1', port: 8125, prefix: nil, postfix: nil)
12
- self.host = host
13
- self.port = port
14
- self.namespace = prefix if prefix
15
- self.postfix = postfix
16
- @batch_size = 10
17
- end
18
- end
19
-
20
- class Plugin < Praxis::Plugin
21
- include Singleton
22
-
23
- def initialize
24
- @options = {config_file: 'config/stats.yml'}
25
- end
26
-
27
- def config_key
28
- :stats # 'praxis.stats'
29
- end
30
-
31
- def prepare_config!(node)
32
- node.attributes do
33
- attribute :collector, Hash, default: {type: 'Harness::FakeCollector'} do
34
- key :type, String, required: true
35
- key :args, Hash
36
- end
37
- attribute :queue, Hash, default: {type: 'Harness::AsyncQueue' } do
38
- key :type, String, required: true
39
- key :args, Hash
40
- end
41
- end
42
- end
43
-
44
- def setup!
45
- Harness.config.collector = load_type(config.collector)
46
- Harness.config.queue = load_type(config.queue)
47
- end
48
-
49
- def load_type(hash)
50
- type = hash[:type].constantize
51
- args = hash[:args]
52
- args_hash = case args
53
- when Attributor::Hash
54
- args.contents.symbolize_keys
55
- when Hash
56
- args.symbolize_keys
57
- when nil
58
- {}
59
- else
60
- raise "unknown args type: #{args.class.name}"
61
- end
62
-
63
- if args_hash.any?
64
- type.new(**args_hash)
65
- else
66
- type.new
67
- end
68
-
69
- end
70
-
71
- end
72
-
73
-
74
- def self.collector
75
- Harness.collector
76
- end
77
-
78
- def self.config
79
- Harness.config
80
- end
81
-
82
- def self.queue
83
- Harness.queue
84
- end
85
-
86
- def self.count(*args)
87
- Harness.count(*args)
88
- end
89
-
90
- def self.decrement(*args)
91
- Harness.decrement(*args)
92
- end
93
-
94
- def self.gauge(*args)
95
- Harness.gauge(*args)
96
- end
97
-
98
- def self.increment(*args)
99
- Harness.increment(*args)
100
- end
101
-
102
- def self.time(stat, sample_rate = 1, &block)
103
- Harness.time(stat, sample_rate, &block)
104
- end
105
-
106
- def self.timing(*args)
107
- Harness.timing(*args)
108
- end
109
-
110
-
111
- end
112
-
113
- end