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,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Praxis
2
4
  module Extensions
3
5
  module Pagination
4
6
  class PaginationHandler
5
- class PaginationException < Exception; end
7
+ class PaginationException < RuntimeError; end
6
8
 
7
9
  def self.paginate(query, pagination)
8
10
  return query unless pagination.paginator
@@ -10,57 +12,57 @@ module Praxis
10
12
  paginator = pagination.paginator
11
13
 
12
14
  q = if paginator.page
13
- offset(query, (paginator.page - 1) * paginator.items)
14
- else
15
- # If there is an order clause that complies with the "by" field sorting, we can use it directly
16
- # i.e., We can be smart about allowing the main sort field matching the pagination one (in case you want to sub-order in a custom way)
17
- oclause = if pagination.order.nil? || pagination.order.empty? # No ordering specified => use ascending based on the "by" field
18
- direction = :asc
19
- order(query, [{ asc: paginator.by }])
20
- else
21
- first_ordering = pagination.order.items.first
22
- direction = first_ordering.keys.first
23
- unless first_ordering[direction].to_sym == pagination.paginator.by.to_sym
24
- string_clause = pagination.order.items.map { |h|
25
- dir, name = h.first
26
- "#{name} #{dir}"
27
- }.join(',')
28
- raise PaginationException,
29
- "Ordering clause [#{string_clause}] is incompatible with pagination by field '#{pagination.paginator.by}'. " \
30
- "When paginating by a field value, one cannot specify the 'order' clause " \
31
- "unless the clause's primary field matches the pagination field."
32
- end
33
- order(query, pagination.order)
34
- end
35
-
36
- if paginator.from
37
- if direction == :desc
38
- where_lt(oclause, paginator.by, paginator.from)
15
+ offset(query, (paginator.page - 1) * paginator.items)
39
16
  else
40
- where_gt(oclause, paginator.by, paginator.from)
41
- end
42
- else
43
- oclause
44
- end
17
+ # If there is an order clause that complies with the "by" field sorting, we can use it directly
18
+ # i.e., We can be smart about allowing the main sort field matching the pagination one (in case you want to sub-order in a custom way)
19
+ oclause = if pagination.order.nil? || pagination.order.empty? # No ordering specified => use ascending based on the "by" field
20
+ direction = :asc
21
+ order(query, [{ asc: paginator.by }])
22
+ else
23
+ first_ordering = pagination.order.items.first
24
+ direction = first_ordering.keys.first
25
+ unless first_ordering[direction].to_sym == pagination.paginator.by.to_sym
26
+ string_clause = pagination.order.items.map do |h|
27
+ dir, name = h.first
28
+ "#{name} #{dir}"
29
+ end.join(',')
30
+ raise PaginationException,
31
+ "Ordering clause [#{string_clause}] is incompatible with pagination by field '#{pagination.paginator.by}'. " \
32
+ "When paginating by a field value, one cannot specify the 'order' clause " \
33
+ "unless the clause's primary field matches the pagination field."
34
+ end
35
+ order(query, pagination.order)
36
+ end
45
37
 
46
- end
38
+ if paginator.from
39
+ if direction == :desc
40
+ where_lt(oclause, paginator.by, paginator.from)
41
+ else
42
+ where_gt(oclause, paginator.by, paginator.from)
43
+ end
44
+ else
45
+ oclause
46
+ end
47
+
48
+ end
47
49
  limit(q, paginator.items)
48
50
  end
49
51
 
50
- def self.where_lt(query, attr, value)
51
- raise "implement in derived class"
52
+ def self.where_lt(_query, _attr, _value)
53
+ raise 'implement in derived class'
52
54
  end
53
-
54
- def self.where_gt(query, attr, value)
55
- raise "implement in derived class"
55
+
56
+ def self.where_gt(_query, _attr, _value)
57
+ raise 'implement in derived class'
56
58
  end
57
59
 
58
- def self.offset(query, offset)
59
- raise "implement in derived class"
60
+ def self.offset(_query, _offset)
61
+ raise 'implement in derived class'
60
62
  end
61
63
 
62
- def self.limit(query, limit)
63
- raise "implement in derived class"
64
+ def self.limit(_query, _limit)
65
+ raise 'implement in derived class'
64
66
  end
65
67
  end
66
68
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Praxis
2
4
  module Extensions
3
5
  module Pagination
@@ -76,40 +78,32 @@ module Praxis
76
78
  default_mode, default_value = target.defaults[:default_mode].first
77
79
  case pagination_type
78
80
  when :paging
79
- if default_mode == :page
80
- raise "Cannot disallow page-based pagination if you define a default pagination of: page: #{default_value}"
81
- end
81
+ raise "Cannot disallow page-based pagination if you define a default pagination of: page: #{default_value}" if default_mode == :page
82
+
82
83
  target.defaults[:disallow_paging] = true
83
84
  when :cursor
84
- if default_mode == :by
85
- raise "Cannot disallow cursor-based pagination if you define a default pagination of: by: #{default_value}"
86
- end
85
+ raise "Cannot disallow cursor-based pagination if you define a default pagination of: by: #{default_value}" if default_mode == :by
86
+
87
87
  target.defaults[:disallow_cursor] = true
88
88
  end
89
89
  end
90
90
 
91
91
  def default(spec)
92
- unless spec.is_a?(Hash) && spec.keys.size == 1 && [:by, :page].include?(spec.keys.first)
92
+ unless spec.is_a?(Hash) && spec.keys.size == 1 && %i[by page].include?(spec.keys.first)
93
93
  raise "'default' syntax for pagination takes exactly one key specification. Either by: <:fieldname> or page: <num>" \
94
94
  "#{spec} is invalid"
95
95
  end
96
96
  mode, value = spec.first
97
97
  def_mode = case mode
98
98
  when :by
99
- if target.fields_allowed && !target.fields_allowed&.include?(value)
100
- raise "Error setting default pagination. Field #{value} is not amongst the allowed fields."
101
- end
102
- if target.defaults[:disallow_cursor]
103
- raise "Cannot define a default pagination that is cursor based, if cursor-based pagination is disallowed."
104
- end
99
+ raise "Error setting default pagination. Field #{value} is not amongst the allowed fields." if target.fields_allowed && !target.fields_allowed&.include?(value)
100
+ raise 'Cannot define a default pagination that is cursor based, if cursor-based pagination is disallowed.' if target.defaults[:disallow_cursor]
101
+
105
102
  { by: value }
106
103
  when :page
107
- unless value.is_a?(Integer)
108
- raise "Error setting default pagination. Initial page should be a integer (but got #{value})"
109
- end
110
- if target.defaults[:disallow_paging]
111
- raise "Cannot define a default pagination that is page-based, if page-based pagination is disallowed."
112
- end
104
+ raise "Error setting default pagination. Initial page should be a integer (but got #{value})" unless value.is_a?(Integer)
105
+ raise 'Cannot define a default pagination that is page-based, if page-based pagination is disallowed.' if target.defaults[:disallow_paging]
106
+
113
107
  { page: value }
114
108
  end
115
109
  target.defaults[:default_mode] = def_mode
@@ -141,9 +135,8 @@ module Praxis
141
135
 
142
136
  def self.paging_default_mode(newval = nil)
143
137
  if newval
144
- unless newval.respond_to?(:keys) && newval.keys.size == 1 && [:by, :page].include?(newval.keys.first)
145
- raise "Error setting paging_default_mode, value must be a hash with :by or :page keys"
146
- end
138
+ raise 'Error setting paging_default_mode, value must be a hash with :by or :page keys' unless newval.respond_to?(:keys) && newval.keys.size == 1 && %i[by page].include?(newval.keys.first)
139
+
147
140
  @paging_default_mode = newval
148
141
  end
149
142
  @paging_default_mode
@@ -152,14 +145,13 @@ module Praxis
152
145
  # Abstract class, which needs to be used by subclassing it through the .for method, to link it to a particular
153
146
  # MediaType, so that the field name checking and value coercion can be performed
154
147
  class << self
155
- attr_reader :media_type
156
- attr_reader :defaults
148
+ attr_reader :media_type, :defaults
157
149
  attr_accessor :fields_allowed
158
150
 
159
151
  def for(media_type, **_opts)
160
152
  unless media_type < Praxis::MediaType
161
153
  raise ArgumentError, "Invalid type: #{media_type.name} for Paginator. " \
162
- "Must be a subclass of MediaType"
154
+ 'Must be a subclass of MediaType'
163
155
  end
164
156
 
165
157
  ::Class.new(self) do
@@ -206,11 +198,7 @@ module Praxis
206
198
  self
207
199
  end
208
200
 
209
- attr_reader :by
210
- attr_reader :from
211
- attr_reader :items
212
- attr_reader :page
213
- attr_reader :total_count
201
+ attr_reader :by, :from, :items, :page, :total_count
214
202
 
215
203
  def self.example(_context = Attributor::DEFAULT_ROOT_CONTEXT, **_options)
216
204
  fields = if media_type
@@ -224,10 +212,10 @@ module Praxis
224
212
  from = media_type.attributes[by].example(parent: mt_example).to_s
225
213
  # Make sure to encode the value of the from, as it can contain commas and such
226
214
  # Only add the from parameter if it's not empty (an empty from is not allowed and it's gonna blow up in load)
227
- optional_from_component = (from && !from.empty?) ? ",from=#{CGI.escape(from)}" : ''
215
+ optional_from_component = from && !from.empty? ? ",from=#{CGI.escape(from)}" : ''
228
216
  "by=#{by}#{optional_from_component},items=#{defaults[:page_size]}"
229
217
  else
230
- "by=id,from=20,items=100"
218
+ 'by=id,from=20,items=100'
231
219
  end
232
220
  load(fields)
233
221
  end
@@ -237,9 +225,10 @@ module Praxis
237
225
  instance.validate(context)
238
226
  end
239
227
 
240
- CLAUSE_REGEX = /(?<type>[^=]+)=(?<value>.+)$/
228
+ CLAUSE_REGEX = /(?<type>[^=]+)=(?<value>.+)$/.freeze
241
229
  def self.load(paginator, _context = Attributor::DEFAULT_ROOT_CONTEXT, **_options)
242
230
  return paginator if paginator.is_a?(native_type) || paginator.nil?
231
+
243
232
  parsed = {}
244
233
  unless paginator.nil?
245
234
  parsed = paginator.split(',').each_with_object({}) do |paginator_string, hash|
@@ -305,7 +294,7 @@ module Praxis
305
294
  if media_type&.attributes
306
295
  attrs = media_type&.attributes || {}
307
296
  attribute = attrs[name.to_sym]
308
- attribute.type.load(value) if attribute
297
+ attribute&.type&.load(value)
309
298
  else
310
299
  value
311
300
  end
@@ -320,28 +309,20 @@ module Praxis
320
309
  @total_count = parsed[:total_count]
321
310
  end
322
311
 
323
- def validate(_context = Attributor::DEFAULT_ROOT_CONTEXT) # rubocop:disable Metrics/PerceivedComplexity
312
+ def validate(_context = Attributor::DEFAULT_ROOT_CONTEXT)
324
313
  errors = []
325
314
 
326
315
  if page
327
- if self.class.defaults[:disallow_paging]
328
- errors << "Page-based pagination is disallowed (i.e., using 'page=' parameter)"
329
- end
316
+ errors << "Page-based pagination is disallowed (i.e., using 'page=' parameter)" if self.class.defaults[:disallow_paging]
330
317
  elsif self.class.defaults[:disallow_cursor]
331
318
  errors << "Cursor-based pagination is disallowed (i.e., using 'by=' or 'from=' parameter)"
332
319
  end
333
320
 
334
- if page && page <= 0
335
- errors << "Page parameter cannot be zero or negative! (got: #{parsed.page})"
336
- end
321
+ errors << "Page parameter cannot be zero or negative! (got: #{parsed.page})" if page && page <= 0
337
322
 
338
- if items && (items <= 0 || ( self.class.defaults[:max_items] && items > self.class.defaults[:max_items]) )
339
- errors << "Value of 'items' is invalid (got: #{items}). It must be positive, and smaller than the maximum amount of items per request (set to #{self.class.defaults[:max_items]})"
340
- end
323
+ errors << "Value of 'items' is invalid (got: #{items}). It must be positive, and smaller than the maximum amount of items per request (set to #{self.class.defaults[:max_items]})" if items && (items <= 0 || (self.class.defaults[:max_items] && items > self.class.defaults[:max_items]))
341
324
 
342
- if page && (by || from)
343
- errors << "Cannot specify the field to use and its start value to paginate from when using a fix pager (i.e., `by` and/or `from` params are not compabible with `page`)"
344
- end
325
+ errors << 'Cannot specify the field to use and its start value to paginate from when using a fix pager (i.e., `by` and/or `from` params are not compabible with `page`)' if page && (by || from)
345
326
 
346
327
  if by && self.class.fields_allowed && !self.class.fields_allowed.include?(by.to_sym)
347
328
  errors << if self.class.media_type.attributes.key?(by.to_sym)
@@ -364,7 +345,7 @@ module Praxis
364
345
  s
365
346
  end
366
347
  str += ",items=#{items}" if @items
367
- str += ",total_count=true" if @total_count
348
+ str += ',total_count=true' if @total_count
368
349
  str
369
350
  end
370
351
  end
@@ -1,20 +1,22 @@
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 SequelPaginationHandler < PaginationHandler
7
-
8
9
  def self.where_lt(query, attr, value)
9
10
  query.where("#{attr} < ?", value)
10
11
  end
11
-
12
+
12
13
  def self.where_gt(query, attr, value)
13
14
  query.where("#{attr} > ?", value)
14
15
  end
15
16
 
16
17
  def self.order(query, order)
17
18
  return query unless order
19
+
18
20
  order_clause = order.map do |spec_hash|
19
21
  direction, name = spec_hash.first
20
22
  case direction.to_sym
@@ -24,21 +26,20 @@ module Praxis
24
26
  Sequel.asc(name.to_sym)
25
27
  end
26
28
  end
27
- query = query.order(*order_clause)
28
- query
29
+ query.order(*order_clause)
29
30
  end
30
-
31
+
31
32
  def self.count(query)
32
33
  query.count
33
34
  end
34
-
35
+
35
36
  def self.offset(query, offset)
36
37
  query.offset(offset)
37
38
  end
38
39
 
39
40
  def self.limit(query, limit)
40
41
  query.limit(limit)
41
- end
42
+ end
42
43
  end
43
44
  end
44
45
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  begin
2
4
  require 'link_header'
3
5
  rescue LoadError
@@ -43,17 +45,13 @@ module Praxis
43
45
 
44
46
  included do
45
47
  after :action do |controller, _callee|
46
- if controller.response.status < 300
47
- # If this action has the pagination parameter defined,
48
- # calculate and set the pagination headers (Link header and possibly Total-Count)
49
- if controller._pagination.paginator
50
- headers = controller.build_pagination_headers(
51
- pagination: controller._pagination,
52
- current_url: controller.request.path,
53
- current_query_params: controller.request.query
54
- )
55
- controller.response.headers.merge! headers
56
- end
48
+ if controller.response.status < 300 && controller._pagination.paginator
49
+ headers = controller.build_pagination_headers(
50
+ pagination: controller._pagination,
51
+ current_url: controller.request.path,
52
+ current_query_params: controller.request.query
53
+ )
54
+ controller.response.headers.merge! headers
57
55
  end
58
56
  end
59
57
  end
@@ -75,9 +73,7 @@ module Praxis
75
73
  links = if pagination.paginator.by
76
74
  # We're assuming that the last element has a "symbol/string" field with the same name of the "by" pagination.
77
75
  last_element = response.body.last
78
- if last_element
79
- last_value = last_element[pagination.paginator.by.to_sym] || last_element[pagination.paginator.by]
80
- end
76
+ last_value = last_element[pagination.paginator.by.to_sym] || last_element[pagination.paginator.by] if last_element
81
77
  HeaderGenerator.build_cursor_headers(
82
78
  paginator: pagination.paginator,
83
79
  last_value: last_value,
@@ -97,7 +93,6 @@ module Praxis
97
93
  total_count: pagination.total_count
98
94
  )
99
95
  end
100
-
101
96
  end
102
97
  end
103
98
  end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Make Praxis' request derive from ActionDispatch
2
4
  if defined? Praxis::Request
3
5
  puts "IT seems that we're trying to redefine Praxis' request parent too late."
4
- puts "-> try to include the Rails compat pieces earlier in the bootstrap process (before Praxis::Request is requried)"
6
+ puts '-> try to include the Rails compat pieces earlier in the bootstrap process (before Praxis::Request is requried)'
5
7
  exit(-1)
6
8
  end
7
9
 
@@ -14,6 +16,3 @@ begin
14
16
  end
15
17
  require 'praxis/request'
16
18
  end
17
-
18
-
19
-
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'praxis/extensions/rails_compat/request_methods'
2
4
  require 'praxis/plugins/rails_plugin'
@@ -1,32 +1,33 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Praxis
2
4
  module Extensions
3
-
4
5
  module Rendering
5
6
  extend ActiveSupport::Concern
6
7
  include FieldExpansion
7
8
 
8
9
  def render(object, include_nil: false)
9
- loaded = self.media_type.load(object)
10
+ loaded = media_type.load(object)
10
11
  renderer = Praxis::Renderer.new(include_nil: include_nil)
11
- renderer.render(loaded, self.expanded_fields)
12
+ renderer.render(loaded, expanded_fields)
12
13
  rescue Attributor::DumpError
13
- if self.media_type.domain_model == Object
14
- warn "Detected the rendering of an object of type #{self.media_type} without having a domain object model set.\n" +
15
- "Did you forget to define it?"
14
+ if media_type.domain_model == Object
15
+ warn "Detected the rendering of an object of type #{media_type} without having a domain object model set.\n" \
16
+ 'Did you forget to define it?'
16
17
  end
17
18
  raise
18
19
  end
19
20
 
20
- def display(object, include_nil: false, encoder: self.default_encoder )
21
- identifier = Praxis::MediaTypeIdentifier.load(self.media_type.identifier)
21
+ def display(object, include_nil: false, encoder: default_encoder)
22
+ identifier = Praxis::MediaTypeIdentifier.load(media_type.identifier)
22
23
  identifier += encoder unless encoder.blank?
23
24
  response.headers['Content-Type'] = identifier.to_s
24
25
  response.body = render(object, include_nil: include_nil)
25
26
  response
26
27
  rescue Praxis::Renderer::CircularRenderingError => e
27
- Praxis::Application.instance.validation_handler.handle!(
28
- summary: "Circular Rendering Error when rendering response. " +
29
- "Please especify a view to narrow the dependent fields, or narrow your field set.",
28
+ Praxis::Application.instance.validation_handler.handle!(
29
+ summary: 'Circular Rendering Error when rendering response. ' \
30
+ 'Please especify a view to narrow the dependent fields, or narrow your field set.',
30
31
  exception: e,
31
32
  request: request,
32
33
  stage: :action,
@@ -37,7 +38,6 @@ module Praxis
37
38
  def default_encoder
38
39
  ''
39
40
  end
40
-
41
41
  end
42
42
  end
43
43
  end
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Praxis
3
4
  class FieldExpander
4
5
  def self.expand(object, fields = true)
5
6
  new.expand(object, fields)
6
7
  end
7
8
 
8
- attr_reader :stack
9
- attr_reader :history
9
+ attr_reader :stack, :history
10
10
 
11
11
  def initialize
12
12
  @stack = Hash.new do |hash, key|
@@ -20,6 +20,7 @@ module Praxis
20
20
  def expand(object, fields = true)
21
21
  if stack[object].include? fields
22
22
  return history[object][fields] if history[object].include? fields
23
+
23
24
  # We should probably never get here, since we should have a record
24
25
  # of the history of an expansion if we're trying to redo it,
25
26
  # but we should also be conservative and raise here just in case.
@@ -61,11 +62,9 @@ module Praxis
61
62
 
62
63
  def expand_type(object, fields = true)
63
64
  unless object.respond_to?(:attributes)
64
- if object.respond_to?(:member_attribute)
65
- return expand_type(object.member_attribute.type, fields)
66
- else
67
- return true
68
- end
65
+ return expand_type(object.member_attribute.type, fields) if object.respond_to?(:member_attribute)
66
+
67
+ return true
69
68
  end
70
69
 
71
70
  # just include the full thing if it has no attributes
@@ -80,11 +79,11 @@ module Praxis
80
79
  result = expand_fields(object.attributes, fields) do |dumpable, sub_fields|
81
80
  expand(dumpable.type, sub_fields)
82
81
  end
83
- unless fields == true
82
+ unless fields == true
84
83
  non_matching = fields.keys - object.attributes.keys
85
84
  raise "FieldExpansion error: attribute(s) #{non_matching} do not exist in #{object}" unless non_matching.empty?
86
85
  end
87
86
  history[object][fields].merge!(result)
88
87
  end
89
88
  end
90
- end
89
+ end
@@ -1,26 +1,23 @@
1
- module Praxis
1
+ # frozen_string_literal: true
2
2
 
3
+ module Praxis
3
4
  class FileGroup
4
-
5
5
  attr_reader :groups, :base
6
6
 
7
7
  def initialize(base, &block)
8
8
  if base.nil?
9
- raise ArgumentError, "base must not be nil." \
10
- "Are you missing a call Praxis::Application.instance.setup?"
9
+ raise ArgumentError, 'base must not be nil.' \
10
+ 'Are you missing a call Praxis::Application.instance.setup?'
11
11
  end
12
12
 
13
-
14
- @groups = Hash.new
13
+ @groups = {}
15
14
  @base = Pathname.new(base)
16
15
 
17
- if block_given?
18
- self.instance_eval(&block)
19
- end
16
+ instance_eval(&block) if block_given?
20
17
  end
21
18
 
22
19
  def layout(&block)
23
- self.instance_eval(&block)
20
+ instance_eval(&block)
24
21
  end
25
22
 
26
23
  def map(name, pattern, &block)
@@ -30,7 +27,7 @@ module Praxis
30
27
  @groups[name] = FileGroup.new(base + pattern, &block)
31
28
  else
32
29
  @groups[name] ||= []
33
- files = Pathname.glob(base+pattern).select { |file| file.file? }
30
+ files = Pathname.glob(base + pattern).select(&:file?)
34
31
  files.sort_by! { |file| [file.to_s.split('/').size, file.to_s] }
35
32
  files.each { |file| @groups[name] << file }
36
33
  end
@@ -39,6 +36,5 @@ module Praxis
39
36
  def [](*names)
40
37
  names.inject(@groups) { |group, name| group[name] || [] }
41
38
  end
42
-
43
39
  end
44
40
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Praxis
3
4
  module Finalizable
4
5
  def self.extended(klass)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Praxis
2
4
  module Handlers
3
5
  class JSON
@@ -9,7 +11,7 @@ module Praxis
9
11
  rescue LoadError
10
12
  # Should never happen since JSON is a default gem; might as well be cautious!
11
13
  raise Praxis::Exceptions::InvalidConfiguration,
12
- "JSON handler depends on json ~> 1.0; please add it to your Gemfile"
14
+ 'JSON handler depends on json ~> 1.0; please add it to your Gemfile'
13
15
  end
14
16
 
15
17
  # Parse a JSON document into structured data.
@@ -18,7 +20,8 @@ module Praxis
18
20
  # @return [Hash,Array] the structured-data representation of the document
19
21
  def parse(document)
20
22
  # Try to be nice and accept an empty string as an empty payload (seems nice to do for dumb http clients)
21
- return nil if (document.nil? || document == '')
23
+ return nil if document.nil? || document == ''
24
+
22
25
  ::JSON.parse(document)
23
26
  end
24
27
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Praxis
2
4
  module Handlers
3
5
  class Plain
@@ -10,7 +12,6 @@ module Praxis
10
12
  def generate(structured_data)
11
13
  structured_data
12
14
  end
13
-
14
15
  end
15
16
  end
16
17
  end
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This is an example of a handler that can load and generate www-url-encoded payloads.
2
- # Note that if you use your API to pass nil values for attributes as a way to unset their
3
- # values, this handler will not work (as there isn't necessarily a defined "null" value in
4
+ # Note that if you use your API to pass nil values for attributes as a way to unset their
5
+ # values, this handler will not work (as there isn't necessarily a defined "null" value in
4
6
  # this encoding (although you can probably define how to encode/decode it and use it as such)
5
7
  # Use at your own risk.
6
8
  module Praxis
@@ -19,8 +21,9 @@ module Praxis
19
21
  # is not very useful for a response body.
20
22
  def generate(structured_data)
21
23
  return nil if structured_data.nil?
24
+
22
25
  URI.encode_www_form(structured_data)
23
26
  end
24
27
  end
25
28
  end
26
- end
29
+ end