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,30 +1,32 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Praxis
3
4
  module Extensions
4
5
  module FieldSelection
5
6
  class ActiveRecordQuerySelector
6
7
  attr_reader :selector, :query
8
+
7
9
  # Gets a dataset, a selector...and should return a dataset with the selector definition applied.
8
10
  def initialize(query:, selectors:, debug: false)
9
11
  @selector = selectors
10
12
  @query = query
11
- @logger = debug ? Logger.new(STDOUT) : nil
13
+ @logger = debug ? Logger.new($stdout) : nil
12
14
  end
13
15
 
14
16
  def generate
15
- # TODO: unfortunately, I think we can only control the select clauses for the top model
17
+ # TODO: unfortunately, I think we can only control the select clauses for the top model
16
18
  # (as I'm not sure ActiveRecord supports expressing it in the join...)
17
19
  @query = add_select(query: query, selector_node: selector)
18
20
  eager_hash = _eager(selector)
19
21
 
20
- @query = @query.includes(eager_hash)
22
+ @query = @query.includes(eager_hash)
21
23
  explain_query(query, eager_hash) if @logger
22
24
 
23
25
  @query
24
26
  end
25
27
 
26
28
  def add_select(query:, selector_node:)
27
- # We're gonna always require the PK of the model, as it is a special case for AR, and the app itself
29
+ # We're gonna always require the PK of the model, as it is a special case for AR, and the app itself
28
30
  # might assume it is always there and not be surprised by the fact that if it isn't, it won't blow up
29
31
  # in the same way as any other attribute not being loaded...i.e., ActiveModel::MissingAttributeError: missing attribute: xyz
30
32
  select_fields = selector_node.select + [selector_node.resource.model.primary_key.to_sym]
@@ -32,8 +34,8 @@ module Praxis
32
34
  end
33
35
 
34
36
  def _eager(selector_node)
35
- selector_node.tracks.each_with_object({}) do |(track_name, track_node), h|
36
- h[track_name] = _eager(track_node)
37
+ selector_node.tracks.transform_values do |track_node|
38
+ _eager(track_node)
37
39
  end
38
40
  end
39
41
 
@@ -41,9 +43,9 @@ module Praxis
41
43
  @logger.debug("Query plan for ...#{selector.resource.model} with selectors: #{JSON.generate(selector.dump)}")
42
44
  @logger.debug(" ActiveRecord query: #{selector.resource.model}.includes(#{eager_hash})")
43
45
  query.explain
44
- @logger.debug("Query plan end")
46
+ @logger.debug('Query plan end')
45
47
  end
46
48
  end
47
49
  end
48
50
  end
49
- end
51
+ end
@@ -1,123 +1,122 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module Praxis
3
4
  module Extensions
4
5
  module FieldSelection
6
+ class FieldSelector
7
+ include Attributor::Type
8
+ include Attributor::Dumpable
5
9
 
6
- class FieldSelector
7
- include Attributor::Type
8
- include Attributor::Dumpable
10
+ def self.json_schema_type
11
+ :string
12
+ end
9
13
 
10
- def self.json_schema_type
11
- :string
12
- end
13
-
14
- def self.native_type
15
- self
16
- end
14
+ def self.native_type
15
+ self
16
+ end
17
17
 
18
- def self.display_name
19
- 'FieldSelector'
18
+ def self.display_name
19
+ 'FieldSelector'
20
+ end
21
+
22
+ def self.family
23
+ 'string'
24
+ end
25
+
26
+ def self.for(media_type)
27
+ unless media_type < Praxis::MediaType
28
+ raise ArgumentError, "Invalid type: #{media_type.name} for FieldSelector. " \
29
+ 'Must be a subclass of MediaType'
20
30
  end
21
31
 
22
- def self.family
23
- 'string'
32
+ ::Class.new(self) do
33
+ @media_type = media_type
24
34
  end
35
+ end
25
36
 
26
- def self.for(media_type)
27
- unless media_type < Praxis::MediaType
28
- raise ArgumentError, "Invalid type: #{media_type.name} for FieldSelector. " +
29
- "Must be a subclass of MediaType"
30
- end
37
+ def self.load(value, _context = Attributor::DEFAULT_ROOT_CONTEXT, **_options)
38
+ return value if value.is_a?(native_type)
31
39
 
32
- ::Class.new(self) do
33
- @media_type = media_type
34
- end
40
+ if value.nil? || value.blank?
41
+ new(true)
42
+ else
43
+ parsed = Attributor::FieldSelector.load(value)
44
+ new(parsed)
35
45
  end
46
+ end
36
47
 
37
- def self.load(value,context=Attributor::DEFAULT_ROOT_CONTEXT, **options)
38
- return value if value.kind_of?(self.native_type)
48
+ def self.example(context = Attributor::DEFAULT_ROOT_CONTEXT, **options)
49
+ fields = if media_type
50
+ media_type.attributes.keys.sample(3).join(',')
51
+ else
52
+ Attributor::FieldSelector.example(context, **options)
53
+ end
54
+ self.load(fields)
55
+ end
39
56
 
40
- if value.nil? || value.blank?
41
- self.new(true)
42
- else
43
- parsed = Attributor::FieldSelector.load(value)
44
- self.new(parsed)
45
- end
46
- end
57
+ def self.validate(value, context = Attributor::DEFAULT_ROOT_CONTEXT, _attribute = nil)
58
+ return [] unless media_type
47
59
 
48
- def self.example(context=Attributor::DEFAULT_ROOT_CONTEXT, **options)
49
- fields = if media_type
50
- media_type.attributes.keys.sample(3).join(',')
51
- else
52
- Attributor::FieldSelector.example(context,**options)
53
- end
54
- self.load(fields)
55
- end
60
+ instance = self.load(value, context)
61
+ instance.validate(context)
62
+ end
56
63
 
57
- def self.validate(value, context=Attributor::DEFAULT_ROOT_CONTEXT, _attribute=nil)
58
- return [] unless media_type
59
- instance = self.load(value, context)
60
- instance.validate(context)
61
- end
64
+ def self.dump(value, **_opts)
65
+ self.load(value).dump
66
+ end
62
67
 
63
- def self.dump(value,**opts)
64
- self.load(value).dump
65
- end
68
+ class << self
69
+ attr_reader :media_type
70
+ end
66
71
 
67
- class << self
68
- attr_reader :media_type
69
- end
72
+ attr_reader :fields
70
73
 
71
- attr_reader :fields
74
+ def initialize(fields)
75
+ @fields = fields
76
+ end
72
77
 
73
- def initialize(fields)
74
- @fields = fields
75
- end
78
+ def dump(*_args)
79
+ return '' if fields == true
76
80
 
77
- def dump(*args)
78
- return '' if self.fields == true
79
- _dump(self.fields)
80
- end
81
+ _dump(fields)
82
+ end
81
83
 
82
- def _dump(fields)
83
- fields.each_with_object([]) do |(field, spec), array|
84
- if spec == true
85
- array << field
86
- else
87
- array << "#{field}{#{_dump(spec)}}"
88
- end
89
- end.join(',')
90
- end
84
+ def _dump(fields)
85
+ fields.each_with_object([]) do |(field, spec), array|
86
+ array << if spec == true
87
+ field
88
+ else
89
+ "#{field}{#{_dump(spec)}}"
90
+ end
91
+ end.join(',')
92
+ end
91
93
 
92
- def validate(context=Attributor::DEFAULT_ROOT_CONTEXT)
93
- errors = []
94
- return errors if self.fields == true
95
- _validate(self.class.media_type, fields)
96
- end
94
+ def validate(_context = Attributor::DEFAULT_ROOT_CONTEXT)
95
+ errors = []
96
+ return errors if fields == true
97
+
98
+ _validate(self.class.media_type, fields)
99
+ end
97
100
 
98
- def _validate(type, fields, context=Attributor::DEFAULT_ROOT_CONTEXT)
99
- errors = []
100
- fields.each do |name, field_spec|
101
- unless type.attributes.key?(name)
102
- errors << "Attribute with name #{name} not found in #{Attributor.type_name(type)}"
103
- next
104
- end
105
-
106
- if field_spec.kind_of?(Hash)
107
- sub_context = context + [name]
108
- sub_attribute = type.attributes[name]
109
- sub_type = sub_attribute.type
110
- if sub_attribute.type.respond_to?(:member_attribute)
111
- sub_type = sub_type.member_type
112
- end
113
- errors.push(*_validate(sub_type,field_spec, sub_context))
114
- end
101
+ def _validate(type, fields, context = Attributor::DEFAULT_ROOT_CONTEXT)
102
+ errors = []
103
+ fields.each do |name, field_spec|
104
+ unless type.attributes.key?(name)
105
+ errors << "Attribute with name #{name} not found in #{Attributor.type_name(type)}"
106
+ next
115
107
  end
116
- errors
117
- end
118
108
 
119
- end
109
+ next unless field_spec.is_a?(Hash)
120
110
 
111
+ sub_context = context + [name]
112
+ sub_attribute = type.attributes[name]
113
+ sub_type = sub_attribute.type
114
+ sub_type = sub_type.member_type if sub_attribute.type.respond_to?(:member_attribute)
115
+ errors.push(*_validate(sub_type, field_spec, sub_context))
116
+ end
117
+ errors
118
+ end
119
+ end
121
120
  end
122
121
  end
123
122
  end
@@ -7,18 +7,19 @@ module Praxis
7
7
  module FieldSelection
8
8
  class SequelQuerySelector
9
9
  attr_reader :selector, :query
10
+
10
11
  # Gets a dataset, a selector...and should return a dataset with the selector definition applied.
11
12
  def initialize(query:, selectors:, debug: false)
12
13
  @selector = selectors
13
14
  @query = query
14
- @logger = debug ? Logger.new(STDOUT) : nil
15
+ @logger = debug ? Logger.new($stdout) : nil
15
16
  end
16
17
 
17
18
  def generate
18
19
  @query = add_select(query: query, selector_node: @selector)
19
-
20
+
20
21
  @query = @selector.tracks.inject(@query) do |ds, (track_name, track_node)|
21
- ds.eager(track_name => _eager(track_node) )
22
+ ds.eager(track_name => _eager(track_node))
22
23
  end
23
24
 
24
25
  explain_query(query) if @logger
@@ -29,30 +30,29 @@ module Praxis
29
30
  lambda do |dset|
30
31
  dset = add_select(query: dset, selector_node: selector_node)
31
32
 
32
- dset = selector_node.tracks.inject(dset) do |ds, (track_name, track_node)|
33
- ds.eager(track_name => _eager(track_node) )
33
+ selector_node.tracks.inject(dset) do |ds, (track_name, track_node)|
34
+ ds.eager(track_name => _eager(track_node))
34
35
  end
35
-
36
36
  end
37
37
  end
38
38
 
39
39
  def add_select(query:, selector_node:)
40
- # We're gonna always require the PK of the model, as it is a special case for Sequel, and the app itself
40
+ # We're gonna always require the PK of the model, as it is a special case for Sequel, and the app itself
41
41
  # might assume it is always there and not be surprised by the fact that if it isn't, it won't blow up
42
42
  # in the same way as any other attribute not being loaded...i.e., NoMethodError: undefined method `foobar' for #<...>
43
43
  select_fields = selector_node.select + [selector_node.resource.model.primary_key.to_sym]
44
-
44
+
45
45
  table_name = selector_node.resource.model.table_name
46
46
  qualified = select_fields.map { |f| Sequel.qualify(table_name, f) }
47
47
  query.select(*qualified)
48
48
  end
49
49
 
50
- def explain_query(ds)
50
+ def explain_query(dataset)
51
51
  @logger.debug("Query plan for ...#{selector.resource.model} with selectors: #{JSON.generate(selector.dump)}")
52
- ds.all
53
- @logger.debug("Query plan end")
52
+ dataset.all
53
+ @logger.debug('Query plan end')
54
54
  end
55
55
  end
56
56
  end
57
57
  end
58
- end
58
+ end
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'attributor/extras/field_selector'
2
- require 'praxis/extensions/field_selection/field_selector'
4
+ require 'praxis/extensions/field_selection/field_selector'
@@ -1,30 +1,32 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'pagination_handler'
2
4
 
3
5
  module Praxis
4
6
  module Extensions
5
7
  module Pagination
6
8
  class ActiveRecordPaginationHandler < PaginationHandler
7
-
8
9
  def self.where_lt(query, attr, value)
9
10
  # TODO: common for AR/Sequel? Seems we could use Arel and more-specific Sequel things
10
11
  query.where(query.table[attr].lt(value))
11
12
  end
12
-
13
+
13
14
  def self.where_gt(query, attr, value)
14
15
  query.where(query.table[attr].gt(value))
15
16
  end
16
17
 
17
18
  def self.order(query, order)
18
19
  return query unless order
20
+
19
21
  query = query.reorder('')
20
-
22
+
21
23
  order.each do |spec_hash|
22
24
  direction, name = spec_hash.first
23
25
  query = query.order(name => direction)
24
26
  end
25
27
  query
26
28
  end
27
-
29
+
28
30
  def self.count(query)
29
31
  query.count(:all)
30
32
  end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Praxis
2
4
  module Extensions
3
5
  module Pagination
4
- class HeaderGenerator
6
+ class HeaderGenerator
5
7
  def self.build_cursor_headers(paginator:, last_value:, total_count: nil)
6
- [:next, :prev, :first, :last].each_with_object({}) do |rel_name, info|
8
+ %i[next prev first last].each_with_object({}) do |rel_name, info|
7
9
  case rel_name
8
10
  when :next
9
11
  # If we don't know the total, we'll try to go to the next page
@@ -26,26 +28,29 @@ module Praxis
26
28
  # This is only for plain paging
27
29
  def self.build_paging_headers(paginator:, total_count: nil)
28
30
  last_page = total_count.nil? ? nil : (total_count / (paginator.items * 1.0)).ceil
29
- [:next, :prev, :first, :last].each_with_object({}) do |rel_name, info|
31
+ %i[next prev first last].each_with_object({}) do |rel_name, info|
30
32
  num = case rel_name
31
33
  when :first
32
34
  1
33
35
  when :prev
34
36
  next if paginator.page < 2
37
+
35
38
  paginator.page - 1
36
39
  when :next
37
40
  # don't include this link if we know the total and we see there are no more pages
38
41
  next if last_page.present? && (paginator.page >= last_page)
42
+
39
43
  # if we don't know the total, we'll specify to the next page even if it ends up being blank
40
44
  paginator.page + 1
41
45
  when :last
42
46
  next if last_page.blank?
47
+
43
48
  last_page
44
49
  end
45
50
  info[rel_name] = {
46
- page: num,
47
- items: paginator.items,
48
- total_count: total_count.present?
51
+ page: num,
52
+ items: paginator.items,
53
+ total_count: total_count.present?
49
54
  }
50
55
  end
51
56
  end
@@ -53,15 +58,15 @@ module Praxis
53
58
  def self.generate_headers(links:, current_url:, current_query_params:, total_count:)
54
59
  mapped = links.map do |(rel, info)|
55
60
  # Make sure to encode it our way (with comma-separated args, as it is our own syntax, and not a query string one)
56
- pagination_param = info.map { |(k, v)| "#{k}=#{v}" }.join(",")
57
- new_url = current_url + "?" + current_query_params.dup.merge(pagination: pagination_param).to_query
61
+ pagination_param = info.map { |(k, v)| "#{k}=#{v}" }.join(',')
62
+ new_url = "#{current_url}?#{current_query_params.dup.merge(pagination: pagination_param).to_query}"
58
63
 
59
- LinkHeader::Link.new(new_url, [["rel", rel.to_s]])
64
+ LinkHeader::Link.new(new_url, [['rel', rel.to_s]])
60
65
  end
61
66
  link_header = LinkHeader.new(mapped)
62
67
 
63
- headers = { "Link" => link_header.to_s }
64
- headers["Total-Count"] = total_count if total_count
68
+ headers = { 'Link' => link_header.to_s }
69
+ headers['Total-Count'] = total_count if total_count
65
70
  headers
66
71
  end
67
72
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
2
4
 
3
5
  module Praxis
@@ -65,13 +67,12 @@ module Praxis
65
67
  # MediaType, so that the field name checking and value coercion can be performed
66
68
  class << self
67
69
  attr_reader :media_type
68
- attr_accessor :fields_allowed
69
- attr_accessor :enforce_all # True when we need to enforce the allowed fields at all ordering positions
70
+ attr_accessor :fields_allowed, :enforce_all # True when we need to enforce the allowed fields at all ordering positions
70
71
 
71
72
  def for(media_type, **_opts)
72
73
  unless media_type < Praxis::MediaType
73
74
  raise ArgumentError, "Invalid type: #{media_type.name} for Ordering. " \
74
- "Must be a subclass of MediaType"
75
+ 'Must be a subclass of MediaType'
75
76
  end
76
77
 
77
78
  ::Class.new(self) do
@@ -121,22 +122,22 @@ module Praxis
121
122
 
122
123
  def self.example(_context = Attributor::DEFAULT_ROOT_CONTEXT, **_options)
123
124
  fields = if media_type
124
- chosen_set = if enforce_all
125
+ chosen_set = if enforce_all
125
126
  fields_allowed.sample(2)
126
127
  else
127
128
  starting_set = fields_allowed.sample(1)
128
129
  simple_attrs = media_type.attributes.select do |_k, attr|
129
130
  attr.type == Attributor::String || attr.type < Attributor::Numeric || attr.type < Attributor::Temporal
130
131
  end.keys
131
- starting_set + simple_attrs.select { |attr| attr != starting_set.first }.sample(1)
132
+ starting_set + simple_attrs.reject { |attr| attr == starting_set.first }.sample(1)
132
133
  end
133
- chosen_set.each_with_object([]) do |chosen, arr|
134
- sign = rand(10) < 5 ? "-" : ""
135
- arr << "#{sign}#{chosen}"
136
- end.join(',')
137
- else
138
- "name,last_name,-birth_date"
139
- end
134
+ chosen_set.each_with_object([]) do |chosen, arr|
135
+ sign = rand(10) < 5 ? '-' : ''
136
+ arr << "#{sign}#{chosen}"
137
+ end.join(',')
138
+ else
139
+ 'name,last_name,-birth_date'
140
+ end
140
141
  load(fields)
141
142
  end
142
143
 
@@ -151,13 +152,14 @@ module Praxis
151
152
  parsed_order = {}
152
153
  unless order.nil?
153
154
  parsed_order = order.split(',').each_with_object([]) do |order_string, arr|
154
- item = if order_string[0] == '-'
155
- { desc: order_string[1..-1].to_s }
156
- elsif order_string[0] == '+'
157
- { asc: order_string[1..-1].to_s }
158
- else
159
- { asc: order_string.to_s }
160
- end
155
+ item = case order_string[0]
156
+ when '-'
157
+ { desc: order_string[1..-1].to_s }
158
+ when '+'
159
+ { asc: order_string[1..-1].to_s }
160
+ else
161
+ { asc: order_string.to_s }
162
+ end
161
163
  arr.push item
162
164
  end
163
165
  end
@@ -195,7 +197,8 @@ module Praxis
195
197
  enforceable_items.each do |spec|
196
198
  _dir, field = spec.first
197
199
  field = field.to_sym
198
- next unless !self.class.fields_allowed.include?(field)
200
+ next if self.class.fields_allowed.include?(field)
201
+
199
202
  errors << if self.class.media_type.attributes.key?(field)
200
203
  "Ordering by field \'#{field}\' is disallowed. Ordering is only allowed using the following fields: " +
201
204
  self.class.fields_allowed.map { |f| "\'#{f}\'" }.join(', ').to_s
@@ -213,17 +216,15 @@ module Praxis
213
216
  items.each_with_object([]) do |spec, arr|
214
217
  dir, field = spec.first
215
218
  arr << if dir == :desc
216
- "-#{field}"
217
- else
218
- field
219
- end
219
+ "-#{field}"
220
+ else
221
+ field
222
+ end
220
223
  end.join(',')
221
224
  end
222
225
 
223
- def each
224
- items.each do |item|
225
- yield item
226
- end
226
+ def each(&block)
227
+ items.each(&block)
227
228
  end
228
229
  end
229
230
  end