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

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