praxis 2.0.pre.7 → 2.0.pre.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.ruby-version +1 -1
- data/.travis.yml +1 -3
- data/CHANGELOG.md +29 -0
- data/TODO.md +1 -4
- data/bin/praxis +67 -12
- data/lib/praxis.rb +10 -3
- data/lib/praxis/action_definition.rb +15 -13
- data/lib/praxis/action_definition/headers_dsl_compiler.rb +0 -7
- data/lib/praxis/api_general_info.rb +1 -1
- data/lib/praxis/application.rb +6 -2
- data/lib/praxis/blueprint.rb +357 -0
- data/lib/praxis/bootloader.rb +9 -3
- data/lib/praxis/bootloader_stages/environment.rb +16 -13
- data/lib/praxis/collection.rb +1 -11
- data/lib/praxis/config_hash.rb +44 -0
- data/lib/praxis/docs/{openapi → open_api}/info_object.rb +18 -10
- data/lib/praxis/docs/{openapi → open_api}/media_type_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/operation_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/parameter_object.rb +2 -2
- data/lib/praxis/docs/{openapi → open_api}/paths_object.rb +12 -15
- data/lib/praxis/docs/{openapi → open_api}/request_body_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/response_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/responses_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/schema_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/server_object.rb +0 -0
- data/lib/praxis/docs/{openapi → open_api}/tag_object.rb +0 -0
- data/lib/praxis/docs/open_api_generator.rb +91 -6
- data/lib/praxis/endpoint_definition.rb +273 -0
- data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +182 -58
- data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +3 -2
- data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +46 -56
- data/lib/praxis/extensions/attribute_filtering/filters_parser.rb +153 -0
- data/lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb +20 -8
- data/lib/praxis/extensions/field_expansion.rb +3 -36
- data/lib/praxis/extensions/pagination.rb +5 -32
- data/lib/praxis/extensions/pagination/ordering_params.rb +5 -1
- data/lib/praxis/extensions/pagination/pagination_params.rb +10 -4
- data/lib/praxis/field_expander.rb +90 -0
- data/lib/praxis/finalizable.rb +34 -0
- data/lib/praxis/mapper/active_model_compat.rb +4 -0
- data/lib/praxis/mapper/resource.rb +18 -2
- data/lib/praxis/mapper/selector_generator.rb +2 -1
- data/lib/praxis/mapper/sequel_compat.rb +7 -0
- data/lib/praxis/media_type.rb +3 -68
- data/lib/praxis/plugin_concern.rb +1 -1
- data/lib/praxis/plugins/mapper_plugin.rb +24 -15
- data/lib/praxis/plugins/pagination_plugin.rb +34 -4
- data/lib/praxis/renderer.rb +88 -0
- data/lib/praxis/request.rb +1 -1
- data/lib/praxis/resource_definition.rb +2 -311
- data/lib/praxis/response_definition.rb +2 -10
- data/lib/praxis/response_template.rb +3 -3
- data/lib/praxis/router.rb +2 -2
- data/lib/praxis/routing_config.rb +1 -1
- data/lib/praxis/tasks/api_docs.rb +17 -64
- data/lib/praxis/tasks/routes.rb +1 -1
- data/lib/praxis/types/media_type_common.rb +1 -11
- data/lib/praxis/version.rb +1 -1
- data/praxis.gemspec +0 -1
- data/spec/functional_spec.rb +5 -9
- data/spec/praxis/action_definition_spec.rb +12 -20
- data/spec/praxis/blueprint_spec.rb +373 -0
- data/spec/praxis/bootloader_spec.rb +10 -2
- data/spec/praxis/collection_spec.rb +0 -13
- data/spec/praxis/config_hash_spec.rb +64 -0
- data/spec/praxis/{resource_definition_spec.rb → endpoint_definition_spec.rb} +37 -64
- data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +249 -168
- data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +25 -6
- data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +175 -8
- data/spec/praxis/extensions/attribute_filtering/filters_parser_spec.rb +131 -0
- data/spec/praxis/extensions/field_expansion_spec.rb +5 -24
- data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +1 -1
- data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +1 -1
- data/spec/praxis/extensions/support/spec_resources_active_model.rb +1 -1
- data/spec/praxis/field_expander_spec.rb +149 -0
- data/spec/praxis/mapper/selector_generator_spec.rb +1 -1
- data/spec/praxis/media_type_identifier_spec.rb +5 -4
- data/spec/praxis/media_type_spec.rb +4 -93
- data/spec/praxis/renderer_spec.rb +188 -0
- data/spec/praxis/response_definition_spec.rb +0 -31
- data/spec/praxis/response_spec.rb +1 -1
- data/spec/praxis/router_spec.rb +8 -8
- data/spec/praxis/routing_config_spec.rb +3 -3
- data/spec/spec_app/app/controllers/instances.rb +13 -7
- data/spec/spec_app/design/media_types/instance.rb +1 -19
- data/spec/spec_app/design/media_types/volume.rb +1 -1
- data/spec/spec_app/design/media_types/volume_snapshot.rb +2 -14
- data/spec/spec_app/design/resources/instances.rb +5 -8
- data/spec/spec_app/design/resources/volume_snapshots.rb +1 -1
- data/spec/spec_app/design/resources/volumes.rb +1 -1
- data/spec/support/spec_authorization_plugin.rb +1 -1
- data/spec/support/spec_blueprints.rb +72 -0
- data/spec/support/{spec_resource_definitions.rb → spec_endpoint_definitions.rb} +2 -2
- data/spec/support/spec_media_types.rb +6 -26
- data/tasks/thor/app.rb +8 -34
- data/tasks/thor/example.rb +51 -285
- data/tasks/thor/model.rb +40 -0
- data/tasks/thor/scaffold.rb +117 -0
- data/tasks/thor/templates/generator/empty_app/.gitignore +0 -1
- data/tasks/thor/templates/generator/empty_app/Gemfile +7 -23
- data/tasks/thor/templates/generator/empty_app/README.md +1 -1
- data/tasks/thor/templates/generator/empty_app/Rakefile +4 -13
- data/tasks/thor/templates/generator/empty_app/{design/response_templates → app/v1/resources}/.empty_directory +0 -0
- data/tasks/thor/templates/generator/empty_app/{design/response_templates → app/v1/resources}/.gitkeep +0 -0
- data/tasks/thor/templates/generator/empty_app/config/environment.rb +26 -17
- data/tasks/thor/templates/generator/empty_app/{design/v1/resources → config/initializers}/.empty_directory +0 -0
- data/tasks/thor/templates/generator/empty_app/{design/v1/resources → config/initializers}/.gitkeep +0 -0
- data/tasks/thor/templates/generator/empty_app/design/v1/endpoints/.empty_directory +0 -0
- data/tasks/thor/templates/generator/empty_app/design/v1/endpoints/.gitkeep +0 -0
- data/tasks/thor/templates/generator/empty_app/docs/.empty_directory +0 -0
- data/tasks/thor/templates/generator/empty_app/docs/.gitkeep +0 -0
- data/tasks/thor/templates/generator/empty_app/spec/spec_helper.rb +14 -9
- data/tasks/thor/templates/generator/example_app/.gitignore +1 -0
- data/tasks/thor/templates/generator/example_app/Gemfile +19 -0
- data/tasks/thor/templates/generator/example_app/Rakefile +61 -0
- data/tasks/thor/templates/generator/example_app/app/models/user.rb +6 -0
- data/tasks/thor/templates/generator/example_app/app/v1/concerns/controller_base.rb +24 -0
- data/tasks/thor/templates/generator/example_app/app/v1/controllers/users.rb +17 -0
- data/tasks/thor/templates/generator/example_app/app/v1/resources/base.rb +11 -0
- data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +25 -0
- data/tasks/thor/templates/generator/example_app/config.ru +30 -0
- data/tasks/thor/templates/generator/example_app/config/environment.rb +41 -0
- data/tasks/thor/templates/generator/example_app/db/migrate/20201010101010_create_users_table.rb +12 -0
- data/tasks/thor/templates/generator/example_app/db/seeds.rb +6 -0
- data/tasks/thor/templates/generator/example_app/design/api.rb +18 -0
- data/tasks/thor/templates/generator/example_app/design/v1/endpoints/users.rb +37 -0
- data/tasks/thor/templates/generator/example_app/design/v1/media_types/user.rb +21 -0
- data/tasks/thor/templates/generator/example_app/spec/helpers/database_helper.rb +20 -0
- data/tasks/thor/templates/generator/example_app/spec/spec_helper.rb +42 -0
- data/tasks/thor/templates/generator/example_app/spec/v1/controllers/users_spec.rb +37 -0
- data/tasks/thor/templates/generator/scaffold/design/endpoints/collection.rb +98 -0
- data/tasks/thor/templates/generator/scaffold/design/media_types/item.rb +18 -0
- data/tasks/thor/templates/generator/scaffold/implementation/controllers/collection.rb +77 -0
- data/tasks/thor/templates/generator/scaffold/implementation/resources/base.rb +11 -0
- data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +45 -0
- data/tasks/thor/templates/generator/scaffold/models/active_record.rb +6 -0
- data/tasks/thor/templates/generator/scaffold/models/sequel.rb +6 -0
- metadata +64 -136
- data/lib/api_browser/.bowerrc +0 -3
- data/lib/api_browser/.editorconfig +0 -21
- data/lib/api_browser/Gruntfile.js +0 -581
- data/lib/api_browser/app/index.html +0 -59
- data/lib/api_browser/app/js/app.js +0 -48
- data/lib/api_browser/app/js/controllers/action.js +0 -47
- data/lib/api_browser/app/js/controllers/controller.js +0 -10
- data/lib/api_browser/app/js/controllers/menu.js +0 -93
- data/lib/api_browser/app/js/controllers/trait.js +0 -10
- data/lib/api_browser/app/js/controllers/type.js +0 -24
- data/lib/api_browser/app/js/directives/attribute_description.js +0 -56
- data/lib/api_browser/app/js/directives/attribute_table.js +0 -28
- data/lib/api_browser/app/js/directives/conditional_requirements.js +0 -13
- data/lib/api_browser/app/js/directives/fixed_if_fits.js +0 -38
- data/lib/api_browser/app/js/directives/highlight.js +0 -14
- data/lib/api_browser/app/js/directives/menu_item.js +0 -59
- data/lib/api_browser/app/js/directives/no_container.js +0 -8
- data/lib/api_browser/app/js/directives/readable_list.js +0 -87
- data/lib/api_browser/app/js/directives/request_examples.js +0 -31
- data/lib/api_browser/app/js/directives/type_placeholder.js +0 -30
- data/lib/api_browser/app/js/directives/url.js +0 -15
- data/lib/api_browser/app/js/factories/Configuration.js +0 -12
- data/lib/api_browser/app/js/factories/Documentation.js +0 -61
- data/lib/api_browser/app/js/factories/Example.js +0 -51
- data/lib/api_browser/app/js/factories/PageInfo.js +0 -9
- data/lib/api_browser/app/js/factories/normalize_attributes.js +0 -20
- data/lib/api_browser/app/js/factories/prepare_template.js +0 -15
- data/lib/api_browser/app/js/factories/template_for.js +0 -128
- data/lib/api_browser/app/js/filters/attribute_name.js +0 -10
- data/lib/api_browser/app/js/filters/friendly_json.js +0 -5
- data/lib/api_browser/app/js/filters/has_requirement.js +0 -14
- data/lib/api_browser/app/js/filters/header_info.js +0 -9
- data/lib/api_browser/app/js/filters/is_empty.js +0 -8
- data/lib/api_browser/app/js/filters/markdown.js +0 -6
- data/lib/api_browser/app/js/filters/resource_name.js +0 -5
- data/lib/api_browser/app/js/filters/tag_requirement.js +0 -13
- data/lib/api_browser/app/sass/modules/_body.scss +0 -40
- data/lib/api_browser/app/sass/modules/_cloke.scss +0 -8
- data/lib/api_browser/app/sass/modules/_header.scss +0 -10
- data/lib/api_browser/app/sass/modules/_nav.scss +0 -7
- data/lib/api_browser/app/sass/modules/_sidebar.scss +0 -134
- data/lib/api_browser/app/sass/modules/_switch.scss +0 -55
- data/lib/api_browser/app/sass/modules/_table.scss +0 -13
- data/lib/api_browser/app/sass/praxis.scss +0 -70
- data/lib/api_browser/app/sass/variables/_bootstrap-variables.scss +0 -774
- data/lib/api_browser/app/views/action.html +0 -97
- data/lib/api_browser/app/views/builtin/field-selector.html +0 -24
- data/lib/api_browser/app/views/controller.html +0 -55
- data/lib/api_browser/app/views/directives/attribute_description.html +0 -2
- data/lib/api_browser/app/views/directives/attribute_description/default.html +0 -2
- data/lib/api_browser/app/views/directives/attribute_description/example.html +0 -13
- data/lib/api_browser/app/views/directives/attribute_description/headers.html +0 -8
- data/lib/api_browser/app/views/directives/attribute_description/member_options.html +0 -4
- data/lib/api_browser/app/views/directives/attribute_description/values.html +0 -14
- data/lib/api_browser/app/views/directives/attribute_table.html +0 -17
- data/lib/api_browser/app/views/directives/menu_item.html +0 -8
- data/lib/api_browser/app/views/directives/url.html +0 -3
- data/lib/api_browser/app/views/examples/general.html +0 -26
- data/lib/api_browser/app/views/home.html +0 -5
- data/lib/api_browser/app/views/layout.html +0 -8
- data/lib/api_browser/app/views/menu.html +0 -42
- data/lib/api_browser/app/views/navbar.html +0 -9
- data/lib/api_browser/app/views/trait.html +0 -13
- data/lib/api_browser/app/views/type.html +0 -6
- data/lib/api_browser/app/views/type/details.html +0 -33
- data/lib/api_browser/app/views/types/embedded/array.html +0 -2
- data/lib/api_browser/app/views/types/embedded/default.html +0 -12
- data/lib/api_browser/app/views/types/embedded/field-selector.html +0 -13
- data/lib/api_browser/app/views/types/embedded/links.html +0 -11
- data/lib/api_browser/app/views/types/embedded/requirements.html +0 -6
- data/lib/api_browser/app/views/types/embedded/single_req.html +0 -9
- data/lib/api_browser/app/views/types/embedded/struct.html +0 -14
- data/lib/api_browser/app/views/types/label/link.html +0 -1
- data/lib/api_browser/app/views/types/label/primitive.html +0 -1
- data/lib/api_browser/app/views/types/label/primitive_collection.html +0 -1
- data/lib/api_browser/app/views/types/label/type.html +0 -1
- data/lib/api_browser/app/views/types/label/type_collection.html +0 -1
- data/lib/api_browser/app/views/types/main/array.html +0 -22
- data/lib/api_browser/app/views/types/main/default.html +0 -23
- data/lib/api_browser/app/views/types/main/hash.html +0 -23
- data/lib/api_browser/app/views/types/standalone/array.html +0 -3
- data/lib/api_browser/app/views/types/standalone/default.html +0 -18
- data/lib/api_browser/app/views/types/standalone/struct.html +0 -2
- data/lib/api_browser/bower_template.json +0 -41
- data/lib/api_browser/package-lock.json +0 -7110
- data/lib/api_browser/package.json +0 -43
- data/lib/praxis/docs/generator.rb +0 -243
- data/lib/praxis/docs/link_builder.rb +0 -30
- data/lib/praxis/links.rb +0 -135
- data/lib/praxis/types/multipart.rb +0 -109
- data/spec/api_browser/directives/type_placeholder_spec.js +0 -134
- data/spec/api_browser/factories/configuration_spec.js +0 -32
- data/spec/api_browser/factories/documentation_spec.js +0 -100
- data/spec/api_browser/factories/normalize_attributes_spec.js +0 -92
- data/spec/api_browser/factories/template_for_spec.js +0 -67
- data/spec/api_browser/filters/attribute_name_spec.js +0 -23
- data/spec/praxis/types/multipart_spec.rb +0 -112
- data/tasks/thor/templates/generator/empty_app/.rspec +0 -1
- data/tasks/thor/templates/generator/empty_app/Guardfile +0 -3
- data/tasks/thor/templates/generator/empty_app/config/rainbows.rb +0 -57
- data/tasks/thor/templates/generator/empty_app/docs/app.js +0 -1
- data/tasks/thor/templates/generator/empty_app/docs/styles.scss +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf4b54b686373509e0920a93d482e29f4c198491f88d382b6ff429e5128d6574
|
4
|
+
data.tar.gz: '08b2d7342069e8584676e03c828e144b99eb9282b193113b987d8060cb143cfd'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ef7cd7fe985d4af76cf7ec6459e58990b8dbc6f3c4fac5c26ce04d56e23a3d743a0f4bb776f4d5c913224e031567e9993ad52e611e4258ee853cdf86b3099255
|
7
|
+
data.tar.gz: f4fe45f97c9bc26aa513a24ee358b04dc114af0e7f4849071f699c0c22df460253452dd54e9bcb510fc4f977aa9104774f70c882f46b8aa937404e79814d6f3d
|
data/.gitignore
CHANGED
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.7.1
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,35 @@
|
|
2
2
|
|
3
3
|
## next
|
4
4
|
|
5
|
+
## 2.0.pre.12
|
6
|
+
|
7
|
+
* Rebuilt API filters to support a much richer syntax. One can now use ANDs and ORs (with ANDs having order precedence), as well as group them with parenthesis. The same individual filter operands are supported. For example: 'email=*@gmail.com&(friends.first_name=Joe*,Patty|friends.last_name=Smith)
|
8
|
+
|
9
|
+
## 2.0.pre.11
|
10
|
+
|
11
|
+
- Remove MapperPlugin's `set_selectors` (made `selector_generator` lazy instead), and ensure it includes the rendering extensions to the Controllers. Less things to configure if you opt into the Mapper way.
|
12
|
+
- Built scaffolding generator for quickly creating a new API endpoint in the praxis binary (it builds endpoint+mediatype+controller+resource at one, with useful base code and comments)
|
13
|
+
- Dropped support for Ruby 2.4 and 2.5 as some of the newest dependent gems are dropping it as well.
|
14
|
+
- Simplify filters_mapping definition, by not requiring to define same-name mappings if the underlying model has an attribute with the same exact name. i.e., a `name: :name` entry is not necessary if the model has a `:name` attribute.
|
15
|
+
|
16
|
+
## 2.0.pre.10
|
17
|
+
|
18
|
+
- Simple, but pervasive breaking change: Rename `ResourceDefinition` to `EndpointDefinition` (but same functionality).
|
19
|
+
- Remove all deprecated features (and raise error describing it's not supported yet)
|
20
|
+
- Remove `Links` and `LinkBuilder`. Those seem unnecessary from a Framework point of view as they aren't clear most
|
21
|
+
applications would benefit from it. Applications can choose to add that functionality on their own if so desire.
|
22
|
+
- Rebuilt app generators: for new empty app, and example app.
|
23
|
+
- Updated default layout to match new naming structure and more concepts commonly necessary for normal applications.
|
24
|
+
- Completely removed the native Praxis API documentation browser in lieu of OpenAPI 3.x standards, and reDoc.
|
25
|
+
- Remove dependency from praxis-blueprints, as simplified subset of its code has now been included in this repo:
|
26
|
+
- 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.
|
27
|
+
- 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.
|
28
|
+
|
29
|
+
## 2.0.pre.9
|
30
|
+
|
31
|
+
- Refined OpenAPI doc generation to output only non-null attributes in the InfoObject.
|
32
|
+
- Fixed filtering params validation to properly allow null values for the "!" and "!!" operators
|
33
|
+
|
5
34
|
## 2.0.pre.6
|
6
35
|
|
7
36
|
- 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.
|
data/TODO.md
CHANGED
@@ -3,17 +3,14 @@
|
|
3
3
|
## Things to delete
|
4
4
|
|
5
5
|
* views (just use a sensible default view of just simple types?)
|
6
|
-
* make handlers hang from a singleton (and possibly get rid of xml encoding as well)
|
7
|
-
* remove app instances?
|
8
6
|
* remove collection summary things...
|
9
7
|
* FieldResolver?? and that conditional dependency thing...?
|
10
8
|
* simplify or change examples? ...maybe get rid of randexp.
|
11
|
-
* get rid of doc browser in lieu to OAPI and redoc
|
12
|
-
* traits? ... we still use them...
|
13
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)
|
14
10
|
* Plugins? ... maybe leave them out for the moment?
|
15
11
|
* change errors to be machine readable
|
16
12
|
* change naming of resource definition to endpoint definition
|
13
|
+
* get rid of deprecations
|
17
14
|
|
18
15
|
|
19
16
|
## DONE
|
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:
|
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
|
@@ -46,7 +46,7 @@ class PraxisGenerator < Thor
|
|
46
46
|
def routes
|
47
47
|
end
|
48
48
|
|
49
|
-
desc "docs [generate|browser|package]",
|
49
|
+
desc "docs [generate|browser|package]", <<~EOF
|
50
50
|
Generates API documentation and a Web App to inspect it
|
51
51
|
generate - Generates the JSON docs
|
52
52
|
browser - (default) Generates JSON docs, and automatically starts a Web app to browse them.
|
@@ -71,19 +71,74 @@ class PraxisGenerator < Thor
|
|
71
71
|
|
72
72
|
desc_for "new APP_NAME", ::PraxisGen::App, :new
|
73
73
|
def new(app_name)
|
74
|
-
::PraxisGen::App.
|
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, :
|
79
|
+
desc_for "example APP_NAME", ::PraxisGen::Example, :example
|
78
80
|
def example(app_name)
|
79
|
-
::PraxisGen::Example.
|
81
|
+
gen = ::PraxisGen::Example.new([app_name])
|
82
|
+
gen.destination_root = app_name
|
83
|
+
gen.invoke(:example)
|
80
84
|
end
|
81
|
-
|
82
|
-
desc_for "
|
83
|
-
|
84
|
-
|
85
|
+
|
86
|
+
desc_for "g COLLECTION_NAME", ::PraxisGen::Scaffold, :g
|
87
|
+
# Cannot use the argument below or it will apply to all commands (the action in the class has it)
|
88
|
+
# argument :collection_name, required: false
|
89
|
+
# The options, however, since they're optional are fine (But need to be duplicated from the class :( )
|
90
|
+
option :version, required: false, default: '1',
|
91
|
+
desc: 'Version string for the API endpoint. This also dictates the directory structure (i.e., v1/endpoints/...))'
|
92
|
+
option :design, type: :boolean, default: true,
|
93
|
+
desc: 'Include the Endpoint and MediaType files for the collection'
|
94
|
+
option :implementation, type: :boolean, default: true,
|
95
|
+
desc: 'Include the Controller and (possibly the) Resource files for the collection (see --no-resource)'
|
96
|
+
option :resource, type: :boolean, default: true,
|
97
|
+
desc: 'Disable (or enable) the creation of the Resource files when generating implementation'
|
98
|
+
option :model, type: :string, enum: ['activerecord','sequel'],
|
99
|
+
desc: 'It also generates a model for the given ORM. An empty --model flag will default to activerecord'
|
100
|
+
option :actions, type: :string, default: 'crud', enum: ['cr','cru','crud','u','ud','d'],
|
101
|
+
desc: 'Specifies the actions to generate for the API. cr=create, u=update, d=delete. Index and show actions are always generated'
|
102
|
+
def g(*args)
|
103
|
+
# Because we cannot share the :collection_name argument, we need to do this check here, before
|
104
|
+
# we "parse" it and pass it to the g command
|
105
|
+
unless args.size == 1
|
106
|
+
::PraxisGen::Scaffold.command_help(shell,:g)
|
107
|
+
exit 1
|
108
|
+
end
|
109
|
+
|
110
|
+
collection_name,_ = args
|
111
|
+
::PraxisGen::Scaffold.new([collection_name],options).invoke(:g)
|
112
|
+
if options[:model]
|
113
|
+
# Make it easy to be able to both enable or not enable the creation of the model, by passing --model=...
|
114
|
+
# but also make it easy so that if there is no value for it, it default to activerecord
|
115
|
+
opts = {orm: options[:model] }
|
116
|
+
opts[:orm] = 'activerecord' if opts[:orm] == 'model' # value is model param passed by no value
|
117
|
+
::PraxisGen::Model.new([collection_name.singularize],opts).invoke(:g)
|
118
|
+
end
|
85
119
|
end
|
86
|
-
|
120
|
+
|
121
|
+
# Initially, the idea was to build some quick model generator, but I think it's better to keep it
|
122
|
+
# simple and just use the scaffold generator with `--no-implementation --no-design --model` instead
|
123
|
+
# Left here in case we want to rescue it
|
124
|
+
# desc_for "gmodel MODEL_NAME", ::PraxisGen::Model, :g
|
125
|
+
# # Cannot use the argument below or it will apply to all commands (the action in the class has it)
|
126
|
+
# # argument :collection_name, required: false
|
127
|
+
# # The options, however, since they're optional are fine (But need to be duplicated from the class :( )
|
128
|
+
# option :orm, required: false, default: 'activerecord', enum: ['activerecord','sequel'],
|
129
|
+
# desc: 'Type of ORM model to create.'
|
130
|
+
# def gmodel(*args)
|
131
|
+
# # Because we cannot share the :collection_name argument, we need to do this check here, before
|
132
|
+
# # we "parse" it and pass it to the g command
|
133
|
+
# unless args.size == 1
|
134
|
+
# ::PraxisGen::Model.command_help(shell,:g)
|
135
|
+
# exit 1
|
136
|
+
# end
|
137
|
+
|
138
|
+
# model_name,_ = args
|
139
|
+
# ::PraxisGen::Model.new([model_name],options).invoke(:g)
|
140
|
+
# end
|
141
|
+
|
87
142
|
end
|
88
143
|
|
89
144
|
PraxisGenerator.start(ARGV)
|
data/lib/praxis.rb
CHANGED
@@ -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'
|
@@ -62,7 +70,6 @@ module Praxis
|
|
62
70
|
|
63
71
|
autoload :MediaType, 'praxis/media_type'
|
64
72
|
autoload :MediaTypeIdentifier, 'praxis/media_type_identifier'
|
65
|
-
autoload :Multipart, 'praxis/types/multipart'
|
66
73
|
autoload :Collection, 'praxis/collection'
|
67
74
|
|
68
75
|
autoload :MultipartParser, 'praxis/multipart/parser'
|
@@ -11,7 +11,7 @@ module Praxis
|
|
11
11
|
class ActionDefinition
|
12
12
|
|
13
13
|
attr_reader :name
|
14
|
-
attr_reader :
|
14
|
+
attr_reader :endpoint_definition
|
15
15
|
attr_reader :api_definition
|
16
16
|
attr_reader :route
|
17
17
|
attr_reader :responses
|
@@ -31,33 +31,33 @@ module Praxis
|
|
31
31
|
self.doc_decorations << callback
|
32
32
|
end
|
33
33
|
|
34
|
-
def initialize(name,
|
34
|
+
def initialize(name, endpoint_definition, **opts, &block)
|
35
35
|
@name = name
|
36
|
-
@
|
36
|
+
@endpoint_definition = endpoint_definition
|
37
37
|
@responses = Hash.new
|
38
38
|
@metadata = Hash.new
|
39
39
|
@route = nil
|
40
40
|
@traits = []
|
41
41
|
|
42
|
-
if (media_type =
|
42
|
+
if (media_type = endpoint_definition.media_type)
|
43
43
|
if media_type.kind_of?(Class) && media_type < Praxis::Types::MediaTypeCommon
|
44
44
|
@reference_media_type = media_type
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
-
version =
|
49
|
-
api_info = ApiDefinition.instance.info(
|
48
|
+
version = endpoint_definition.version
|
49
|
+
api_info = ApiDefinition.instance.info(endpoint_definition.version)
|
50
50
|
|
51
|
-
route_base = "#{api_info.base_path}#{
|
52
|
-
prefix = Array(
|
51
|
+
route_base = "#{api_info.base_path}#{endpoint_definition.version_prefix}"
|
52
|
+
prefix = Array(endpoint_definition.routing_prefix)
|
53
53
|
|
54
54
|
@routing_config = RoutingConfig.new(version: version, base: route_base, prefix: prefix)
|
55
55
|
|
56
|
-
|
56
|
+
endpoint_definition.traits.each do |trait|
|
57
57
|
self.trait(trait)
|
58
58
|
end
|
59
59
|
|
60
|
-
|
60
|
+
endpoint_definition.action_defaults.apply!(self)
|
61
61
|
|
62
62
|
self.instance_eval(&block) if block_given?
|
63
63
|
end
|
@@ -71,7 +71,6 @@ module Praxis
|
|
71
71
|
trait.apply!(self)
|
72
72
|
traits << trait_name
|
73
73
|
end
|
74
|
-
alias_method :use, :trait
|
75
74
|
|
76
75
|
def update_attribute(attribute, options, block)
|
77
76
|
attribute.options.merge!(options)
|
@@ -246,7 +245,7 @@ module Praxis
|
|
246
245
|
def params_description(example:)
|
247
246
|
route_params = []
|
248
247
|
if route.nil?
|
249
|
-
warn "Warning: No route defined for #{
|
248
|
+
warn "Warning: No route defined for #{endpoint_definition.name}##{name}."
|
250
249
|
else
|
251
250
|
route_params = route.path.
|
252
251
|
named_captures.
|
@@ -343,6 +342,9 @@ module Praxis
|
|
343
342
|
metadata[:doc_visibility] = :none
|
344
343
|
end
|
345
344
|
|
346
|
-
|
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
|
347
349
|
end
|
348
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,'')
|
data/lib/praxis/application.rb
CHANGED
@@ -8,7 +8,7 @@ module Praxis
|
|
8
8
|
|
9
9
|
attr_reader :router
|
10
10
|
attr_reader :controllers
|
11
|
-
attr_reader :
|
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
|
-
@
|
35
|
+
@endpoint_definitions = Set.new
|
36
36
|
|
37
37
|
@error_handler = ErrorHandler.new
|
38
38
|
@validation_handler = ValidationHandler.new
|
@@ -123,5 +123,9 @@ module Praxis
|
|
123
123
|
@config.set(config)
|
124
124
|
end
|
125
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
|
126
130
|
end
|
127
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
|