praxis 2.0.pre.8 → 2.0.pre.13

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 (242) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +1 -3
  5. data/CHANGELOG.md +33 -0
  6. data/TODO.md +1 -4
  7. data/bin/praxis +67 -12
  8. data/lib/praxis.rb +10 -3
  9. data/lib/praxis/action_definition.rb +15 -13
  10. data/lib/praxis/action_definition/headers_dsl_compiler.rb +0 -7
  11. data/lib/praxis/api_general_info.rb +1 -1
  12. data/lib/praxis/application.rb +6 -2
  13. data/lib/praxis/blueprint.rb +357 -0
  14. data/lib/praxis/bootloader.rb +9 -3
  15. data/lib/praxis/bootloader_stages/environment.rb +16 -13
  16. data/lib/praxis/collection.rb +1 -11
  17. data/lib/praxis/config_hash.rb +44 -0
  18. data/lib/praxis/docs/{openapi → open_api}/info_object.rb +18 -10
  19. data/lib/praxis/docs/{openapi → open_api}/media_type_object.rb +0 -0
  20. data/lib/praxis/docs/{openapi → open_api}/operation_object.rb +0 -0
  21. data/lib/praxis/docs/{openapi → open_api}/parameter_object.rb +0 -0
  22. data/lib/praxis/docs/{openapi → open_api}/paths_object.rb +0 -0
  23. data/lib/praxis/docs/{openapi → open_api}/request_body_object.rb +0 -0
  24. data/lib/praxis/docs/{openapi → open_api}/response_object.rb +0 -0
  25. data/lib/praxis/docs/{openapi → open_api}/responses_object.rb +0 -0
  26. data/lib/praxis/docs/{openapi → open_api}/schema_object.rb +0 -0
  27. data/lib/praxis/docs/{openapi → open_api}/server_object.rb +0 -0
  28. data/lib/praxis/docs/{openapi → open_api}/tag_object.rb +0 -0
  29. data/lib/praxis/docs/open_api_generator.rb +91 -6
  30. data/lib/praxis/endpoint_definition.rb +273 -0
  31. data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +182 -58
  32. data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +3 -2
  33. data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +47 -56
  34. data/lib/praxis/extensions/attribute_filtering/filters_parser.rb +153 -0
  35. data/lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb +20 -8
  36. data/lib/praxis/extensions/field_expansion.rb +3 -36
  37. data/lib/praxis/extensions/pagination.rb +5 -32
  38. data/lib/praxis/extensions/pagination/ordering_params.rb +1 -1
  39. data/lib/praxis/extensions/pagination/pagination_params.rb +6 -4
  40. data/lib/praxis/field_expander.rb +90 -0
  41. data/lib/praxis/finalizable.rb +34 -0
  42. data/lib/praxis/mapper/active_model_compat.rb +4 -0
  43. data/lib/praxis/mapper/resource.rb +18 -2
  44. data/lib/praxis/mapper/selector_generator.rb +2 -1
  45. data/lib/praxis/mapper/sequel_compat.rb +7 -0
  46. data/lib/praxis/media_type.rb +3 -68
  47. data/lib/praxis/plugin_concern.rb +1 -1
  48. data/lib/praxis/plugins/mapper_plugin.rb +24 -15
  49. data/lib/praxis/plugins/pagination_plugin.rb +34 -4
  50. data/lib/praxis/renderer.rb +88 -0
  51. data/lib/praxis/request.rb +1 -1
  52. data/lib/praxis/resource_definition.rb +2 -311
  53. data/lib/praxis/response_definition.rb +2 -10
  54. data/lib/praxis/response_template.rb +3 -3
  55. data/lib/praxis/router.rb +2 -2
  56. data/lib/praxis/routing_config.rb +1 -1
  57. data/lib/praxis/tasks/api_docs.rb +17 -64
  58. data/lib/praxis/tasks/routes.rb +1 -1
  59. data/lib/praxis/types/media_type_common.rb +1 -11
  60. data/lib/praxis/version.rb +1 -1
  61. data/praxis.gemspec +0 -1
  62. data/spec/functional_spec.rb +5 -9
  63. data/spec/praxis/action_definition_spec.rb +12 -20
  64. data/spec/praxis/blueprint_spec.rb +373 -0
  65. data/spec/praxis/bootloader_spec.rb +10 -2
  66. data/spec/praxis/collection_spec.rb +0 -13
  67. data/spec/praxis/config_hash_spec.rb +64 -0
  68. data/spec/praxis/{resource_definition_spec.rb → endpoint_definition_spec.rb} +37 -64
  69. data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +249 -168
  70. data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +25 -6
  71. data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +190 -8
  72. data/spec/praxis/extensions/attribute_filtering/filters_parser_spec.rb +140 -0
  73. data/spec/praxis/extensions/field_expansion_spec.rb +5 -24
  74. data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +1 -1
  75. data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +1 -1
  76. data/spec/praxis/extensions/support/spec_resources_active_model.rb +1 -1
  77. data/spec/praxis/field_expander_spec.rb +149 -0
  78. data/spec/praxis/mapper/selector_generator_spec.rb +1 -1
  79. data/spec/praxis/media_type_identifier_spec.rb +5 -4
  80. data/spec/praxis/media_type_spec.rb +4 -93
  81. data/spec/praxis/renderer_spec.rb +188 -0
  82. data/spec/praxis/response_definition_spec.rb +0 -31
  83. data/spec/praxis/response_spec.rb +1 -1
  84. data/spec/praxis/router_spec.rb +8 -8
  85. data/spec/praxis/routing_config_spec.rb +3 -3
  86. data/spec/spec_app/app/controllers/instances.rb +13 -7
  87. data/spec/spec_app/design/media_types/instance.rb +1 -19
  88. data/spec/spec_app/design/media_types/volume.rb +1 -1
  89. data/spec/spec_app/design/media_types/volume_snapshot.rb +2 -14
  90. data/spec/spec_app/design/resources/instances.rb +5 -8
  91. data/spec/spec_app/design/resources/volume_snapshots.rb +1 -1
  92. data/spec/spec_app/design/resources/volumes.rb +1 -1
  93. data/spec/support/spec_authorization_plugin.rb +1 -1
  94. data/spec/support/spec_blueprints.rb +72 -0
  95. data/spec/support/{spec_resource_definitions.rb → spec_endpoint_definitions.rb} +2 -2
  96. data/spec/support/spec_media_types.rb +6 -26
  97. data/tasks/thor/app.rb +8 -34
  98. data/tasks/thor/example.rb +51 -285
  99. data/tasks/thor/model.rb +40 -0
  100. data/tasks/thor/scaffold.rb +117 -0
  101. data/tasks/thor/templates/generator/empty_app/.gitignore +0 -1
  102. data/tasks/thor/templates/generator/empty_app/Gemfile +7 -23
  103. data/tasks/thor/templates/generator/empty_app/README.md +1 -1
  104. data/tasks/thor/templates/generator/empty_app/Rakefile +4 -13
  105. data/tasks/thor/templates/generator/empty_app/{design/response_templates → app/v1/resources}/.empty_directory +0 -0
  106. data/tasks/thor/templates/generator/empty_app/{design/response_templates → app/v1/resources}/.gitkeep +0 -0
  107. data/tasks/thor/templates/generator/empty_app/config/environment.rb +26 -17
  108. data/tasks/thor/templates/generator/empty_app/{design/v1/resources → config/initializers}/.empty_directory +0 -0
  109. data/tasks/thor/templates/generator/empty_app/{design/v1/resources → config/initializers}/.gitkeep +0 -0
  110. data/tasks/thor/templates/generator/empty_app/design/v1/endpoints/.empty_directory +0 -0
  111. data/tasks/thor/templates/generator/empty_app/design/v1/endpoints/.gitkeep +0 -0
  112. data/tasks/thor/templates/generator/empty_app/docs/.empty_directory +0 -0
  113. data/tasks/thor/templates/generator/empty_app/docs/.gitkeep +0 -0
  114. data/tasks/thor/templates/generator/empty_app/spec/spec_helper.rb +14 -9
  115. data/tasks/thor/templates/generator/example_app/.gitignore +1 -0
  116. data/tasks/thor/templates/generator/example_app/Gemfile +19 -0
  117. data/tasks/thor/templates/generator/example_app/Rakefile +61 -0
  118. data/tasks/thor/templates/generator/example_app/app/models/user.rb +6 -0
  119. data/tasks/thor/templates/generator/example_app/app/v1/concerns/controller_base.rb +24 -0
  120. data/tasks/thor/templates/generator/example_app/app/v1/controllers/users.rb +17 -0
  121. data/tasks/thor/templates/generator/example_app/app/v1/resources/base.rb +11 -0
  122. data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +25 -0
  123. data/tasks/thor/templates/generator/example_app/config.ru +30 -0
  124. data/tasks/thor/templates/generator/example_app/config/environment.rb +41 -0
  125. data/tasks/thor/templates/generator/example_app/db/migrate/20201010101010_create_users_table.rb +12 -0
  126. data/tasks/thor/templates/generator/example_app/db/seeds.rb +6 -0
  127. data/tasks/thor/templates/generator/example_app/design/api.rb +18 -0
  128. data/tasks/thor/templates/generator/example_app/design/v1/endpoints/users.rb +37 -0
  129. data/tasks/thor/templates/generator/example_app/design/v1/media_types/user.rb +21 -0
  130. data/tasks/thor/templates/generator/example_app/spec/helpers/database_helper.rb +20 -0
  131. data/tasks/thor/templates/generator/example_app/spec/spec_helper.rb +42 -0
  132. data/tasks/thor/templates/generator/example_app/spec/v1/controllers/users_spec.rb +37 -0
  133. data/tasks/thor/templates/generator/scaffold/design/endpoints/collection.rb +98 -0
  134. data/tasks/thor/templates/generator/scaffold/design/media_types/item.rb +18 -0
  135. data/tasks/thor/templates/generator/scaffold/implementation/controllers/collection.rb +77 -0
  136. data/tasks/thor/templates/generator/scaffold/implementation/resources/base.rb +11 -0
  137. data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +45 -0
  138. data/tasks/thor/templates/generator/scaffold/models/active_record.rb +6 -0
  139. data/tasks/thor/templates/generator/scaffold/models/sequel.rb +6 -0
  140. metadata +64 -136
  141. data/lib/api_browser/.bowerrc +0 -3
  142. data/lib/api_browser/.editorconfig +0 -21
  143. data/lib/api_browser/Gruntfile.js +0 -581
  144. data/lib/api_browser/app/index.html +0 -59
  145. data/lib/api_browser/app/js/app.js +0 -48
  146. data/lib/api_browser/app/js/controllers/action.js +0 -47
  147. data/lib/api_browser/app/js/controllers/controller.js +0 -10
  148. data/lib/api_browser/app/js/controllers/menu.js +0 -93
  149. data/lib/api_browser/app/js/controllers/trait.js +0 -10
  150. data/lib/api_browser/app/js/controllers/type.js +0 -24
  151. data/lib/api_browser/app/js/directives/attribute_description.js +0 -56
  152. data/lib/api_browser/app/js/directives/attribute_table.js +0 -28
  153. data/lib/api_browser/app/js/directives/conditional_requirements.js +0 -13
  154. data/lib/api_browser/app/js/directives/fixed_if_fits.js +0 -38
  155. data/lib/api_browser/app/js/directives/highlight.js +0 -14
  156. data/lib/api_browser/app/js/directives/menu_item.js +0 -59
  157. data/lib/api_browser/app/js/directives/no_container.js +0 -8
  158. data/lib/api_browser/app/js/directives/readable_list.js +0 -87
  159. data/lib/api_browser/app/js/directives/request_examples.js +0 -31
  160. data/lib/api_browser/app/js/directives/type_placeholder.js +0 -30
  161. data/lib/api_browser/app/js/directives/url.js +0 -15
  162. data/lib/api_browser/app/js/factories/Configuration.js +0 -12
  163. data/lib/api_browser/app/js/factories/Documentation.js +0 -61
  164. data/lib/api_browser/app/js/factories/Example.js +0 -51
  165. data/lib/api_browser/app/js/factories/PageInfo.js +0 -9
  166. data/lib/api_browser/app/js/factories/normalize_attributes.js +0 -20
  167. data/lib/api_browser/app/js/factories/prepare_template.js +0 -15
  168. data/lib/api_browser/app/js/factories/template_for.js +0 -128
  169. data/lib/api_browser/app/js/filters/attribute_name.js +0 -10
  170. data/lib/api_browser/app/js/filters/friendly_json.js +0 -5
  171. data/lib/api_browser/app/js/filters/has_requirement.js +0 -14
  172. data/lib/api_browser/app/js/filters/header_info.js +0 -9
  173. data/lib/api_browser/app/js/filters/is_empty.js +0 -8
  174. data/lib/api_browser/app/js/filters/markdown.js +0 -6
  175. data/lib/api_browser/app/js/filters/resource_name.js +0 -5
  176. data/lib/api_browser/app/js/filters/tag_requirement.js +0 -13
  177. data/lib/api_browser/app/sass/modules/_body.scss +0 -40
  178. data/lib/api_browser/app/sass/modules/_cloke.scss +0 -8
  179. data/lib/api_browser/app/sass/modules/_header.scss +0 -10
  180. data/lib/api_browser/app/sass/modules/_nav.scss +0 -7
  181. data/lib/api_browser/app/sass/modules/_sidebar.scss +0 -134
  182. data/lib/api_browser/app/sass/modules/_switch.scss +0 -55
  183. data/lib/api_browser/app/sass/modules/_table.scss +0 -13
  184. data/lib/api_browser/app/sass/praxis.scss +0 -70
  185. data/lib/api_browser/app/sass/variables/_bootstrap-variables.scss +0 -774
  186. data/lib/api_browser/app/views/action.html +0 -97
  187. data/lib/api_browser/app/views/builtin/field-selector.html +0 -24
  188. data/lib/api_browser/app/views/controller.html +0 -55
  189. data/lib/api_browser/app/views/directives/attribute_description.html +0 -2
  190. data/lib/api_browser/app/views/directives/attribute_description/default.html +0 -2
  191. data/lib/api_browser/app/views/directives/attribute_description/example.html +0 -13
  192. data/lib/api_browser/app/views/directives/attribute_description/headers.html +0 -8
  193. data/lib/api_browser/app/views/directives/attribute_description/member_options.html +0 -4
  194. data/lib/api_browser/app/views/directives/attribute_description/values.html +0 -14
  195. data/lib/api_browser/app/views/directives/attribute_table.html +0 -17
  196. data/lib/api_browser/app/views/directives/menu_item.html +0 -8
  197. data/lib/api_browser/app/views/directives/url.html +0 -3
  198. data/lib/api_browser/app/views/examples/general.html +0 -26
  199. data/lib/api_browser/app/views/home.html +0 -5
  200. data/lib/api_browser/app/views/layout.html +0 -8
  201. data/lib/api_browser/app/views/menu.html +0 -42
  202. data/lib/api_browser/app/views/navbar.html +0 -9
  203. data/lib/api_browser/app/views/trait.html +0 -13
  204. data/lib/api_browser/app/views/type.html +0 -6
  205. data/lib/api_browser/app/views/type/details.html +0 -33
  206. data/lib/api_browser/app/views/types/embedded/array.html +0 -2
  207. data/lib/api_browser/app/views/types/embedded/default.html +0 -12
  208. data/lib/api_browser/app/views/types/embedded/field-selector.html +0 -13
  209. data/lib/api_browser/app/views/types/embedded/links.html +0 -11
  210. data/lib/api_browser/app/views/types/embedded/requirements.html +0 -6
  211. data/lib/api_browser/app/views/types/embedded/single_req.html +0 -9
  212. data/lib/api_browser/app/views/types/embedded/struct.html +0 -14
  213. data/lib/api_browser/app/views/types/label/link.html +0 -1
  214. data/lib/api_browser/app/views/types/label/primitive.html +0 -1
  215. data/lib/api_browser/app/views/types/label/primitive_collection.html +0 -1
  216. data/lib/api_browser/app/views/types/label/type.html +0 -1
  217. data/lib/api_browser/app/views/types/label/type_collection.html +0 -1
  218. data/lib/api_browser/app/views/types/main/array.html +0 -22
  219. data/lib/api_browser/app/views/types/main/default.html +0 -23
  220. data/lib/api_browser/app/views/types/main/hash.html +0 -23
  221. data/lib/api_browser/app/views/types/standalone/array.html +0 -3
  222. data/lib/api_browser/app/views/types/standalone/default.html +0 -18
  223. data/lib/api_browser/app/views/types/standalone/struct.html +0 -2
  224. data/lib/api_browser/bower_template.json +0 -41
  225. data/lib/api_browser/package-lock.json +0 -7110
  226. data/lib/api_browser/package.json +0 -43
  227. data/lib/praxis/docs/generator.rb +0 -243
  228. data/lib/praxis/docs/link_builder.rb +0 -30
  229. data/lib/praxis/links.rb +0 -135
  230. data/lib/praxis/types/multipart.rb +0 -109
  231. data/spec/api_browser/directives/type_placeholder_spec.js +0 -134
  232. data/spec/api_browser/factories/configuration_spec.js +0 -32
  233. data/spec/api_browser/factories/documentation_spec.js +0 -100
  234. data/spec/api_browser/factories/normalize_attributes_spec.js +0 -92
  235. data/spec/api_browser/factories/template_for_spec.js +0 -67
  236. data/spec/api_browser/filters/attribute_name_spec.js +0 -23
  237. data/spec/praxis/types/multipart_spec.rb +0 -112
  238. data/tasks/thor/templates/generator/empty_app/.rspec +0 -1
  239. data/tasks/thor/templates/generator/empty_app/Guardfile +0 -3
  240. data/tasks/thor/templates/generator/empty_app/config/rainbows.rb +0 -57
  241. data/tasks/thor/templates/generator/empty_app/docs/app.js +0 -1
  242. data/tasks/thor/templates/generator/empty_app/docs/styles.scss +0 -3
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+ module Praxis
3
+ module Finalizable
4
+ def self.extended(klass)
5
+ klass.module_eval do
6
+ @finalizable = Set.new
7
+ end
8
+ end
9
+
10
+ def inherited(base)
11
+ @finalizable << base
12
+ base.instance_variable_set(:@finalizable, @finalizable)
13
+ base.instance_variable_set(:@finalized, false)
14
+ end
15
+
16
+ def finalizable
17
+ @finalizable
18
+ end
19
+
20
+ def finalized?
21
+ @finalized
22
+ end
23
+
24
+ def _finalize!
25
+ @finalized = true
26
+ end
27
+
28
+ def finalize!
29
+ finalizable.reject(&:finalized?).each(&:_finalize!)
30
+
31
+ finalize! unless finalizable.all?(&:finalized?)
32
+ end
33
+ end
34
+ end
@@ -23,6 +23,10 @@ module Praxis
23
23
  Praxis::Extensions::FieldSelection::ActiveRecordQuerySelector
24
24
  end
25
25
 
26
+ def _pagination_query_builder_class
27
+ Praxis::Extensions::Pagination::ActiveRecordPaginationHandler
28
+ end
29
+
26
30
  def _praxis_associations
27
31
  orig = self.reflections.clone
28
32
 
@@ -30,6 +30,7 @@ module Praxis::Mapper
30
30
  end
31
31
 
32
32
  @properties = self.superclass.properties.clone
33
+ @_filters_map = {}
33
34
  end
34
35
 
35
36
  end
@@ -197,7 +198,7 @@ module Praxis::Mapper
197
198
 
198
199
  # TODO: this shouldn't be needed if we incorporate it with the properties of the mapper...
199
200
  # ...maybe what this means is that we can change it for a better DSL in the resource?
200
- def self.filters_mapping(definition)
201
+ def self.filters_mapping(definition={})
201
202
  @_filters_map = \
202
203
  case definition
203
204
  when Hash
@@ -211,7 +212,9 @@ module Praxis::Mapper
211
212
 
212
213
  def self.craft_filter_query(base_query, filters:) # rubocop:disable Metrics/AbcSize
213
214
  if filters
214
- raise "Must define the mapping of filters if want to use Filtering for resource: #{self}" unless @_filters_map
215
+ unless @_filters_map
216
+ raise "To use API filtering, you must define the mapping of api-names to resource properties (using the `filters_mapping` method in #{self})"
217
+ end
215
218
  debug = Praxis::Application.instance.config.mapper.debug_queries
216
219
  base_query = model._filter_query_builder_class.new(query: base_query, model: model, filters_map: @_filters_map, debug: debug).generate(filters)
217
220
  end
@@ -228,6 +231,19 @@ module Praxis::Mapper
228
231
  base_query
229
232
  end
230
233
 
234
+ def self.craft_pagination_query(base_query, pagination: ) # rubocop:disable Metrics/AbcSize
235
+ handler_klass = model._pagination_query_builder_class
236
+ return base_query unless (handler_klass && (pagination.paginator || pagination.order))
237
+
238
+ # Gather and save the count if required
239
+ if pagination.paginator&.total_count
240
+ pagination.total_count = handler_klass.count(base_query.dup)
241
+ end
242
+
243
+ base_query = handler_klass.order(base_query, pagination.order)
244
+ handler_klass.paginate(base_query, pagination)
245
+ end
246
+
231
247
  def initialize(record)
232
248
  @record = record
233
249
  end
@@ -48,7 +48,7 @@ module Praxis::Mapper
48
48
  new_fields_as_hash = association[:remote_key_columns].each_with_object({}) do|name, hash|
49
49
  hash[name] = true
50
50
  end
51
- fields.merge!(new_fields_as_hash)
51
+ fields = fields.merge(new_fields_as_hash)
52
52
  end
53
53
 
54
54
  node.add(fields) unless fields == true
@@ -141,6 +141,7 @@ module Praxis::Mapper
141
141
  def add(resource, fields)
142
142
  @root = SelectorGeneratorNode.new(resource)
143
143
  @root.add(fields)
144
+ self
144
145
  end
145
146
 
146
147
  def selectors
@@ -7,6 +7,9 @@ module Praxis::Mapper
7
7
 
8
8
  included do
9
9
  attr_accessor :_resource
10
+ class <<self
11
+ alias_method :find_by, :find # Easy way to be method compatible with AR
12
+ end
10
13
  end
11
14
 
12
15
  module ClassMethods
@@ -19,6 +22,10 @@ module Praxis::Mapper
19
22
  Praxis::Extensions::FieldSelection::SequelQuerySelector
20
23
  end
21
24
 
25
+ def _pagination_query_builder_class
26
+ Praxis::Extensions::Pagination::SequelPaginationHandler
27
+ end
28
+
22
29
  def _praxis_associations
23
30
  orig = self.association_reflections.clone
24
31
  orig.each do |k,v|
@@ -34,8 +34,9 @@ module Praxis
34
34
  # description: 'The factory in which this widget was produced'
35
35
  # end
36
36
  #
37
- # # All resources should have a default view
38
- # view :default do
37
+ # # All resources will be assigned a default_fieldset (with only non-blueprint attributes)
38
+ # # But one can explicitly tailor that by using the `default_fieldset` DSL
39
+ # default_fieldset do
39
40
  # attribute :id
40
41
  # attribute :color
41
42
  # attribute :material
@@ -44,72 +45,6 @@ module Praxis
44
45
  class MediaType < Praxis::Blueprint
45
46
 
46
47
  include Types::MediaTypeCommon
47
-
48
- class FieldResolver
49
- def self.resolve(type,fields)
50
- self.new.resolve(type,fields)
51
- end
52
-
53
- attr_reader :history
54
-
55
- def initialize
56
- @history = Hash.new do |hash,key|
57
- hash[key] = Hash.new
58
- end
59
- end
60
-
61
- def resolve(type,fields)
62
- history_key = fields
63
- history_type = type
64
- if fields.kind_of?(Array)
65
- loop do
66
- type = type.member_attribute.type
67
- fields = fields.first
68
- break unless fields.kind_of?(Array)
69
- end
70
- end
71
-
72
- return true if fields == true
73
-
74
- if history[history_type].include? history_key
75
- return history[history_type][history_key]
76
- end
77
-
78
- result = history[history_type][history_key] = {}
79
-
80
-
81
- fields.each do |name, sub_fields|
82
-
83
- new_type = type.attributes[name].type
84
- result[name] = resolve(new_type, sub_fields)
85
- end
86
-
87
- result
88
- end
89
-
90
- # perform a deep recursive *in place* merge
91
- # form all values in +source+ onto +target+
92
- #
93
- # note: can not use ActiveSupport's Hash#deep_merge! because it does not
94
- # properly do a recursive `deep_merge!`, but instead does `deep_merge`,
95
- # which destroys the self-referential behavior of field hashes.
96
- #
97
- # note: unlike Hash#merge, doesn't take a block.
98
- def deep_merge(target, source)
99
- source.each do |current_key, source_value|
100
- target_value = target[current_key]
101
-
102
- target[current_key] = if target_value.is_a?(Hash) && source_value.is_a?(Hash)
103
- deep_merge(target_value, source_value)
104
- else
105
- source_value
106
- end
107
- end
108
- target
109
- end
110
-
111
- end
112
-
113
48
  end
114
49
 
115
50
  end
@@ -11,7 +11,7 @@ module Praxis
11
11
  PLUGIN_CLASSES = [
12
12
  :Request,
13
13
  :Controller,
14
- :ResourceDefinition,
14
+ :EndpointDefinition,
15
15
  :ActionDefinition,
16
16
  :Response,
17
17
  :ApiGeneralInfo
@@ -1,11 +1,23 @@
1
1
  require 'singleton'
2
- require 'praxis/extensions/attribute_filtering'
3
2
 
4
- module Praxis
3
+ require 'praxis/extensions/field_selection'
4
+
5
+ module Praxis
5
6
  module Plugins
6
7
  module MapperPlugin
7
8
  include Praxis::PluginConcern
8
9
 
10
+ # The Mapper plugin is an overarching set of things to include in your application
11
+ # when you want to use the rendring, field_selection, filtering (and potentially pagination) extensions
12
+ # To use the plugin, set it up like any other plugin by registering to the bootloader.
13
+ # Typically you'd do that in environment.rb, inside the `Praxis::Application.configure do |application|` block, by:
14
+ # application.bootloader.use Praxis::Plugins::MapperPlugin
15
+ #
16
+ # The plugin accepts only 1 configuration option thus far, which you can set inside the same block as:
17
+ # application.config.mapper.debug_queries = true
18
+ # when debug_queries is set to true, the system will output information about the expanded fields
19
+ # and associations that the system ihas calculated necessary to pull from the DB, based on the requested
20
+ # API fields, API filters and `property` dependencies defined in the domain models (i.e., resources)
9
21
  class Plugin < Praxis::Plugin
10
22
  include Singleton
11
23
 
@@ -29,36 +41,33 @@ module Praxis
29
41
  extend ActiveSupport::Concern
30
42
 
31
43
  included do
44
+ include Praxis::Extensions::Rendering
32
45
  include Praxis::Extensions::FieldExpansion
33
46
  end
34
47
 
35
- def set_selectors
36
- return unless self.media_type.respond_to?(:domain_model) &&
37
- self.media_type.domain_model < Praxis::Mapper::Resource
38
-
39
- resolved = Praxis::MediaType::FieldResolver.resolve(self.media_type, self.expanded_fields)
40
- selector_generator.add(self.media_type.domain_model, resolved)
41
- end
42
-
43
48
  def build_query(base_query) # rubocop:disable Metrics/AbcSize
44
49
  domain_model = self.media_type&.domain_model
45
50
  raise "No domain model defined for #{self.name}. Cannot use the attribute filtering helpers without it" unless domain_model
46
51
 
47
52
  filters = request.params.filters if request.params&.respond_to?(:filters)
53
+ # Handle filters
48
54
  base_query = domain_model.craft_filter_query( base_query , filters: filters )
49
-
55
+ # Handle field and nested field selection
50
56
  base_query = domain_model.craft_field_selection_query(base_query, selectors: selector_generator.selectors)
57
+ # handle pagination and ordering if the pagination extention is included
58
+ base_query = domain_model.craft_pagination_query(base_query, pagination: _pagination) if self.respond_to?(:_pagination)
51
59
 
52
- # TODO: handle pagination and ordering
53
60
  base_query
54
61
  end
55
62
 
56
63
  def selector_generator
57
- @selector_generator ||= Praxis::Mapper::SelectorGenerator.new
58
- end
64
+ return unless self.media_type.respond_to?(:domain_model) &&
65
+ self.media_type.domain_model < Praxis::Mapper::Resource
59
66
 
67
+ @selector_generator ||= \
68
+ Praxis::Mapper::SelectorGenerator.new.add(self.media_type.domain_model, self.expanded_fields)
69
+ end
60
70
  end
61
-
62
71
  end
63
72
  end
64
73
  end
@@ -1,14 +1,45 @@
1
1
  require 'singleton'
2
2
  require 'praxis/extensions/pagination'
3
3
 
4
- # Simple plugin concept
4
+ # The PaginationPlugin can be configured to take advantage of adding pagination and sorting to
5
+ # your DB queries.
6
+ # When combined with the MapperPlugin, there is no extra configuration that needs to be done for
7
+ # the system to appropriately identify the pagination and order parameters in the API, and translate
8
+ # that in to the appropriate queries to fetch.
9
+ #
10
+ # To use this plugin without the MapperPlugin (probably a rare case), one can apply the appropriate
11
+ # clauses onto a query, by directly calling (in the controller) the `craft_pagination_query` method
12
+ # of the domain_model associated to the controller's mediatype.
13
+ # For example, here's how you can manually use this extension in a fictitious users index action:
14
+ # def index
15
+ # base_query = User.all # Start by not excluding any user
16
+ # domain_model = self.media_type.domain_model
17
+ # objs = domain_model.craft_pagination_query(base_query, pagination: _pagination)
18
+ # display(objs)
19
+ # end
20
+ #
21
+ # This plugin accepts configuration about the default behavior of pagination.
22
+ # Any of these configs can individually be overidden when defining each Pagination/Order parameters
23
+ # in any of the Endpoint actions.
24
+ #
5
25
  # Example configuration for this plugin
6
26
  # Praxis::Application.configure do |application|
7
27
  # application.bootloader.use Praxis::Plugins::PaginationPlugin, {
28
+ # # The maximum number of results that a paginated response will ever allow
8
29
  # max_items: 500, # Unlimited by default,
30
+ # # The default page size to use when no `items` is specified
9
31
  # default_page_size: 100,
10
- # disallow_paging_by_default: false,
11
- # # See all available options below
32
+ # # Disallows the use of the page type pagination mode when true (i.e., using 'page=' parameter)
33
+ # disallow_paging_by_default: true, # Default false
34
+ # # Disallows the use of the cursor type pagination mode when true (i.e., using 'by=' or 'from=' parameter)
35
+ # disallow_cursor_by_default: true, # Default false
36
+ # # The default mode params to use
37
+ # paging_default_mode: {by: :uuid}, # Default {by: :uid}
38
+ # # Weather or not to enforce that all requested sort fields are part of the media_type attributes
39
+ # # when false (not enforced) only the first field would be checked
40
+ # sorting: {
41
+ # enforce_all_fields: false # Default true
42
+ # }
12
43
  # end
13
44
  # end
14
45
  #
@@ -39,7 +70,6 @@ module Praxis
39
70
  attribute :paging_default_mode, Hash, default: Praxis::Types::PaginationParams.paging_default_mode
40
71
  attribute :disallow_paging_by_default, Attributor::Boolean, default: Praxis::Types::PaginationParams.disallow_paging_by_default
41
72
  attribute :disallow_cursor_by_default, Attributor::Boolean, default: Praxis::Types::PaginationParams.disallow_cursor_by_default
42
- attribute :disallow_cursor_by_default, Attributor::Boolean, default: Praxis::Types::PaginationParams.disallow_cursor_by_default
43
73
  attribute :sorting do
44
74
  attribute :enforce_all_fields, Attributor::Boolean, default: Praxis::Types::OrderingParams.enforce_all_fields
45
75
  end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Praxis
4
+ class Renderer
5
+ attr_reader :include_nil
6
+ attr_reader :cache
7
+
8
+ class CircularRenderingError < StandardError
9
+ attr_reader :object
10
+ attr_reader :context
11
+
12
+ def initialize(object, context)
13
+ @object = object
14
+ @context = context
15
+
16
+ first = Attributor.humanize_context(context[0..10])
17
+ last = Attributor.humanize_context(context[-5..-1])
18
+ pretty_context = "#{first}...#{last}"
19
+ super("SystemStackError in rendering #{object.class} with context: #{pretty_context}")
20
+ end
21
+ end
22
+
23
+ def initialize(include_nil: false)
24
+ @cache = Hash.new do |hash, key|
25
+ hash[key] = {}
26
+ end
27
+
28
+ @include_nil = include_nil
29
+ end
30
+
31
+ # Renders an object using a given list of fields.
32
+ #
33
+ # @param [Object] object the object to render
34
+ # @param [Hash] fields the correct set of fields, as from FieldExpander
35
+ def render(object, fields, context: Attributor::DEFAULT_ROOT_CONTEXT)
36
+ if object.is_a? Praxis::Blueprint
37
+ @cache[object._cache_key][fields] ||= _render(object, fields, context: context)
38
+ else
39
+ if object.class < Attributor::Collection
40
+ object.each_with_index.collect do |sub_object, i|
41
+ sub_context = context + ["at(#{i})"]
42
+ render(sub_object, fields, context: sub_context)
43
+ end
44
+ else
45
+ _render(object, fields, context: context)
46
+ end
47
+ end
48
+ rescue SystemStackError
49
+ raise CircularRenderingError.new(object, context)
50
+ end
51
+
52
+ def _render(object, fields, context: Attributor::DEFAULT_ROOT_CONTEXT)
53
+ if fields == true
54
+ return case object
55
+ when Attributor::Dumpable
56
+ object.dump
57
+ else
58
+ object
59
+ end
60
+ end
61
+
62
+ fields.each_with_object({}) do |(key, subfields), hash|
63
+ begin
64
+ value = object._get_attr(key)
65
+ rescue => e
66
+ raise Attributor::DumpError.new(context: context, name: key, type: object.class, original_exception: e)
67
+ end
68
+
69
+ if value.nil?
70
+ hash[key] = nil if self.include_nil
71
+ next
72
+ end
73
+
74
+ if subfields == true
75
+ hash[key] = case value
76
+ when Attributor::Dumpable
77
+ value.dump
78
+ else
79
+ value
80
+ end
81
+ else
82
+ new_context = context + [key]
83
+ hash[key] = render(value, subfields, context: new_context)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end