praxis 2.0.pre.17 → 2.0.pre.21

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 (235) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +54 -0
  3. data/.simplecov +3 -1
  4. data/.travis.yml +2 -1
  5. data/CHANGELOG.md +19 -0
  6. data/CONTRIBUTING.md +2 -79
  7. data/Gemfile +5 -1
  8. data/Guardfile +6 -4
  9. data/LICENSE +0 -2
  10. data/MAINTAINERS.md +1 -0
  11. data/README.md +15 -22
  12. data/Rakefile +4 -2
  13. data/bin/praxis +55 -58
  14. data/lib/praxis/action_definition/headers_dsl_compiler.rb +5 -6
  15. data/lib/praxis/action_definition.rb +65 -95
  16. data/lib/praxis/api_definition.rb +21 -29
  17. data/lib/praxis/api_general_info.rb +55 -66
  18. data/lib/praxis/application.rb +15 -32
  19. data/lib/praxis/blueprint.rb +80 -73
  20. data/lib/praxis/bootloader.rb +24 -33
  21. data/lib/praxis/bootloader_stages/environment.rb +5 -10
  22. data/lib/praxis/bootloader_stages/file_loader.rb +3 -6
  23. data/lib/praxis/bootloader_stages/plugin_config_load.rb +4 -6
  24. data/lib/praxis/bootloader_stages/plugin_config_prepare.rb +2 -2
  25. data/lib/praxis/bootloader_stages/plugin_loader.rb +3 -7
  26. data/lib/praxis/bootloader_stages/plugin_setup.rb +3 -3
  27. data/lib/praxis/bootloader_stages/routing.rb +5 -8
  28. data/lib/praxis/bootloader_stages/subgroup_loader.rb +2 -10
  29. data/lib/praxis/bootloader_stages/warn_unloaded_files.rb +15 -19
  30. data/lib/praxis/callbacks.rb +12 -11
  31. data/lib/praxis/collection.rb +11 -14
  32. data/lib/praxis/config.rb +17 -28
  33. data/lib/praxis/config_hash.rb +2 -1
  34. data/lib/praxis/controller.rb +7 -6
  35. data/lib/praxis/dispatcher.rb +34 -42
  36. data/lib/praxis/docs/open_api/info_object.rb +11 -8
  37. data/lib/praxis/docs/open_api/media_type_object.rb +18 -17
  38. data/lib/praxis/docs/open_api/operation_object.rb +7 -4
  39. data/lib/praxis/docs/open_api/parameter_object.rb +17 -14
  40. data/lib/praxis/docs/open_api/paths_object.rb +11 -9
  41. data/lib/praxis/docs/open_api/request_body_object.rb +14 -13
  42. data/lib/praxis/docs/open_api/response_object.rb +24 -18
  43. data/lib/praxis/docs/open_api/responses_object.rb +3 -1
  44. data/lib/praxis/docs/open_api/schema_object.rb +61 -29
  45. data/lib/praxis/docs/open_api/server_object.rb +5 -2
  46. data/lib/praxis/docs/open_api/tag_object.rb +9 -6
  47. data/lib/praxis/docs/open_api_generator.rb +114 -150
  48. data/lib/praxis/endpoint_definition.rb +60 -77
  49. data/lib/praxis/error_handler.rb +2 -2
  50. data/lib/praxis/exception.rb +2 -0
  51. data/lib/praxis/exceptions/config.rb +3 -1
  52. data/lib/praxis/exceptions/config_load.rb +2 -0
  53. data/lib/praxis/exceptions/config_validation.rb +3 -1
  54. data/lib/praxis/exceptions/invalid_configuration.rb +3 -1
  55. data/lib/praxis/exceptions/invalid_response.rb +3 -1
  56. data/lib/praxis/exceptions/invalid_trait.rb +3 -1
  57. data/lib/praxis/exceptions/stage_not_found.rb +3 -1
  58. data/lib/praxis/exceptions/validation.rb +4 -3
  59. data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +163 -149
  60. data/lib/praxis/extensions/attribute_filtering/active_record_patches/5x.rb +18 -13
  61. data/lib/praxis/extensions/attribute_filtering/active_record_patches/6_0.rb +13 -9
  62. data/lib/praxis/extensions/attribute_filtering/active_record_patches/6_1_plus.rb +14 -11
  63. data/lib/praxis/extensions/attribute_filtering/active_record_patches.rb +12 -9
  64. data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +8 -5
  65. data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +89 -65
  66. data/lib/praxis/extensions/attribute_filtering/filters_parser.rb +68 -62
  67. data/lib/praxis/extensions/attribute_filtering.rb +3 -1
  68. data/lib/praxis/extensions/field_expansion.rb +6 -4
  69. data/lib/praxis/extensions/field_selection/active_record_query_selector.rb +10 -8
  70. data/lib/praxis/extensions/field_selection/field_selector.rb +91 -92
  71. data/lib/praxis/extensions/field_selection/sequel_query_selector.rb +12 -12
  72. data/lib/praxis/extensions/field_selection.rb +3 -1
  73. data/lib/praxis/extensions/pagination/active_record_pagination_handler.rb +6 -4
  74. data/lib/praxis/extensions/pagination/header_generator.rb +16 -11
  75. data/lib/praxis/extensions/pagination/ordering_params.rb +29 -28
  76. data/lib/praxis/extensions/pagination/pagination_handler.rb +44 -42
  77. data/lib/praxis/extensions/pagination/pagination_params.rb +29 -48
  78. data/lib/praxis/extensions/pagination/sequel_pagination_handler.rb +8 -7
  79. data/lib/praxis/extensions/pagination.rb +10 -15
  80. data/lib/praxis/extensions/rails_compat/request_methods.rb +3 -4
  81. data/lib/praxis/extensions/rails_compat.rb +2 -0
  82. data/lib/praxis/extensions/rendering.rb +12 -12
  83. data/lib/praxis/field_expander.rb +8 -9
  84. data/lib/praxis/file_group.rb +8 -12
  85. data/lib/praxis/finalizable.rb +1 -0
  86. data/lib/praxis/handlers/json.rb +5 -2
  87. data/lib/praxis/handlers/plain.rb +2 -1
  88. data/lib/praxis/handlers/www_form.rb +6 -3
  89. data/lib/praxis/handlers/{xml-sample.rb → xml_sample.rb} +26 -22
  90. data/lib/praxis/mapper/active_model_compat.rb +13 -10
  91. data/lib/praxis/mapper/resource.rb +196 -181
  92. data/lib/praxis/mapper/selector_generator.rb +106 -112
  93. data/lib/praxis/mapper/sequel_compat.rb +70 -67
  94. data/lib/praxis/media_type.rb +2 -2
  95. data/lib/praxis/media_type_identifier.rb +26 -22
  96. data/lib/praxis/middleware_app.rb +18 -15
  97. data/lib/praxis/multipart/parser.rb +46 -51
  98. data/lib/praxis/multipart/part.rb +78 -110
  99. data/lib/praxis/notifications.rb +2 -4
  100. data/lib/praxis/plugin.rb +11 -18
  101. data/lib/praxis/plugin_concern.rb +12 -15
  102. data/lib/praxis/plugins/mapper_plugin.rb +15 -13
  103. data/lib/praxis/plugins/pagination_plugin.rb +8 -6
  104. data/lib/praxis/plugins/rails_plugin.rb +33 -28
  105. data/lib/praxis/renderer.rb +11 -15
  106. data/lib/praxis/request.rb +48 -44
  107. data/lib/praxis/request_stages/action.rb +4 -6
  108. data/lib/praxis/request_stages/load_request.rb +2 -4
  109. data/lib/praxis/request_stages/request_stage.rb +19 -23
  110. data/lib/praxis/request_stages/response.rb +4 -6
  111. data/lib/praxis/request_stages/validate.rb +3 -5
  112. data/lib/praxis/request_stages/validate_params_and_headers.rb +15 -22
  113. data/lib/praxis/request_stages/validate_payload.rb +25 -28
  114. data/lib/praxis/request_superclassing.rb +3 -3
  115. data/lib/praxis/resource_definition.rb +1 -0
  116. data/lib/praxis/response.rb +24 -26
  117. data/lib/praxis/response_definition.rb +77 -122
  118. data/lib/praxis/response_template.rb +11 -15
  119. data/lib/praxis/responses/http.rb +23 -44
  120. data/lib/praxis/responses/internal_server_error.rb +18 -21
  121. data/lib/praxis/responses/multipart_ok.rb +4 -9
  122. data/lib/praxis/responses/validation_error.rb +8 -15
  123. data/lib/praxis/route.rb +8 -10
  124. data/lib/praxis/router/rack.rb +13 -7
  125. data/lib/praxis/router/simple.rb +10 -5
  126. data/lib/praxis/router.rb +27 -34
  127. data/lib/praxis/routing_config.rb +52 -29
  128. data/lib/praxis/simple_media_type.rb +5 -8
  129. data/lib/praxis/stage.rb +17 -25
  130. data/lib/praxis/tasks/api_docs.rb +17 -16
  131. data/lib/praxis/tasks/console.rb +3 -1
  132. data/lib/praxis/tasks/environment.rb +2 -0
  133. data/lib/praxis/tasks/routes.rb +26 -24
  134. data/lib/praxis/tasks.rb +3 -1
  135. data/lib/praxis/trait.rb +37 -46
  136. data/lib/praxis/types/fuzzy_hash.rb +13 -14
  137. data/lib/praxis/types/media_type_common.rb +11 -10
  138. data/lib/praxis/types/multipart_array/part_definition.rb +14 -17
  139. data/lib/praxis/types/multipart_array.rb +100 -115
  140. data/lib/praxis/validation_handler.rb +5 -3
  141. data/lib/praxis/version.rb +3 -1
  142. data/lib/praxis.rb +4 -5
  143. data/praxis.gemspec +22 -21
  144. data/spec/functional_spec.rb +44 -56
  145. data/spec/praxis/action_definition_spec.rb +39 -48
  146. data/spec/praxis/api_definition_spec.rb +45 -47
  147. data/spec/praxis/api_general_info_spec.rb +28 -29
  148. data/spec/praxis/application_spec.rb +18 -14
  149. data/spec/praxis/blueprint_spec.rb +33 -34
  150. data/spec/praxis/bootloader_spec.rb +32 -30
  151. data/spec/praxis/callbacks_spec.rb +37 -37
  152. data/spec/praxis/collection_spec.rb +18 -25
  153. data/spec/praxis/config_hash_spec.rb +5 -4
  154. data/spec/praxis/config_spec.rb +27 -26
  155. data/spec/praxis/controller_spec.rb +8 -9
  156. data/spec/praxis/endpoint_definition_spec.rb +25 -32
  157. data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +171 -114
  158. data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +22 -21
  159. data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +112 -60
  160. data/spec/praxis/extensions/attribute_filtering/filters_parser_spec.rb +37 -38
  161. data/spec/praxis/extensions/field_expansion_spec.rb +8 -10
  162. data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +14 -13
  163. data/spec/praxis/extensions/field_selection/field_selector_spec.rb +9 -16
  164. data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +50 -49
  165. data/spec/praxis/extensions/pagination/active_record_pagination_handler_spec.rb +32 -31
  166. data/spec/praxis/extensions/rendering_spec.rb +9 -9
  167. data/spec/praxis/extensions/support/spec_resources_active_model.rb +32 -49
  168. data/spec/praxis/extensions/support/spec_resources_sequel.rb +48 -48
  169. data/spec/praxis/field_expander_spec.rb +6 -5
  170. data/spec/praxis/file_group_spec.rb +3 -1
  171. data/spec/praxis/handlers/json_spec.rb +6 -5
  172. data/spec/praxis/mapper/resource_spec.rb +39 -29
  173. data/spec/praxis/mapper/selector_generator_spec.rb +80 -46
  174. data/spec/praxis/media_type_identifier_spec.rb +13 -10
  175. data/spec/praxis/media_type_spec.rb +12 -12
  176. data/spec/praxis/middleware_app_spec.rb +23 -22
  177. data/spec/praxis/multipart/parser_spec.rb +7 -9
  178. data/spec/praxis/notifications_spec.rb +4 -4
  179. data/spec/praxis/plugin_concern_spec.rb +5 -6
  180. data/spec/praxis/renderer_spec.rb +10 -9
  181. data/spec/praxis/request_spec.rb +38 -41
  182. data/spec/praxis/request_stages/action_spec.rb +14 -15
  183. data/spec/praxis/request_stages/request_stage_spec.rb +30 -41
  184. data/spec/praxis/request_stages/validate_spec.rb +3 -1
  185. data/spec/praxis/response_definition_spec.rb +79 -92
  186. data/spec/praxis/response_spec.rb +35 -40
  187. data/spec/praxis/responses/internal_server_error_spec.rb +6 -9
  188. data/spec/praxis/responses/validation_error_spec.rb +17 -18
  189. data/spec/praxis/route_spec.rb +4 -7
  190. data/spec/praxis/router_spec.rb +69 -79
  191. data/spec/praxis/routing_config_spec.rb +15 -14
  192. data/spec/praxis/stage_spec.rb +56 -53
  193. data/spec/praxis/trait_spec.rb +17 -17
  194. data/spec/praxis/types/fuzzy_hash_spec.rb +11 -9
  195. data/spec/praxis/types/multipart_array/part_definition_spec.rb +3 -2
  196. data/spec/praxis/types/multipart_array_spec.rb +33 -48
  197. data/spec/spec_app/app/concerns/authenticated.rb +5 -5
  198. data/spec/spec_app/app/concerns/basic_api.rb +3 -1
  199. data/spec/spec_app/app/concerns/log_wrapper.rb +5 -3
  200. data/spec/spec_app/app/controllers/base_class.rb +6 -5
  201. data/spec/spec_app/app/controllers/instances.rb +31 -34
  202. data/spec/spec_app/app/controllers/volumes.rb +6 -6
  203. data/spec/spec_app/app/responses/multipart.rb +1 -2
  204. data/spec/spec_app/app/responses/other_response.rb +2 -2
  205. data/spec/spec_app/config/environment.rb +19 -6
  206. data/spec/spec_app/config.ru +4 -3
  207. data/spec/spec_app/design/api.rb +13 -15
  208. data/spec/spec_app/design/media_types/instance.rb +6 -6
  209. data/spec/spec_app/design/media_types/volume.rb +2 -1
  210. data/spec/spec_app/design/media_types/volume_snapshot.rb +2 -1
  211. data/spec/spec_app/design/resources/instances.rb +11 -17
  212. data/spec/spec_app/design/resources/volume_snapshots.rb +4 -5
  213. data/spec/spec_app/design/resources/volumes.rb +4 -5
  214. data/spec/spec_helper.rb +11 -13
  215. data/spec/support/be_deep_equal_matcher.rb +5 -0
  216. data/spec/support/spec_authorization_plugin.rb +7 -12
  217. data/spec/support/spec_blueprints.rb +5 -4
  218. data/spec/support/spec_complex_authentication_plugin.rb +17 -34
  219. data/spec/support/spec_endpoint_definitions.rb +2 -3
  220. data/spec/support/spec_media_types.rb +28 -35
  221. data/spec/support/spec_resources.rb +22 -16
  222. data/spec/support/spec_simple_authentication_plugin.rb +5 -9
  223. data/tasks/loader.thor +4 -2
  224. data/tasks/thor/app.rb +7 -5
  225. data/tasks/thor/example.rb +23 -22
  226. data/tasks/thor/model.rb +7 -7
  227. data/tasks/thor/scaffold.rb +23 -23
  228. data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +0 -8
  229. data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +1 -2
  230. metadata +72 -84
  231. data/MAINTAINERS +0 -2
  232. data/TODO.md +0 -25
  233. data/spec/praxis/api_resource_spec.rb +0 -0
  234. data/spec/praxis/dispatcher_spec.rb +0 -0
  235. data/spec/spec_app/app/responses/bulk_response.rb +0 -0
@@ -1,151 +1,145 @@
1
- module Praxis::Mapper
1
+ # frozen_string_literal: true
2
2
 
3
- class SelectorGeneratorNode
4
- attr_reader :select, :model, :resource, :tracks
3
+ module Praxis
4
+ module Mapper
5
+ class SelectorGeneratorNode
6
+ attr_reader :select, :model, :resource, :tracks
5
7
 
6
- def initialize(resource)
7
- @resource = resource
8
+ def initialize(resource)
9
+ @resource = resource
8
10
 
9
- @select = Set.new
10
- @select_star = false
11
- @tracks = Hash.new
12
- end
13
-
14
- def add(fields)
15
- fields.each do |name, field|
16
- map_property(name, field)
11
+ @select = Set.new
12
+ @select_star = false
13
+ @tracks = {}
17
14
  end
18
- self
19
- end
20
15
 
21
- def map_property(name, fields)
22
- praxis_compat_model = resource.model && resource.model.respond_to?(:_praxis_associations)
23
- if resource.properties.key?(name)
24
- add_property(name, fields)
25
- elsif praxis_compat_model && resource.model._praxis_associations.key?(name)
26
- add_association(name, fields)
27
- else
28
- add_select(name)
16
+ def add(fields)
17
+ fields.each do |name, field|
18
+ map_property(name, field)
19
+ end
20
+ self
29
21
  end
30
- end
31
22
 
32
- def add_association(name, fields)
33
-
34
- association = resource.model._praxis_associations.fetch(name) do
35
- raise "missing association for #{resource} with name #{name}"
36
- end
37
- associated_resource = resource.model_map[association[:model]]
38
- unless associated_resource
39
- raise "Whoops! could not find a resource associated with model #{association[:model]} (root resource #{resource})"
40
- end
41
- # Add the required columns in this model to make sure the association can be loaded
42
- association[:local_key_columns].each {|col| add_select(col) }
43
-
44
- node = SelectorGeneratorNode.new(associated_resource)
45
- unless association[:remote_key_columns].empty?
46
- # Make sure we add the required columns for this association to the remote model query
47
- fields = {} if fields == true
48
- new_fields_as_hash = association[:remote_key_columns].each_with_object({}) do|name, hash|
49
- hash[name] = true
23
+ def map_property(name, fields)
24
+ praxis_compat_model = resource.model&.respond_to?(:_praxis_associations)
25
+ if resource.properties.key?(name)
26
+ add_property(name, fields)
27
+ elsif praxis_compat_model && resource.model._praxis_associations.key?(name)
28
+ add_association(name, fields)
29
+ else
30
+ add_select(name)
50
31
  end
51
- fields = fields.merge(new_fields_as_hash)
52
32
  end
53
33
 
54
- node.add(fields) unless fields == true
34
+ def add_association(name, fields)
35
+ association = resource.model._praxis_associations.fetch(name) do
36
+ raise "missing association for #{resource} with name #{name}"
37
+ end
38
+ associated_resource = resource.model_map[association[:model]]
39
+ raise "Whoops! could not find a resource associated with model #{association[:model]} (root resource #{resource})" unless associated_resource
40
+
41
+ # Add the required columns in this model to make sure the association can be loaded
42
+ association[:local_key_columns].each { |col| add_select(col) }
43
+
44
+ node = SelectorGeneratorNode.new(associated_resource)
45
+ unless association[:remote_key_columns].empty?
46
+ # Make sure we add the required columns for this association to the remote model query
47
+ fields = {} if fields == true
48
+ new_fields_as_hash = association[:remote_key_columns].each_with_object({}) do |key, hash|
49
+ hash[key] = true
50
+ end
51
+ fields = fields.merge(new_fields_as_hash)
52
+ end
53
+
54
+ node.add(fields) unless fields == true
55
55
 
56
- self.merge_track(name, node)
57
- end
56
+ merge_track(name, node)
57
+ end
58
58
 
59
- def add_select(name)
60
- return @select_star = true if name == :*
61
- return if @select_star
62
- @select.add name
63
- end
59
+ def add_select(name)
60
+ return @select_star = true if name == :*
61
+ return if @select_star
64
62
 
65
- def add_property(name, fields)
66
- dependencies = resource.properties[name][:dependencies]
67
- # Always add the underlying association if we're overriding the name...
68
- praxis_compat_model = resource.model && resource.model.respond_to?(:_praxis_associations)
69
- if praxis_compat_model && resource.model._praxis_associations.key?(name)
70
- add_association(name, fields)
63
+ @select.add name
71
64
  end
72
- if dependencies
73
- dependencies.each do |dependency|
65
+
66
+ def add_property(name, fields)
67
+ dependencies = resource.properties[name][:dependencies]
68
+ # Always add the underlying association if we're overriding the name...
69
+ praxis_compat_model = resource.model&.respond_to?(:_praxis_associations)
70
+ add_association(name, fields) if praxis_compat_model && resource.model._praxis_associations.key?(name)
71
+ dependencies&.each do |dependency|
74
72
  # To detect recursion, let's allow mapping depending fields to the same name of the property
75
73
  # but properly detecting if it's a real association...in which case we've already added it above
76
74
  if dependency == name
77
- unless praxis_compat_model && resource.model._praxis_associations.key?(name)
78
- add_select(name)
79
- end
75
+ add_select(name) unless praxis_compat_model && resource.model._praxis_associations.key?(name)
80
76
  else
81
77
  apply_dependency(dependency)
82
78
  end
83
79
  end
84
- end
85
80
 
86
- head, *tail = resource.properties[name][:through]
87
- return if head.nil?
81
+ head, *tail = resource.properties[name][:through]
82
+ return if head.nil?
88
83
 
89
- new_fields = tail.reverse.inject(fields) do |thing, step|
90
- {step => thing}
91
- end
84
+ new_fields = tail.reverse.inject(fields) do |thing, step|
85
+ { step => thing }
86
+ end
92
87
 
93
- add_association(head, new_fields)
94
- end
88
+ add_association(head, new_fields)
89
+ end
95
90
 
96
- def apply_dependency(dependency)
97
- case dependency
98
- when Symbol
99
- map_property(dependency, true)
100
- when String
101
- head, tail = dependency.split('.').collect(&:to_sym)
102
- raise "String dependencies can not be singular" if tail.nil?
91
+ def apply_dependency(dependency)
92
+ case dependency
93
+ when Symbol
94
+ map_property(dependency, true)
95
+ when String
96
+ head, *tail = dependency.split('.').collect(&:to_sym)
97
+ raise 'String dependencies can not be singular' if tail.nil?
103
98
 
104
- add_association(head, {tail => true})
99
+ add_association(head, tail.reverse.inject({}) { |hash, dep| { dep => hash } })
100
+ end
105
101
  end
106
- end
107
102
 
108
- def merge_track( track_name, node )
109
- raise "Cannot merge another node for association #{track_name}: incompatible model" unless node.model == self.model
103
+ def merge_track(track_name, node)
104
+ raise "Cannot merge another node for association #{track_name}: incompatible model" unless node.model == model
110
105
 
111
- existing = self.tracks[track_name]
112
- if existing
113
- node.select.each do|col_name|
114
- existing.add_select(col_name)
115
- end
116
- node.tracks.each do |name, n|
117
- existing.merge_track(name, n)
106
+ existing = tracks[track_name]
107
+ if existing
108
+ node.select.each do |col_name|
109
+ existing.add_select(col_name)
110
+ end
111
+ node.tracks.each do |name, n|
112
+ existing.merge_track(name, n)
113
+ end
114
+ else
115
+ tracks[track_name] = node
118
116
  end
119
- else
120
- self.tracks[track_name] = node
121
117
  end
122
- end
123
118
 
124
- def dump
125
- hash = {}
126
- hash[:model] = resource.model
127
- if !@select.empty? || @select_star
128
- hash[:columns] = @select_star ? [ :* ] : @select.to_a
129
- end
130
- unless @tracks.empty?
131
- hash[:tracks] = @tracks.each_with_object({}) {|(name, node), hash| hash[name] = node.dump }
119
+ def dump
120
+ hash = {}
121
+ hash[:model] = resource.model
122
+ if !@select.empty? || @select_star
123
+ hash[:columns] = @select_star ? [:*] : @select.to_a
124
+ end
125
+ hash[:tracks] = @tracks.transform_values(&:dump) unless @tracks.empty?
126
+ hash
132
127
  end
133
- hash
134
128
  end
135
- end
136
129
 
137
- # Generates a set of selectors given a resource and
138
- # list of resource attributes.
139
- class SelectorGenerator
140
- # Entry point
141
- def add(resource, fields)
142
- @root = SelectorGeneratorNode.new(resource)
143
- @root.add(fields)
144
- self
145
- end
130
+ # Generates a set of selectors given a resource and
131
+ # list of resource attributes.
132
+ class SelectorGenerator
133
+ # Entry point
134
+ def add(resource, fields)
135
+ @root = SelectorGeneratorNode.new(resource)
136
+ @root.add(fields)
137
+ self
138
+ end
146
139
 
147
- def selectors
148
- @root
140
+ def selectors
141
+ @root
142
+ end
149
143
  end
150
144
  end
151
145
  end
@@ -1,84 +1,87 @@
1
- require 'active_support/concern'
1
+ # frozen_string_literal: true
2
2
 
3
+ require 'active_support/concern'
3
4
 
4
- module Praxis::Mapper
5
- module SequelCompat
6
- extend ActiveSupport::Concern
5
+ module Praxis
6
+ module Mapper
7
+ module SequelCompat
8
+ extend ActiveSupport::Concern
7
9
 
8
- included do
9
- attr_accessor :_resource
10
- class <<self
11
- alias_method :find_by, :find # Easy way to be method compatible with AR
12
- end
13
- end
10
+ included do
11
+ attr_accessor :_resource
14
12
 
15
- module ClassMethods
16
- def _filter_query_builder_class
17
- # TODO: refactor the query builder, and add the explicit require in this file
18
- Praxis::Extensions::SequelFilterQueryBuilder
13
+ class << self
14
+ alias_method :find_by, :find # Easy way to be method compatible with AR
15
+ end
19
16
  end
20
17
 
21
- def _field_selector_query_builder_class
22
- Praxis::Extensions::FieldSelection::SequelQuerySelector
23
- end
18
+ module ClassMethods
19
+ def _filter_query_builder_class
20
+ # TODO: refactor the query builder, and add the explicit require in this file
21
+ Praxis::Extensions::SequelFilterQueryBuilder
22
+ end
24
23
 
25
- def _pagination_query_builder_class
26
- Praxis::Extensions::Pagination::SequelPaginationHandler
27
- end
24
+ def _field_selector_query_builder_class
25
+ Praxis::Extensions::FieldSelection::SequelQuerySelector
26
+ end
28
27
 
29
- def _praxis_associations
30
- orig = self.association_reflections.clone
31
- orig.each do |k,v|
32
- v[:model] = v.associated_class
33
- v[:local_key_columns] = local_columns_used_for_the_association(v[:type], v)
34
- v[:remote_key_columns] = remote_columns_used_for_the_association(v[:type], v)
35
- if v.respond_to?(:primary_key)
36
- v[:primary_key] = v.primary_key
37
- else
38
- # FIXME: figure out exactly what to do here.
39
- # not super critical, as we can't track these associations
40
- # directly, but it would be nice to traverse these
41
- # properly.
42
- v[:primary_key] = :unsupported
28
+ def _pagination_query_builder_class
29
+ Praxis::Extensions::Pagination::SequelPaginationHandler
30
+ end
31
+
32
+ def _praxis_associations
33
+ orig = association_reflections.clone
34
+ orig.each do |_k, v|
35
+ v[:model] = v.associated_class
36
+ v[:local_key_columns] = local_columns_used_for_the_association(v[:type], v)
37
+ v[:remote_key_columns] = remote_columns_used_for_the_association(v[:type], v)
38
+ v[:primary_key] = if v.respond_to?(:primary_key)
39
+ v.primary_key
40
+ else
41
+ # FIXME: figure out exactly what to do here.
42
+ # not super critical, as we can't track these associations
43
+ # directly, but it would be nice to traverse these
44
+ # properly.
45
+ :unsupported
46
+ end
43
47
  end
48
+ orig
44
49
  end
45
- orig
46
- end
47
50
 
48
- private
49
- def local_columns_used_for_the_association(type, assoc_reflection)
50
- case type
51
- when :one_to_many
52
- # The associated table (or middle table if many to many) will point to us by PK
53
- assoc_reflection[:primary_key_columns]
54
- when :many_to_one
55
- # We have the FKs to the associated model
56
- assoc_reflection[:keys]
57
- when :many_to_many
58
- # The middle table if many to many) will point to us by key (usually the PK, but not always)
59
- assoc_reflection[:left_primary_keys]
60
- else
61
- raise "association type #{type} not supported"
51
+ private
52
+
53
+ def local_columns_used_for_the_association(type, assoc_reflection)
54
+ case type
55
+ when :one_to_many
56
+ # The associated table (or middle table if many to many) will point to us by PK
57
+ assoc_reflection[:primary_key_columns]
58
+ when :many_to_one
59
+ # We have the FKs to the associated model
60
+ assoc_reflection[:keys]
61
+ when :many_to_many
62
+ # The middle table if many to many) will point to us by key (usually the PK, but not always)
63
+ assoc_reflection[:left_primary_keys]
64
+ else
65
+ raise "association type #{type} not supported"
66
+ end
62
67
  end
63
- end
64
68
 
65
- def remote_columns_used_for_the_association(type, assoc_reflection)
66
- case type
67
- when :one_to_many
68
- # The columns in the associated table that will point back to the original association
69
- assoc_reflection[:keys]
70
- when :many_to_one
71
- # The columns in the associated table that the children will point to (usually the PK, but not always) ??
72
- [assoc_reflection.associated_class.primary_key]
73
- when :many_to_many
74
- # The middle table if many to many will point to us by key (usually the PK, but not always) ??
75
- [assoc_reflection.associated_class.primary_key]
76
- else
77
- raise "association type #{type} not supported"
69
+ def remote_columns_used_for_the_association(type, assoc_reflection)
70
+ case type
71
+ when :one_to_many
72
+ # The columns in the associated table that will point back to the original association
73
+ assoc_reflection[:keys]
74
+ when :many_to_one
75
+ # The columns in the associated table that the children will point to (usually the PK, but not always) ??
76
+ [assoc_reflection.associated_class.primary_key]
77
+ when :many_to_many
78
+ # The middle table if many to many will point to us by key (usually the PK, but not always) ??
79
+ [assoc_reflection.associated_class.primary_key]
80
+ else
81
+ raise "association type #{type} not supported"
82
+ end
78
83
  end
79
84
  end
80
-
81
85
  end
82
-
83
86
  end
84
- end
87
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Praxis
2
4
  # An Internet Media Type as defined in RFC 1590, as used in HTTP (see RFC 2616). As used in the
3
5
  # Praxis framework, media types also define the structure and content of entities of that type:
@@ -43,8 +45,6 @@ module Praxis
43
45
  # end
44
46
  # end
45
47
  class MediaType < Praxis::Blueprint
46
-
47
48
  include Types::MediaTypeCommon
48
49
  end
49
-
50
50
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'set'
2
4
  require 'active_support/core_ext/object/blank'
3
5
 
@@ -10,7 +12,7 @@ module Praxis
10
12
  # the syntax for type identifiers is substantially narrower than what we accept there.
11
13
  #
12
14
  # Note that this ONLY matches type, subtype and suffix; we handle options differently.
13
- VALID_TYPE = /^\s*(?<type>[^\/]+)\/(?<subtype>[^\+]+)(\+(?<suffix>[^; ]+))?\s*$/x.freeze
15
+ VALID_TYPE = %r{^\s*(?<type>[^/]+)/(?<subtype>[^+]+)(?<nothing>\+(?<suffix>[^; ]+))?\s*$}x.freeze
14
16
 
15
17
  # Pattern that separates parameters of a media type from each other, and from the base identifier.
16
18
  PARAMETER_SEPARATOR = /\s*;\s*/x.freeze
@@ -23,7 +25,7 @@ module Praxis
23
25
  QUOTED_STRING = /^".*"$/.freeze
24
26
 
25
27
  # Token that indicates a media-type component that matches anything.
26
- WILDCARD = '*'.freeze
28
+ WILDCARD = '*'
27
29
 
28
30
  # Inner type representing semicolon-delimited parameters.
29
31
  Parameters = Attributor::Hash.of(key: String)
@@ -32,7 +34,7 @@ module Praxis
32
34
  attribute :type, Attributor::String, default: 'application', description: 'RFC2046 media type'
33
35
  attribute :subtype, Attributor::String, default: '*', description: 'RFC2046 media subtype', example: 'vnd.widget'
34
36
  attribute :suffix, Attributor::String, default: '', description: 'RFC6838 structured-syntax suffix', example: 'json'
35
- attribute :parameters, Parameters, default: {}, description: "Type-specific parameters", example: "{'type' => 'collection'}"
37
+ attribute :parameters, Parameters, default: {}, description: 'Type-specific parameters', example: "{'type' => 'collection'}"
36
38
  end
37
39
 
38
40
  # Parse a media type identifier from a String, or load it from a Hash or Model. Assume malformed
@@ -41,10 +43,11 @@ module Praxis
41
43
  # @param [String,Hash,Attributor::Model] value
42
44
  # @return [MediaTypeIdentifier]
43
45
  # @see Attributor::Model#load
44
- def self.load(value, context=Attributor::DEFAULT_ROOT_CONTEXT, recurse: false, **options)
46
+ def self.load(value, context = Attributor::DEFAULT_ROOT_CONTEXT, recurse: false, **options)
45
47
  case value
46
48
  when String
47
49
  return nil if value.blank?
50
+
48
51
  base, *parameters = value.split(PARAMETER_SEPARATOR)
49
52
  match = VALID_TYPE.match(base)
50
53
 
@@ -56,17 +59,19 @@ module Praxis
56
59
  h[k] = v
57
60
  end
58
61
 
59
- obj.type, obj.subtype, obj.suffix, obj.parameters =
60
- match[:type], match[:subtype], match[:suffix], parameters
62
+ obj.type = match[:type]
63
+ obj.subtype = match[:subtype]
64
+ obj.suffix = match[:suffix]
65
+ obj.parameters = parameters
61
66
  else
62
67
  obj.type = 'application'
63
68
  obj.subtype = base.split(WORD_SEPARATOR, 2).first
64
- obj.suffix = ''
69
+ obj.suffix = String.new
65
70
  obj.parameters = {}
66
71
  end
67
72
  obj
68
73
  when nil
69
- return nil
74
+ nil
70
75
  else
71
76
  super
72
77
  end
@@ -97,6 +102,7 @@ module Praxis
97
102
  return false unless type == other.type || type == WILDCARD
98
103
  return false unless subtype == other.subtype || subtype == WILDCARD
99
104
  return false unless suffix.empty? || suffix == other.suffix
105
+
100
106
  parameters.each_pair do |k, v|
101
107
  return false unless other.parameters[k] == v
102
108
  end
@@ -120,7 +126,7 @@ module Praxis
120
126
  # @return [String] canonicalized representation of the media type including all suffixes and parameters
121
127
  def to_s
122
128
  # Our handcrafted media types consist of a rich chocolatey center
123
- s = "#{type}/#{subtype}"
129
+ s = String.new("#{type}/#{subtype}")
124
130
 
125
131
  # coated in a hard candy shell
126
132
  s << '+' << suffix unless suffix.empty?
@@ -135,18 +141,17 @@ module Praxis
135
141
  s
136
142
  end
137
143
 
138
- alias_method :to_str, :to_s
139
-
144
+ alias to_str to_s
140
145
 
141
146
  # If parameters are empty, return self; otherwise return a new object consisting of this type
142
147
  # minus parameters.
143
148
  #
144
149
  # @return [MediaTypeIdentifier]
145
150
  def without_parameters
146
- if self.parameters.empty?
151
+ if parameters.empty?
147
152
  self
148
153
  else
149
- val = {type: self.type, subtype: self.subtype, suffix: self.suffix}
154
+ val = { type: type, subtype: subtype, suffix: suffix }
150
155
  MediaTypeIdentifier.load(val)
151
156
  end
152
157
  end
@@ -176,12 +181,12 @@ module Praxis
176
181
  #
177
182
  # @example Indicate UTF8 encoding
178
183
  # MediaTypeIdentifier.new('application/vnd.widget') + 'charset=UTF8' # => 'application/vnd.widget; charset="UTF8"'
179
- def +(extension)
180
- parameters = extension.split(PARAMETER_SEPARATOR)
184
+ def +(other)
185
+ parameters = other.split(PARAMETER_SEPARATOR)
181
186
  # remove useless initial '; '
182
187
  parameters.shift if parameters.first && parameters.first.empty?
183
188
 
184
- raise ArgumentError, "Must pass a type identifier suffix and/or parameters" if parameters.empty?
189
+ raise ArgumentError, 'Must pass a type identifier suffix and/or parameters' if parameters.empty?
185
190
 
186
191
  suffix = parameters.shift unless parameters.first.include?('=')
187
192
  # remove redundant '+'
@@ -196,10 +201,10 @@ module Praxis
196
201
  parameters = Parameters.load(parameters)
197
202
 
198
203
  obj = self.class.new
199
- obj.type = self.type
200
- obj.subtype = self.subtype
204
+ obj.type = type
205
+ obj.subtype = subtype
201
206
  target_suffix = suffix || self.suffix
202
- obj.suffix = redundant_suffix(target_suffix) ? '' : target_suffix
207
+ obj.suffix = redundant_suffix(target_suffix) ? String.new : target_suffix
203
208
  obj.parameters = self.parameters.merge(parameters)
204
209
 
205
210
  obj
@@ -208,9 +213,8 @@ module Praxis
208
213
  def redundant_suffix(suffix)
209
214
  # application/json does not need to be suffixed with +json (same for application/xml)
210
215
  # we're supporting text/json and text/xml for older formats as well
211
- if (self.type == 'application' || self.type == 'text') && self.subtype == suffix
212
- return true
213
- end
216
+ return true if (type == 'application' || type == 'text') && subtype == suffix
217
+
214
218
  false
215
219
  end
216
220
  end
@@ -1,48 +1,51 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Praxis
2
4
  class MiddlewareApp
3
-
4
5
  attr_reader :target
5
6
 
6
7
  # Initialize the application instance with the desired args, and return the wrapping class.
7
- def self.for( **args )
8
+ def self.for(**args)
8
9
  Class.new(self) do
9
10
  @args = args
10
11
  @setup_done = false
11
12
  def self.name
12
13
  'MiddlewareApp'
13
14
  end
14
- def self.args
15
- @args
15
+
16
+ class << self
17
+ attr_reader :args
16
18
  end
17
- def self.setup_done
18
- @setup_done
19
+
20
+ class << self
21
+ attr_reader :setup_done
19
22
  end
23
+
20
24
  def self.setup
21
25
  @setup_done = true
22
26
  Praxis::Application.instance.setup(**@args)
23
27
  end
24
28
  end
25
- end
29
+ end
26
30
 
27
- def initialize( inner )
31
+ def initialize(inner)
28
32
  @target = inner
29
33
  @setup_done = false
30
34
  end
31
35
 
32
36
  def call(env)
33
37
  self.class.setup unless self.class.setup_done
34
-
38
+
35
39
  result = Praxis::Application.instance.call(env)
36
40
 
37
- unless ( [404,405].include?(result[0].to_i) && result[1]['X-Cascade'] == 'pass' )
38
- # Respect X-Cascade header if it doesn't specify 'pass'
39
- result
40
- else
41
+ if [404, 405].include?(result[0].to_i) && result[1]['X-Cascade'] == 'pass'
41
42
  last_body = result[2]
42
43
  last_body.close if last_body.respond_to? :close
43
44
  target.call(env)
45
+ else
46
+ # Respect X-Cascade header if it doesn't specify 'pass'
47
+ result
44
48
  end
45
49
  end
46
-
47
50
  end
48
- end
51
+ end