praxis 2.0.pre.5 → 2.0.pre.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (257) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +2 -0
  3. data/.rspec +0 -1
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +38 -0
  6. data/Gemfile +1 -1
  7. data/Guardfile +2 -1
  8. data/Rakefile +1 -7
  9. data/TODO.md +25 -0
  10. data/bin/praxis +11 -13
  11. data/lib/praxis.rb +16 -7
  12. data/lib/praxis/action_definition.rb +23 -28
  13. data/lib/praxis/action_definition/headers_dsl_compiler.rb +0 -7
  14. data/lib/praxis/api_general_info.rb +1 -1
  15. data/lib/praxis/application.rb +7 -4
  16. data/lib/praxis/blueprint.rb +357 -0
  17. data/lib/praxis/bootloader.rb +9 -3
  18. data/lib/praxis/bootloader_stages/environment.rb +15 -13
  19. data/lib/praxis/bootloader_stages/routing.rb +2 -4
  20. data/lib/praxis/collection.rb +1 -11
  21. data/lib/praxis/config_hash.rb +44 -0
  22. data/lib/praxis/docs/{openapi → open_api}/info_object.rb +18 -10
  23. data/lib/praxis/docs/{openapi → open_api}/media_type_object.rb +0 -0
  24. data/lib/praxis/docs/{openapi → open_api}/operation_object.rb +0 -0
  25. data/lib/praxis/docs/{openapi → open_api}/parameter_object.rb +2 -2
  26. data/lib/praxis/docs/{openapi → open_api}/paths_object.rb +12 -15
  27. data/lib/praxis/docs/{openapi → open_api}/request_body_object.rb +0 -0
  28. data/lib/praxis/docs/{openapi → open_api}/response_object.rb +0 -0
  29. data/lib/praxis/docs/{openapi → open_api}/responses_object.rb +0 -0
  30. data/lib/praxis/docs/{openapi → open_api}/schema_object.rb +0 -0
  31. data/lib/praxis/docs/{openapi → open_api}/server_object.rb +0 -0
  32. data/lib/praxis/docs/{openapi → open_api}/tag_object.rb +0 -0
  33. data/lib/praxis/docs/open_api_generator.rb +90 -5
  34. data/lib/praxis/endpoint_definition.rb +273 -0
  35. data/lib/praxis/extensions/attribute_filtering.rb +2 -0
  36. data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +148 -157
  37. data/lib/praxis/extensions/attribute_filtering/active_record_patches.rb +15 -0
  38. data/lib/praxis/extensions/attribute_filtering/active_record_patches/5x.rb +90 -0
  39. data/lib/praxis/extensions/attribute_filtering/active_record_patches/6_0.rb +68 -0
  40. data/lib/praxis/extensions/attribute_filtering/active_record_patches/6_1_plus.rb +58 -0
  41. data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +35 -0
  42. data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +9 -25
  43. data/lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb +3 -2
  44. data/lib/praxis/extensions/field_expansion.rb +3 -36
  45. data/lib/praxis/extensions/field_selection/active_record_query_selector.rb +7 -9
  46. data/lib/praxis/extensions/field_selection/sequel_query_selector.rb +6 -9
  47. data/lib/praxis/extensions/pagination.rb +130 -0
  48. data/lib/praxis/extensions/pagination/active_record_pagination_handler.rb +42 -0
  49. data/lib/praxis/extensions/pagination/header_generator.rb +70 -0
  50. data/lib/praxis/extensions/pagination/ordering_params.rb +238 -0
  51. data/lib/praxis/extensions/pagination/pagination_handler.rb +68 -0
  52. data/lib/praxis/extensions/pagination/pagination_params.rb +380 -0
  53. data/lib/praxis/extensions/pagination/sequel_pagination_handler.rb +45 -0
  54. data/lib/praxis/field_expander.rb +90 -0
  55. data/lib/praxis/finalizable.rb +34 -0
  56. data/lib/praxis/handlers/json.rb +2 -0
  57. data/lib/praxis/handlers/www_form.rb +5 -0
  58. data/lib/praxis/handlers/{xml.rb → xml-sample.rb} +6 -0
  59. data/lib/praxis/mapper/active_model_compat.rb +23 -5
  60. data/lib/praxis/mapper/resource.rb +16 -9
  61. data/lib/praxis/mapper/selector_generator.rb +1 -1
  62. data/lib/praxis/mapper/sequel_compat.rb +1 -0
  63. data/lib/praxis/media_type.rb +4 -124
  64. data/lib/praxis/plugin_concern.rb +1 -1
  65. data/lib/praxis/plugins/mapper_plugin.rb +7 -7
  66. data/lib/praxis/plugins/pagination_plugin.rb +71 -0
  67. data/lib/praxis/renderer.rb +88 -0
  68. data/lib/praxis/request.rb +1 -1
  69. data/lib/praxis/resource_definition.rb +2 -319
  70. data/lib/praxis/response_definition.rb +2 -10
  71. data/lib/praxis/response_template.rb +3 -3
  72. data/lib/praxis/route.rb +2 -4
  73. data/lib/praxis/router.rb +2 -2
  74. data/lib/praxis/routing_config.rb +3 -7
  75. data/lib/praxis/tasks/api_docs.rb +13 -63
  76. data/lib/praxis/tasks/routes.rb +11 -16
  77. data/lib/praxis/types/media_type_common.rb +1 -11
  78. data/lib/praxis/validation_handler.rb +1 -2
  79. data/lib/praxis/version.rb +1 -1
  80. data/praxis.gemspec +2 -4
  81. data/spec/functional_spec.rb +12 -13
  82. data/spec/praxis/action_definition_spec.rb +15 -35
  83. data/spec/praxis/api_general_info_spec.rb +6 -6
  84. data/spec/praxis/blueprint_spec.rb +373 -0
  85. data/spec/praxis/bootloader_spec.rb +10 -2
  86. data/spec/praxis/collection_spec.rb +0 -13
  87. data/spec/praxis/config_hash_spec.rb +64 -0
  88. data/spec/praxis/{resource_definition_spec.rb → endpoint_definition_spec.rb} +38 -65
  89. data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +302 -0
  90. data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +39 -0
  91. data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +140 -0
  92. data/spec/praxis/extensions/field_expansion_spec.rb +7 -44
  93. data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +15 -11
  94. data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +4 -3
  95. data/spec/praxis/extensions/pagination/active_record_pagination_handler_spec.rb +130 -0
  96. data/spec/praxis/extensions/{field_selection/support → support}/spec_resources_active_model.rb +45 -2
  97. data/spec/praxis/extensions/{field_selection/support → support}/spec_resources_sequel.rb +0 -0
  98. data/spec/praxis/field_expander_spec.rb +149 -0
  99. data/spec/praxis/media_type_identifier_spec.rb +5 -4
  100. data/spec/praxis/media_type_spec.rb +7 -220
  101. data/spec/praxis/renderer_spec.rb +188 -0
  102. data/spec/praxis/request_spec.rb +3 -22
  103. data/spec/praxis/response_definition_spec.rb +1 -36
  104. data/spec/praxis/response_spec.rb +1 -1
  105. data/spec/praxis/route_spec.rb +2 -9
  106. data/spec/praxis/router_spec.rb +8 -8
  107. data/spec/praxis/routing_config_spec.rb +7 -16
  108. data/spec/praxis/types/multipart_array_spec.rb +4 -21
  109. data/spec/spec_app/app/controllers/instances.rb +13 -7
  110. data/spec/spec_app/config/environment.rb +0 -2
  111. data/spec/spec_app/design/api.rb +1 -1
  112. data/spec/spec_app/design/media_types/instance.rb +1 -27
  113. data/spec/spec_app/design/media_types/volume.rb +1 -13
  114. data/spec/spec_app/design/media_types/volume_snapshot.rb +2 -14
  115. data/spec/spec_app/design/resources/instances.rb +6 -10
  116. data/spec/spec_app/design/resources/volume_snapshots.rb +1 -1
  117. data/spec/spec_app/design/resources/volumes.rb +1 -1
  118. data/spec/spec_helper.rb +6 -0
  119. data/spec/support/spec_authorization_plugin.rb +1 -1
  120. data/spec/support/spec_blueprints.rb +72 -0
  121. data/spec/support/{spec_resource_definitions.rb → spec_endpoint_definitions.rb} +2 -2
  122. data/spec/support/spec_media_types.rb +6 -99
  123. data/tasks/thor/app.rb +8 -34
  124. data/tasks/thor/example.rb +45 -285
  125. data/tasks/thor/templates/generator/empty_app/.gitignore +0 -1
  126. data/tasks/thor/templates/generator/empty_app/Gemfile +7 -23
  127. data/tasks/thor/templates/generator/empty_app/README.md +1 -1
  128. data/tasks/thor/templates/generator/empty_app/Rakefile +4 -13
  129. data/tasks/thor/templates/generator/empty_app/{design/response_templates → app/v1/resources}/.empty_directory +0 -0
  130. data/tasks/thor/templates/generator/empty_app/{design/response_templates → app/v1/resources}/.gitkeep +0 -0
  131. data/tasks/thor/templates/generator/empty_app/config/environment.rb +25 -17
  132. data/tasks/thor/templates/generator/empty_app/{design/v1/resources → config/initializers}/.empty_directory +0 -0
  133. data/tasks/thor/templates/generator/empty_app/{design/v1/resources → config/initializers}/.gitkeep +0 -0
  134. data/tasks/thor/templates/generator/empty_app/design/v1/endpoints/.empty_directory +0 -0
  135. data/tasks/thor/templates/generator/empty_app/design/v1/endpoints/.gitkeep +0 -0
  136. data/tasks/thor/templates/generator/empty_app/docs/.empty_directory +0 -0
  137. data/tasks/thor/templates/generator/empty_app/docs/.gitkeep +0 -0
  138. data/tasks/thor/templates/generator/empty_app/spec/spec_helper.rb +14 -9
  139. data/tasks/thor/templates/generator/example_app/.gitignore +1 -0
  140. data/tasks/thor/templates/generator/example_app/Gemfile +19 -0
  141. data/tasks/thor/templates/generator/example_app/Rakefile +54 -0
  142. data/tasks/thor/templates/generator/example_app/app/models/user.rb +6 -0
  143. data/tasks/thor/templates/generator/example_app/app/v1/controllers/users.rb +17 -0
  144. data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +46 -0
  145. data/tasks/thor/templates/generator/example_app/config.ru +31 -0
  146. data/tasks/thor/templates/generator/example_app/config/environment.rb +40 -0
  147. data/tasks/thor/templates/generator/example_app/db/migrate/20201010101010_create_users_table.rb +11 -0
  148. data/tasks/thor/templates/generator/example_app/design/api.rb +18 -0
  149. data/tasks/thor/templates/generator/example_app/design/v1/endpoints/users.rb +37 -0
  150. data/tasks/thor/templates/generator/example_app/design/v1/media_types/user.rb +26 -0
  151. data/tasks/thor/templates/generator/example_app/spec/helpers/database_helper.rb +18 -0
  152. data/tasks/thor/templates/generator/example_app/spec/spec_helper.rb +42 -0
  153. data/tasks/thor/templates/generator/example_app/spec/v1/controllers/users_spec.rb +37 -0
  154. metadata +81 -177
  155. data/lib/api_browser/.bowerrc +0 -3
  156. data/lib/api_browser/.editorconfig +0 -21
  157. data/lib/api_browser/Gruntfile.js +0 -581
  158. data/lib/api_browser/app/index.html +0 -59
  159. data/lib/api_browser/app/js/app.js +0 -48
  160. data/lib/api_browser/app/js/controllers/action.js +0 -47
  161. data/lib/api_browser/app/js/controllers/controller.js +0 -10
  162. data/lib/api_browser/app/js/controllers/menu.js +0 -93
  163. data/lib/api_browser/app/js/controllers/trait.js +0 -10
  164. data/lib/api_browser/app/js/controllers/type.js +0 -24
  165. data/lib/api_browser/app/js/directives/attribute_description.js +0 -56
  166. data/lib/api_browser/app/js/directives/attribute_table.js +0 -28
  167. data/lib/api_browser/app/js/directives/conditional_requirements.js +0 -13
  168. data/lib/api_browser/app/js/directives/fixed_if_fits.js +0 -38
  169. data/lib/api_browser/app/js/directives/highlight.js +0 -14
  170. data/lib/api_browser/app/js/directives/menu_item.js +0 -59
  171. data/lib/api_browser/app/js/directives/no_container.js +0 -8
  172. data/lib/api_browser/app/js/directives/readable_list.js +0 -87
  173. data/lib/api_browser/app/js/directives/request_examples.js +0 -31
  174. data/lib/api_browser/app/js/directives/type_placeholder.js +0 -30
  175. data/lib/api_browser/app/js/directives/url.js +0 -15
  176. data/lib/api_browser/app/js/factories/Configuration.js +0 -12
  177. data/lib/api_browser/app/js/factories/Documentation.js +0 -61
  178. data/lib/api_browser/app/js/factories/Example.js +0 -51
  179. data/lib/api_browser/app/js/factories/PageInfo.js +0 -9
  180. data/lib/api_browser/app/js/factories/normalize_attributes.js +0 -20
  181. data/lib/api_browser/app/js/factories/prepare_template.js +0 -15
  182. data/lib/api_browser/app/js/factories/template_for.js +0 -128
  183. data/lib/api_browser/app/js/filters/attribute_name.js +0 -10
  184. data/lib/api_browser/app/js/filters/friendly_json.js +0 -5
  185. data/lib/api_browser/app/js/filters/has_requirement.js +0 -14
  186. data/lib/api_browser/app/js/filters/header_info.js +0 -9
  187. data/lib/api_browser/app/js/filters/is_empty.js +0 -8
  188. data/lib/api_browser/app/js/filters/markdown.js +0 -6
  189. data/lib/api_browser/app/js/filters/resource_name.js +0 -5
  190. data/lib/api_browser/app/js/filters/tag_requirement.js +0 -13
  191. data/lib/api_browser/app/sass/modules/_body.scss +0 -40
  192. data/lib/api_browser/app/sass/modules/_cloke.scss +0 -8
  193. data/lib/api_browser/app/sass/modules/_header.scss +0 -10
  194. data/lib/api_browser/app/sass/modules/_nav.scss +0 -7
  195. data/lib/api_browser/app/sass/modules/_sidebar.scss +0 -134
  196. data/lib/api_browser/app/sass/modules/_switch.scss +0 -55
  197. data/lib/api_browser/app/sass/modules/_table.scss +0 -13
  198. data/lib/api_browser/app/sass/praxis.scss +0 -70
  199. data/lib/api_browser/app/sass/variables/_bootstrap-variables.scss +0 -774
  200. data/lib/api_browser/app/views/action.html +0 -97
  201. data/lib/api_browser/app/views/builtin/field-selector.html +0 -24
  202. data/lib/api_browser/app/views/controller.html +0 -55
  203. data/lib/api_browser/app/views/directives/attribute_description.html +0 -2
  204. data/lib/api_browser/app/views/directives/attribute_description/default.html +0 -2
  205. data/lib/api_browser/app/views/directives/attribute_description/example.html +0 -13
  206. data/lib/api_browser/app/views/directives/attribute_description/headers.html +0 -8
  207. data/lib/api_browser/app/views/directives/attribute_description/member_options.html +0 -4
  208. data/lib/api_browser/app/views/directives/attribute_description/values.html +0 -14
  209. data/lib/api_browser/app/views/directives/attribute_table.html +0 -17
  210. data/lib/api_browser/app/views/directives/menu_item.html +0 -8
  211. data/lib/api_browser/app/views/directives/url.html +0 -3
  212. data/lib/api_browser/app/views/examples/general.html +0 -26
  213. data/lib/api_browser/app/views/home.html +0 -5
  214. data/lib/api_browser/app/views/layout.html +0 -8
  215. data/lib/api_browser/app/views/menu.html +0 -42
  216. data/lib/api_browser/app/views/navbar.html +0 -9
  217. data/lib/api_browser/app/views/trait.html +0 -13
  218. data/lib/api_browser/app/views/type.html +0 -6
  219. data/lib/api_browser/app/views/type/details.html +0 -33
  220. data/lib/api_browser/app/views/types/embedded/array.html +0 -2
  221. data/lib/api_browser/app/views/types/embedded/default.html +0 -12
  222. data/lib/api_browser/app/views/types/embedded/field-selector.html +0 -13
  223. data/lib/api_browser/app/views/types/embedded/links.html +0 -11
  224. data/lib/api_browser/app/views/types/embedded/requirements.html +0 -6
  225. data/lib/api_browser/app/views/types/embedded/single_req.html +0 -9
  226. data/lib/api_browser/app/views/types/embedded/struct.html +0 -14
  227. data/lib/api_browser/app/views/types/label/link.html +0 -1
  228. data/lib/api_browser/app/views/types/label/primitive.html +0 -1
  229. data/lib/api_browser/app/views/types/label/primitive_collection.html +0 -1
  230. data/lib/api_browser/app/views/types/label/type.html +0 -1
  231. data/lib/api_browser/app/views/types/label/type_collection.html +0 -1
  232. data/lib/api_browser/app/views/types/main/array.html +0 -22
  233. data/lib/api_browser/app/views/types/main/default.html +0 -23
  234. data/lib/api_browser/app/views/types/main/hash.html +0 -23
  235. data/lib/api_browser/app/views/types/standalone/array.html +0 -3
  236. data/lib/api_browser/app/views/types/standalone/default.html +0 -18
  237. data/lib/api_browser/app/views/types/standalone/struct.html +0 -2
  238. data/lib/api_browser/bower_template.json +0 -41
  239. data/lib/api_browser/package.json +0 -43
  240. data/lib/praxis/docs/generator.rb +0 -243
  241. data/lib/praxis/docs/link_builder.rb +0 -30
  242. data/lib/praxis/links.rb +0 -135
  243. data/lib/praxis/types/multipart.rb +0 -109
  244. data/spec/api_browser/directives/type_placeholder_spec.js +0 -134
  245. data/spec/api_browser/factories/configuration_spec.js +0 -32
  246. data/spec/api_browser/factories/documentation_spec.js +0 -100
  247. data/spec/api_browser/factories/normalize_attributes_spec.js +0 -92
  248. data/spec/api_browser/factories/template_for_spec.js +0 -67
  249. data/spec/api_browser/filters/attribute_name_spec.js +0 -23
  250. data/spec/praxis/handlers/xml_spec.rb +0 -177
  251. data/spec/praxis/links_spec.rb +0 -68
  252. data/spec/praxis/types/multipart_spec.rb +0 -112
  253. data/tasks/thor/templates/generator/empty_app/.rspec +0 -1
  254. data/tasks/thor/templates/generator/empty_app/Guardfile +0 -3
  255. data/tasks/thor/templates/generator/empty_app/config/rainbows.rb +0 -57
  256. data/tasks/thor/templates/generator/empty_app/docs/app.js +0 -1
  257. data/tasks/thor/templates/generator/empty_app/docs/styles.scss +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 05fc37d45d8686251eb9691f05b7c9cdcfb3a8fe
4
- data.tar.gz: fd5b9c01c4eab9994319eedf5cae50957b8dce06
2
+ SHA256:
3
+ metadata.gz: dd76d8bd13954d5607d009ee37ff7fc1a8ef8a2c6c8c87544dddaee332d984bf
4
+ data.tar.gz: 719bffed7ea98fe42d7c620cd4377c4201c9d23e49a9777e981647d671b39ffd
5
5
  SHA512:
6
- metadata.gz: d8c4a7698ce165605a1c3a59c7fd014490b86a2bd4a27b2814e05dd1e9fa87d268a88dbd549239a07707f18b9c0a2fa350f45725686a50ef0777e1a32a11a7dc
7
- data.tar.gz: 1a93f51757421d516ee80eb0d38101c6f0eff7d9cd9483ebb4fb44634342edf88e5ff7b16b49676cb696e7653843c1a69b7d505eaa4bde954e0bb7ae60b7f13e
6
+ metadata.gz: 18e87e4563faa7a1ffbba174634f1ebabe57320b8324e4ac23a711f5785906482412db711c83308ff87264d48cc496f8bd118b5a5ac77f1d6bd8f09e543f90ed
7
+ data.tar.gz: 9da869a6acecf7c04273ef7dc0519be0cf8b3d00c29df16c851df04f5ea52d7cf4d62123e25533b8887c0a30b940155abfbd812f4fe2b347d1139df91e3e124f
data/.gitignore CHANGED
@@ -14,3 +14,5 @@ lib/api_browser/node_modules/
14
14
  lib/api_browser/app/bower_components/
15
15
  lib/api_browser/.tmp/
16
16
  lib/api_browser/bower.json
17
+
18
+ development.sqlite3
data/.rspec CHANGED
@@ -1,2 +1 @@
1
1
  --colour
2
- --format=Fuubar
@@ -0,0 +1 @@
1
+ 2.6.3
@@ -1,5 +1,43 @@
1
1
  # Praxis Changelog
2
2
 
3
+ ## next
4
+
5
+ - Simple, but pervasive breaking change: Rename `ResourceDefinition` to `EndpointDefinition` (but same functionality).
6
+ - Remove all deprecated features (and raise error describing it's not supported yet)
7
+ - Remove `Links` and `LinkBuilder`. Those seem unnecessary from a Framework point of view as they aren't clear most
8
+ applications would benefit from it. Applications can choose to add that functionality on their own if so desire.
9
+ - Rebuilt app generators: for new empty app, and example app.
10
+ - Updated default layout to match new naming structure and more concepts commonly necessary for normal applications.
11
+ - Completely removed the native Praxis API documentation browser in lieu of OpenAPI 3.x standards, and reDoc.
12
+ - Remove dependency from praxis-blueprints, as simplified subset of its code has now been included in this repo:
13
+ - no more views for mediatypes. A default fieldset will be automatically defined which will be the default set of attributes to render with. This default fieldset will only contain simple direct attributes (i.e., non blueprint/mediatype attributes). One can override the default by explicitly defining one using the `default_fieldset` DSL, similar to how views were defined before.
14
+ - Folded the pagination/ordering extensions to activate within the `build_query` method of the mapper plugin extension. This way all the field selection, filtering and pagination/ordering will kick in automatically when that plugin is included.
15
+
16
+ ## 2.0.pre.9
17
+
18
+ - Refined OpenAPI doc generation to output only non-null attributes in the InfoObject.
19
+ - Fixed filtering params validation to properly allow null values for the "!" and "!!" operators
20
+
21
+ ## 2.0.pre.6
22
+
23
+ - Removed the explicit `links` helpers from a `MediaType`. There was too much magic and assumptions built into it. Things can still be built in a custom basis (or through a plugin) if necessary.
24
+ - Removed documentation `linked_builder` annotations.
25
+ - Leave the `x-www-form-urlencoded` handler in place, but do not automatically register it for a Praxis app. To include the handler in your app register it by requiring the file `require 'praxis/handlers/www_form'` and then though `application.handler('x-www-form-urlencoded',Praxis::Handlers::WWWForm)`
26
+ - Remove the `primary_route` (and name) concept from routes.
27
+ - Added a pagination (and sotring) extension (and a Pagination Plugin for easier use)
28
+ - New pagination and ordering query string type (with simple syntax, borrowing from JSON api pagination ). Page-based and cursor-based options:
29
+ - examples for pagination: `page=5,items=50` or `by=email,from=joe@example.com,items=100`
30
+ - examples for ordering: `name,last_name,-birth_date`
31
+ - New DSL for defining the pagination and sorting parameters in Endpoint definitions
32
+ - Support for pagination header (as per draft spec http://tools.ietf.org/id/draft-nottingham-http-link-header-06.txt)
33
+ - Pagination/ordering implementation for controllers through an easy Plugin inclusion.
34
+ - ActiveRecord and Sequel DBs supported
35
+ - Reimplemented and enhanced Query Filtering Extensions
36
+ - introduced support for 'is NULL' and 'is NOT NULL' for column values in filter syntax (`!` and `!!` operators without value). example: `name!` means (name IS NOT NULL) while `name!!` means (name IS NULL). Null (or not null) relationship conditions not supported.
37
+ - support for nested join conditions (grouping where conditions in same join)
38
+ - support for respecting association scopes when filtering in joins
39
+ - ActiveRecord supported (Sequel support not done yet)
40
+
3
41
  ## 2.0.pre.5
4
42
 
5
43
  - Added support for OpenAPI 3.x document generation. Consider this in Beta state, although it is fairly close to feature complete.
data/Gemfile CHANGED
@@ -2,8 +2,8 @@ source 'https://rubygems.org'
2
2
  gemspec
3
3
 
4
4
  group :test do
5
- gem 'nokogiri'
6
5
  gem 'builder'
7
6
 
8
7
  gem 'parslet'
8
+ gem 'link_header'
9
9
  end
data/Guardfile CHANGED
@@ -1,4 +1,5 @@
1
- guard 'rspec', cmd: 'bundle exec rspec' do
1
+ guard :rspec, cmd: 'bundle exec rspec --format=Fuubar', \
2
+ all_after_pass: false, all_on_start: false, failed_mode: :focus do
2
3
  watch(%r{^spec/.+_spec\.rb$})
3
4
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
5
  watch('spec/spec_helper.rb') { "spec/" }
data/Rakefile CHANGED
@@ -3,16 +3,10 @@ $:.unshift File.expand_path('lib',__dir__)
3
3
  require 'praxis'
4
4
  require 'praxis/tasks'
5
5
 
6
-
7
- require 'rake/notes/rake_task'
8
-
9
6
  require 'rspec/core/rake_task'
10
7
 
11
8
  require 'bundler/gem_tasks'
12
9
 
13
10
  RSpec::Core::RakeTask.new(:spec)
14
11
 
15
- task :default => :spec
16
-
17
- require 'yard'
18
- YARD::Rake::YardocTask.new
12
+ task :default => :spec
data/TODO.md ADDED
@@ -0,0 +1,25 @@
1
+ # TODO
2
+
3
+ ## Things to delete
4
+
5
+ * views (just use a sensible default view of just simple types?)
6
+ * remove collection summary things...
7
+ * FieldResolver?? and that conditional dependency thing...?
8
+ * simplify or change examples? ...maybe get rid of randexp.
9
+ * NOTE: make sure the types we use only expose the json-schema types...no longer things like Hash/Struct/Symbol ...(that might be good only for coding/implementation, not for documentation)
10
+ * Plugins? ... maybe leave them out for the moment?
11
+ * change errors to be machine readable
12
+ * change naming of resource definition to endpoint definition
13
+ * get rid of deprecations
14
+
15
+
16
+ ## DONE
17
+ * remove links and link_builder
18
+ * remove xml handlers for parsing and rendering. (same for url-encoding) -- (which would allow nil-setting things)
19
+ * remove route aliases, named routes...and primary route concepts
20
+
21
+
22
+ NOTES:
23
+ APIGeneralInfo: consumes and produces (make that the default if it isn't...)
24
+ maybe remove Praxis::Handlers::WWWForm AND Praxis::Handlers::XML ? or stash them away as examples...
25
+
data/bin/praxis CHANGED
@@ -13,7 +13,8 @@ if ["routes","docs","console"].include? ARGV[0]
13
13
  require 'rake'
14
14
  require 'praxis'
15
15
  require 'praxis/tasks'
16
-
16
+ load 'Rakefile' # Ensure that we read the App's Rakefile, to pickup any definitions etc.
17
+
17
18
  case ARGV[0]
18
19
  when "routes"
19
20
  Rake::Task['praxis:routes'].invoke(ARGV[1])
@@ -24,9 +25,8 @@ if ["routes","docs","console"].include? ARGV[0]
24
25
  when 'generate'
25
26
  'praxis:docs:generate'
26
27
  when 'package'
27
- 'praxis:docs:build'
28
+ 'praxis:docs:package'
28
29
  end
29
- # task_name = ARGV[1] == 'browser' ? 'praxis:doc_browser' : 'praxis:api_docs'
30
30
  Rake::Task[task_name].invoke
31
31
  when "console"
32
32
  Rake::Task['praxis:console'].invoke
@@ -71,19 +71,17 @@ class PraxisGenerator < Thor
71
71
 
72
72
  desc_for "new APP_NAME", ::PraxisGen::App, :new
73
73
  def new(app_name)
74
- ::PraxisGen::App.start(['new' , app_name])
74
+ gen = ::PraxisGen::App.new([app_name])
75
+ gen.destination_root = app_name
76
+ gen.invoke_all
75
77
  end
76
78
 
77
- desc_for "example APP_NAME", ::PraxisGen::Example, :new
79
+ desc_for "example APP_NAME", ::PraxisGen::Example, :example
78
80
  def example(app_name)
79
- ::PraxisGen::Example.start(['new', app_name])
80
- end
81
-
82
- desc_for "generate APP_NAME", ::PraxisGen::Example, :new, "DEPRECATED!: "
83
- def generate(app_name)
84
- warn "This is a deprecated method.\nTo generate a hello world example, please use:\n praxis example #{app_name} "
85
- end
86
-
81
+ gen = ::PraxisGen::Example.new([app_name])
82
+ gen.destination_root = app_name
83
+ gen.invoke(:example)
84
+ end
87
85
  end
88
86
 
89
87
  PraxisGenerator.start(ARGV)
@@ -1,6 +1,5 @@
1
1
  require 'rack'
2
2
  require 'attributor'
3
- require 'praxis-blueprints'
4
3
 
5
4
  require 'active_support/concern'
6
5
  require 'praxis/request_superclassing'
@@ -33,7 +32,8 @@ module Praxis
33
32
  autoload :Plugin, 'praxis/plugin'
34
33
  autoload :PluginConcern, 'praxis/plugin_concern'
35
34
  autoload :Request, 'praxis/request'
36
- autoload :ResourceDefinition, 'praxis/resource_definition'
35
+ autoload :ResourceDefinition, 'praxis/resource_definition' # Deprecated: this is to support an easier transition
36
+ autoload :EndpointDefinition, 'praxis/endpoint_definition'
37
37
  autoload :Response, 'praxis/response'
38
38
  autoload :ResponseDefinition, 'praxis/response_definition'
39
39
  autoload :ResponseTemplate, 'praxis/response_template'
@@ -43,6 +43,14 @@ module Praxis
43
43
  autoload :SimpleMediaType, 'praxis/simple_media_type'
44
44
  autoload :Stage, 'praxis/stage'
45
45
  autoload :Trait, 'praxis/trait'
46
+ autoload :ConfigHash, 'praxis/config_hash'
47
+ autoload :Finalizable, 'praxis/finalizable'
48
+
49
+ # Sort of part of the old Blueprints gem...but they're really not scoped...
50
+ autoload :Blueprint, 'praxis/blueprint'
51
+ autoload :FieldExpander, 'praxis/field_expander'
52
+ autoload :Renderer, 'praxis/renderer'
53
+
46
54
 
47
55
  autoload :Notifications, 'praxis/notifications'
48
56
  autoload :MiddlewareApp, 'praxis/middleware_app'
@@ -50,7 +58,6 @@ module Praxis
50
58
  autoload :RestfulDocGenerator, 'praxis/restful_doc_generator'
51
59
  module Docs
52
60
  autoload :Generator, 'praxis/docs/generator'
53
- autoload :LinkBuilder, 'praxis/docs/link_builder'
54
61
  autoload :OpenApiGenerator, 'praxis/docs/open_api_generator'
55
62
  end
56
63
 
@@ -61,10 +68,8 @@ module Praxis
61
68
  autoload :MultipartArray, 'praxis/types/multipart_array'
62
69
  end
63
70
 
64
- autoload :Links, 'praxis/links'
65
71
  autoload :MediaType, 'praxis/media_type'
66
72
  autoload :MediaTypeIdentifier, 'praxis/media_type_identifier'
67
- autoload :Multipart, 'praxis/types/multipart'
68
73
  autoload :Collection, 'praxis/collection'
69
74
 
70
75
  autoload :MultipartParser, 'praxis/multipart/parser'
@@ -89,15 +94,19 @@ module Praxis
89
94
  autoload :MapperSelectors, 'praxis/extensions/mapper_selectors'
90
95
  autoload :Rendering, 'praxis/extensions/rendering'
91
96
  autoload :FieldExpansion, 'praxis/extensions/field_expansion'
97
+ autoload :AttributeFiltering, 'praxis/extensions/attribute_filtering'
92
98
  autoload :ActiveRecordFilterQueryBuilder, 'praxis/extensions/attribute_filtering/active_record_filter_query_builder'
93
99
  autoload :SequelFilterQueryBuilder, 'praxis/extensions/attribute_filtering/sequel_filter_query_builder'
100
+ autoload :Pagination, 'praxis/extensions/pagination'
101
+ module Pagination
102
+ autoload :ActiveRecordPaginationHandler, 'praxis/extensions/pagination/active_record_pagination_handler'
103
+ autoload :SequelPaginationHandler, 'praxis/extensions/pagination/sequel_pagination_handler'
104
+ end
94
105
  end
95
106
 
96
107
  module Handlers
97
108
  autoload :Plain, 'praxis/handlers/plain'
98
109
  autoload :JSON, 'praxis/handlers/json'
99
- autoload :WWWForm, 'praxis/handlers/www_form'
100
- autoload :XML, 'praxis/handlers/xml'
101
110
  end
102
111
 
103
112
  module BootloaderStages
@@ -11,10 +11,9 @@ module Praxis
11
11
  class ActionDefinition
12
12
 
13
13
  attr_reader :name
14
- attr_reader :resource_definition
15
- attr_reader :routes
16
- attr_reader :primary_route
17
- attr_reader :named_routes
14
+ attr_reader :endpoint_definition
15
+ attr_reader :api_definition
16
+ attr_reader :route
18
17
  attr_reader :responses
19
18
  attr_reader :traits
20
19
 
@@ -32,33 +31,33 @@ module Praxis
32
31
  self.doc_decorations << callback
33
32
  end
34
33
 
35
- def initialize(name, resource_definition, **opts, &block)
34
+ def initialize(name, endpoint_definition, **opts, &block)
36
35
  @name = name
37
- @resource_definition = resource_definition
36
+ @endpoint_definition = endpoint_definition
38
37
  @responses = Hash.new
39
38
  @metadata = Hash.new
40
- @routes = []
39
+ @route = nil
41
40
  @traits = []
42
41
 
43
- if (media_type = resource_definition.media_type)
42
+ if (media_type = endpoint_definition.media_type)
44
43
  if media_type.kind_of?(Class) && media_type < Praxis::Types::MediaTypeCommon
45
44
  @reference_media_type = media_type
46
45
  end
47
46
  end
48
47
 
49
- version = resource_definition.version
50
- api_info = ApiDefinition.instance.info(resource_definition.version)
48
+ version = endpoint_definition.version
49
+ api_info = ApiDefinition.instance.info(endpoint_definition.version)
51
50
 
52
- route_base = "#{api_info.base_path}#{resource_definition.version_prefix}"
53
- prefix = Array(resource_definition.routing_prefix)
51
+ route_base = "#{api_info.base_path}#{endpoint_definition.version_prefix}"
52
+ prefix = Array(endpoint_definition.routing_prefix)
54
53
 
55
54
  @routing_config = RoutingConfig.new(version: version, base: route_base, prefix: prefix)
56
55
 
57
- resource_definition.traits.each do |trait|
56
+ endpoint_definition.traits.each do |trait|
58
57
  self.trait(trait)
59
58
  end
60
59
 
61
- resource_definition.action_defaults.apply!(self)
60
+ endpoint_definition.action_defaults.apply!(self)
62
61
 
63
62
  self.instance_eval(&block) if block_given?
64
63
  end
@@ -72,7 +71,6 @@ module Praxis
72
71
  trait.apply!(self)
73
72
  traits << trait_name
74
73
  end
75
- alias_method :use, :trait
76
74
 
77
75
  def update_attribute(attribute, options, block)
78
76
  attribute.options.merge!(options)
@@ -179,12 +177,7 @@ module Praxis
179
177
  def routing(&block)
180
178
  @routing_config.instance_eval &block
181
179
 
182
- @routes = @routing_config.routes
183
- @primary_route = @routing_config.routes.first
184
- @named_routes = @routing_config.routes.each_with_object({}) do |route, hash|
185
- next if route.name.nil?
186
- hash[route.name] = route
187
- end
180
+ @route = @routing_config.route
188
181
  end
189
182
 
190
183
 
@@ -232,9 +225,8 @@ module Praxis
232
225
  end
233
226
  hash[:traits] = traits if traits.any?
234
227
  # FIXME: change to :routes along with api browser
235
- hash[:urls] = routes.collect do |route|
236
- ActionDefinition.url_description(route: route, params: self.params, params_example: params_example)
237
- end.compact
228
+ # FIXME: change urls to url ... (along with the browser)
229
+ hash[:urls] = [ ActionDefinition.url_description(route: route, params: self.params, params_example: params_example) ]
238
230
  self.class.doc_decorations.each do |callback|
239
231
  callback.call(self, hash)
240
232
  end
@@ -252,10 +244,10 @@ module Praxis
252
244
 
253
245
  def params_description(example:)
254
246
  route_params = []
255
- if primary_route.nil?
256
- warn "Warning: No routes defined for #{resource_definition.name}##{name}."
247
+ if route.nil?
248
+ warn "Warning: No route defined for #{endpoint_definition.name}##{name}."
257
249
  else
258
- route_params = primary_route.path.
250
+ route_params = route.path.
259
251
  named_captures.
260
252
  keys.
261
253
  collect(&:to_sym)
@@ -350,6 +342,9 @@ module Praxis
350
342
  metadata[:doc_visibility] = :none
351
343
  end
352
344
 
353
-
345
+ # [DEPRECATED] - Warn of the change of method name for the transition
346
+ def resource_definition
347
+ raise "Praxis::ActionDefinition does not use `resource_definition` any longer. Use `endpoint_definition` instead."
348
+ end
354
349
  end
355
350
  end
@@ -27,13 +27,6 @@ module Praxis
27
27
  end
28
28
  key name , String, **options
29
29
  end
30
-
31
- # Override the attribute to really call "key" in the hash (for temporary backwards compat)
32
- def attribute(name, attr_type=nil, **opts, &block)
33
- warn "[DEPRECATION] `attribute` is deprecated when defining headers. Please use `key` instead."
34
- key(name, attr_type, **opts, &block)
35
- end
36
-
37
30
  end
38
31
  end
39
32
  end
@@ -122,7 +122,7 @@ module Praxis
122
122
  global_path = @global_info.base_path
123
123
  if version_with == :path
124
124
  global_pattern = Mustermann.new(global_path)
125
- global_path = global_pattern.expand(Request::API_VERSION_PARAM_NAME => self.version)
125
+ global_path = global_pattern.expand(Request::API_VERSION_PARAM_NAME => self.version.to_s)
126
126
  end
127
127
 
128
128
  version_path = @data.fetch(:base_path,'')
@@ -8,7 +8,7 @@ module Praxis
8
8
 
9
9
  attr_reader :router
10
10
  attr_reader :controllers
11
- attr_reader :resource_definitions
11
+ attr_reader :endpoint_definitions
12
12
  attr_reader :app
13
13
  attr_reader :builder
14
14
 
@@ -32,7 +32,7 @@ module Praxis
32
32
 
33
33
  def initialize
34
34
  @controllers = Set.new
35
- @resource_definitions = Set.new
35
+ @endpoint_definitions = Set.new
36
36
 
37
37
  @error_handler = ErrorHandler.new
38
38
  @validation_handler = ValidationHandler.new
@@ -61,8 +61,7 @@ module Praxis
61
61
 
62
62
  builtin_handlers = {
63
63
  'plain' => Praxis::Handlers::Plain,
64
- 'json' => Praxis::Handlers::JSON,
65
- 'x-www-form-urlencoded' => Praxis::Handlers::WWWForm
64
+ 'json' => Praxis::Handlers::JSON
66
65
  }
67
66
  # Register built-in handlers unless the app already provided its own
68
67
  builtin_handlers.each_pair do |name, handler|
@@ -124,5 +123,9 @@ module Praxis
124
123
  @config.set(config)
125
124
  end
126
125
 
126
+ # [DEPRECATED] - Warn of the change of method name for the transition
127
+ def resource_definitions
128
+ raise "Praxis::Application.instance does not use `resource_definitions` any longer. Use `endpoint_definitions` instead."
129
+ end
127
130
  end
128
131
  end
@@ -0,0 +1,357 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Praxis
4
+ class Blueprint
5
+
6
+ # Simple helper class that can parse the `attribute :foobar` dsl into
7
+ # an equivalent structure hash. Example:
8
+ # do
9
+ # attribute :one
10
+ # attribute :complex do
11
+ # attribute :sub1
12
+ # end
13
+ # end
14
+ # is parsed as: { one: true, complex: { sub1: true} }
15
+ class FieldsetParser
16
+ def initialize( &block)
17
+ @hash = nil
18
+ @block = block
19
+ end
20
+
21
+ def attribute(name, **args, &block)
22
+ raise "Default fieldset definitions do not accept parameters (got: #{args})" \
23
+ "If you're upgrading from a previous version of Praxis and still using the view :default " \
24
+ "block syntax, make sure you don't use any view: X parameters when you define the attributes " \
25
+ "(expand them explicitly if you want deeper structure)" \
26
+ "The offending view with parameters is defined in:\n#{Kernel.caller.first}" unless args.empty?
27
+ @hash[name] = block_given? ? FieldsetParser.new(&block).fieldset : true
28
+ end
29
+
30
+ def fieldset
31
+ return @hash if @hash
32
+ # Lazy eval
33
+ @hash = {}
34
+ instance_eval(&@block)
35
+ @hash
36
+ end
37
+ end
38
+ include Attributor::Type
39
+ include Attributor::Dumpable
40
+
41
+ extend Finalizable
42
+
43
+ @@caching_enabled = false
44
+
45
+ attr_reader :validating
46
+ attr_accessor :object
47
+
48
+ class << self
49
+ attr_reader :attribute
50
+ attr_reader :options
51
+ attr_accessor :reference
52
+ end
53
+
54
+ def self.inherited(klass)
55
+ super
56
+
57
+ klass.instance_eval do
58
+ @options = {}
59
+ @domain_model = Object
60
+ @default_fieldset = {}
61
+ end
62
+ end
63
+
64
+ # Override default new behavior to support memoized creation through an IdentityMap
65
+ def self.new(object)
66
+ # TODO: do we want to allow the identity map thing in the object?...maybe not.
67
+ if @@caching_enabled
68
+ return self.cache[object] ||= begin
69
+ blueprint = self.allocate
70
+ blueprint.send(:initialize, object)
71
+ blueprint
72
+ end
73
+ end
74
+
75
+ blueprint = self.allocate
76
+ blueprint.send(:initialize, object)
77
+ blueprint
78
+ end
79
+
80
+ def self.family
81
+ 'hash'
82
+ end
83
+
84
+ def self.attributes(opts = {}, &block)
85
+ if block_given?
86
+ raise 'Redefining Blueprint attributes is not currently supported' if self.const_defined?(:Struct, false)
87
+
88
+ if opts.key?(:reference) && opts[:reference] != self.reference
89
+ raise "Reference mismatch in #{self.inspect}. Given :reference option #{opts[:reference].inspect}, while using #{self.reference.inspect}"
90
+ elsif self.reference
91
+ opts[:reference] = self.reference # pass the reference Class down
92
+ else
93
+ opts[:reference] = self
94
+ end
95
+
96
+ @options.merge!(opts)
97
+ @block = block
98
+
99
+ return @attribute
100
+ end
101
+
102
+ raise "@attribute not defined yet for #{self.name}" unless @attribute
103
+
104
+ @attribute.attributes
105
+ end
106
+
107
+ def self.domain_model(klass = nil)
108
+ return @domain_model if klass.nil?
109
+ @domain_model = klass
110
+ end
111
+
112
+ def self.check_option!(name, value)
113
+ Attributor::Struct.check_option!(name, value)
114
+ end
115
+
116
+ def self.load(value, context = Attributor::DEFAULT_ROOT_CONTEXT, **options)
117
+ case value
118
+ when self
119
+ value
120
+ when nil, Hash, String
121
+ if (value = self.attribute.load(value, context, **options))
122
+ self.new(value)
123
+ end
124
+ else
125
+ if value.is_a?(self.domain_model) || value.is_a?(self::Struct)
126
+ # Wrap the value directly
127
+ self.new(value)
128
+ else
129
+ # Wrap the object inside the domain_model
130
+ self.new(domain_model.new(value))
131
+ end
132
+ end
133
+ end
134
+
135
+ class << self
136
+ alias from load
137
+ end
138
+
139
+ def self.caching_enabled?
140
+ @@caching_enabled
141
+ end
142
+
143
+ def self.caching_enabled=(caching_enabled)
144
+ @@caching_enabled = caching_enabled
145
+ end
146
+
147
+ # Fetch current blueprint cache, scoped by this class
148
+ def self.cache
149
+ Thread.current[:praxis_blueprints_cache][self]
150
+ end
151
+
152
+ def self.cache=(cache)
153
+ Thread.current[:praxis_blueprints_cache] = cache
154
+ end
155
+
156
+ def self.valid_type?(value)
157
+ value.is_a?(self) || value.is_a?(self.attribute.type)
158
+ end
159
+
160
+ def self.example(context = nil, **values)
161
+ context = case context
162
+ when nil
163
+ ["#{self.name}-#{values.object_id}"]
164
+ when ::String
165
+ [context]
166
+ else
167
+ context
168
+ end
169
+
170
+ self.new(self.attribute.example(context, values: values))
171
+ end
172
+
173
+ def self.validate(value, context = Attributor::DEFAULT_ROOT_CONTEXT, _attribute = nil)
174
+ raise ArgumentError, "Invalid context received (nil) while validating value of type #{self.name}" if context.nil?
175
+ context = [context] if context.is_a? ::String
176
+
177
+ unless value.is_a?(self)
178
+ raise ArgumentError, "Error validating #{Attributor.humanize_context(context)} as #{self.name} for an object of type #{value.class.name}."
179
+ end
180
+
181
+ value.validate(context)
182
+ end
183
+
184
+ def self.default_fieldset(&block)
185
+ return @default_fieldset unless block_given?
186
+
187
+ @block_for_default_fieldset = block
188
+ end
189
+
190
+ def self.view(name, **options, &block)
191
+ unless name == :default
192
+ raise "[ERROR] Views are no longer supported. Please use fully expanded fields when rendering.\n" \
193
+ "NOTE that defining the :default view is deprecated, but still temporarily allowed, as an alias to define the default_fieldset.\n" \
194
+ "A view for name #{name} is attempted to be defined in:\n#{Kernel.caller.first}"
195
+ end
196
+ raise "Cannot define the default fieldset through the default view unless a block is passed" unless block_given?
197
+ puts "[DEPRECATED] default fieldsets should be defined through `default_fieldset` instead of using the view :default block.\n" \
198
+ "A default view is attempted to be defined in:\n#{Kernel.caller.first}"
199
+ default_fieldset(&block)
200
+ end
201
+
202
+ def self.parse_default_fieldset(block)
203
+ @default_fieldset = FieldsetParser.new(&block).fieldset
204
+ @block_for_default_fieldset = nil
205
+ end
206
+
207
+ # renders using the implicit default fieldset
208
+ def self.dump(object, context: Attributor::DEFAULT_ROOT_CONTEXT, **opts)
209
+ object = self.load(object, context, **opts)
210
+ return nil if object.nil?
211
+
212
+ object.render(context: context, **opts)
213
+ end
214
+
215
+ class << self
216
+ alias render dump
217
+ end
218
+
219
+ # Internal finalize! logic
220
+ def self._finalize!
221
+ if @block
222
+ self.define_attribute!
223
+ self.define_readers!
224
+ # Don't blindly override a the default fieldset if the MediaType wants to define it on its own
225
+ if @block_for_default_fieldset
226
+ parse_default_fieldset(@block_for_default_fieldset)
227
+ else
228
+ self.generate_default_fieldset!
229
+ end
230
+ self.resolve_domain_model!
231
+ end
232
+ super
233
+ end
234
+
235
+ def self.resolve_domain_model!
236
+ return unless self.domain_model.is_a?(String)
237
+
238
+ @domain_model = self.domain_model.constantize
239
+ end
240
+
241
+ def self.define_attribute!
242
+ @attribute = Attributor::Attribute.new(Attributor::Struct, @options, &@block)
243
+ @block = nil
244
+ @attribute.type.anonymous_type true
245
+ self.const_set(:Struct, @attribute.type)
246
+ end
247
+
248
+ def self.define_readers!
249
+ self.attributes.each do |name, _attribute|
250
+ name = name.to_sym
251
+
252
+ # Don't redefine existing methods
253
+ next if self.instance_methods.include? name
254
+
255
+ define_reader! name
256
+ end
257
+ end
258
+
259
+ def self.define_reader!(name)
260
+ attribute = self.attributes[name]
261
+ # TODO: profile and optimize
262
+ # because we use the attribute in the reader,
263
+ # it's likely faster to use define_method here
264
+ # than module_eval, but we should make sure.
265
+ define_method(name) do
266
+ value = @object.__send__(name)
267
+ return value if value.nil? || value.is_a?(attribute.type)
268
+ attribute.load(value)
269
+ end
270
+ end
271
+
272
+ def self.generate_default_fieldset!
273
+ attributes = self.attributes
274
+
275
+ @default_fieldset = {}
276
+ attributes.each do |name, attr|
277
+ the_type = (attr.type < Attributor::Collection) ? attr.type.member_type : attr.type
278
+ next if the_type < Blueprint
279
+ # Note: we won't try to expand fields here, as we want to be lazy (and we're expanding)
280
+ # every time a request comes in anyway. This could be an optimization we do at some point
281
+ # or we can 'memoize it' to avoid trying to expand it over an over...
282
+ @default_fieldset[name] = true
283
+ end
284
+ end
285
+
286
+ def initialize(object)
287
+ @object = object
288
+ @validating = false
289
+ end
290
+
291
+ # By default we'll use the object identity, to avoid rendering the same object twice
292
+ # Override, if there is a better way cache things up
293
+ def _cache_key
294
+ self.object
295
+ end
296
+
297
+ # Render the wrapped data with the given fields (or using the default fieldset otherwise)
298
+ def render(fields: self.class.default_fieldset, context: Attributor::DEFAULT_ROOT_CONTEXT, renderer: Renderer.new, **opts)
299
+
300
+ # Accept a simple array of fields, and transform it to a 1-level hash with true values
301
+ if fields.is_a? Array
302
+ fields = fields.each_with_object({}) { |field, hash| hash[field] = true }
303
+ end
304
+
305
+ expanded = Praxis::FieldExpander.new.expand(self, fields)
306
+ renderer.render(self, fields, context: context)
307
+ end
308
+
309
+ alias dump render
310
+
311
+ def to_h
312
+ Attributor.recursive_to_h(@object)
313
+ end
314
+
315
+ def validate(context = Attributor::DEFAULT_ROOT_CONTEXT)
316
+ raise ArgumentError, "Invalid context received (nil) while validating value of type #{self.name}" if context.nil?
317
+ context = [context] if context.is_a? ::String
318
+ keys_with_values = []
319
+
320
+ raise 'validation conflict' if @validating
321
+ @validating = true
322
+
323
+ errors = []
324
+ self.class.attributes.each do |sub_attribute_name, sub_attribute|
325
+ sub_context = self.class.generate_subcontext(context, sub_attribute_name)
326
+ value = self.send(sub_attribute_name)
327
+ keys_with_values << sub_attribute_name unless value.nil?
328
+
329
+ if value.respond_to?(:validating) # really, it's a thing with sub-attributes
330
+ next if value.validating
331
+ end
332
+ errors.concat(sub_attribute.validate(value, sub_context))
333
+ end
334
+ self.class.attribute.type.requirements.each do |req|
335
+ validation_errors = req.validate(keys_with_values, context)
336
+ errors.concat(validation_errors) unless validation_errors.empty?
337
+ end
338
+ errors
339
+ ensure
340
+ @validating = false
341
+ end
342
+
343
+ # generic semi-private getter used by Renderer
344
+ def _get_attr(name)
345
+ self.send(name)
346
+ end
347
+
348
+ # Delegates the json-schema methods to the underlying attribute/member_type
349
+ def self.as_json_schema(**args)
350
+ @attribute.type.as_json_schema(args)
351
+ end
352
+
353
+ def self.json_schema_type
354
+ @attribute.type.json_schema_type
355
+ end
356
+ end
357
+ end