praxis 0.21 → 2.0.pre.3

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