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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.ruby-version +1 -1
- data/.travis.yml +1 -3
- data/CHANGELOG.md +33 -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 +0 -0
- data/lib/praxis/docs/{openapi → open_api}/paths_object.rb +0 -0
- 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 +47 -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 +1 -1
- data/lib/praxis/extensions/pagination/pagination_params.rb +6 -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 +190 -8
- data/spec/praxis/extensions/attribute_filtering/filters_parser_spec.rb +140 -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
|
@@ -29,7 +29,7 @@ describe Praxis::Extensions::FieldSelection::ActiveRecordQuerySelector do
|
|
|
29
29
|
:id # We always load the primary keys
|
|
30
30
|
]
|
|
31
31
|
end
|
|
32
|
-
let(:selector_node) { Praxis::Mapper::SelectorGenerator.new.add(ActiveBookResource,selector_fields) }
|
|
32
|
+
let(:selector_node) { Praxis::Mapper::SelectorGenerator.new.add(ActiveBookResource,selector_fields).selectors }
|
|
33
33
|
let(:debug){ false }
|
|
34
34
|
|
|
35
35
|
subject(:selector) {described_class.new(query: query, selectors: selector_node, debug: debug) }
|
|
@@ -64,7 +64,7 @@ describe Praxis::Extensions::FieldSelection::SequelQuerySelector do
|
|
|
64
64
|
]
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
let(:selector_node) { Praxis::Mapper::SelectorGenerator.new.add(SequelSimpleResource,selector_fields) }
|
|
67
|
+
let(:selector_node) { Praxis::Mapper::SelectorGenerator.new.add(SequelSimpleResource,selector_fields).selectors }
|
|
68
68
|
subject {described_class.new(query: query, selectors: selector_node, debug: debug) }
|
|
69
69
|
|
|
70
70
|
context 'generate' do
|
|
@@ -103,7 +103,7 @@ class ActiveBookResource < ActiveBaseResource
|
|
|
103
103
|
|
|
104
104
|
filters_mapping(
|
|
105
105
|
id: :id,
|
|
106
|
-
category_uuid: :category_uuid
|
|
106
|
+
# category_uuid: :category_uuid #NOTE: we do not need to define same-name-mappings if they exist
|
|
107
107
|
'fake_nested.name': 'simple_name',
|
|
108
108
|
'name': 'simple_name',
|
|
109
109
|
'name_is_not': lambda do |spec| # Silly way to use a proc, but good enough for testing
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'spec_helper'
|
|
3
|
+
|
|
4
|
+
describe Praxis::FieldExpander do
|
|
5
|
+
let(:field_expander) { Praxis::FieldExpander.new }
|
|
6
|
+
let(:expanded_person_default_fieldset) do
|
|
7
|
+
# The only ones that are not already leaves is the full name, which can expand to first, last
|
|
8
|
+
PersonBlueprint.default_fieldset.merge!(full_name: {first: true, last: true})
|
|
9
|
+
end
|
|
10
|
+
let(:expanded_address_default_fieldset) do
|
|
11
|
+
# AddressBlueprint' default fieldset already has all attributes as leaves
|
|
12
|
+
AddressBlueprint.default_fieldset
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
context 'expanding attributes of a PersonBlueprint blueprint' do
|
|
16
|
+
it 'with fields=true, expands all fields on the default fieldset' do
|
|
17
|
+
expect(field_expander.expand(PersonBlueprint, true)).to eq(expanded_person_default_fieldset)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'expands for a subset of the direct fields' do
|
|
21
|
+
expect(field_expander.expand(PersonBlueprint, name: true)).to eq(name: true)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'raises when trying to expand fields that do not exist in the type' do
|
|
25
|
+
expect do
|
|
26
|
+
field_expander.expand(PersonBlueprint, name: true, foobar: true)
|
|
27
|
+
end.to raise_error(/.*:foobar.*do not exist in PersonBlueprint/)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
context 'given inlined struct attribtue' do
|
|
31
|
+
it 'expands for a sub-struct' do
|
|
32
|
+
expect(field_expander.expand(PersonBlueprint, parents: true)).to eq(parents: { mother: true, father: true })
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'expands for a subset of a substruct' do
|
|
36
|
+
expect(field_expander.expand(PersonBlueprint, parents: { mother: true })).to eq(parents: { mother: true })
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
context 'given a non-inlined struct sub attribute' do
|
|
41
|
+
it 'expands a specific subattribute of a struct' do
|
|
42
|
+
expect(field_expander.expand(PersonBlueprint, full_name: { first: true })).to eq(full_name: { first: true })
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'fully expands collections properly by simply listing subfields' do
|
|
46
|
+
expect(field_expander.expand(PersonBlueprint, prior_addresses: true)).to eq(prior_addresses: expanded_address_default_fieldset)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
context 'given a attribute that is also a blueprint' do
|
|
51
|
+
it 'expands to default fieldset for an attribute that is also blueprint' do
|
|
52
|
+
expect(field_expander.expand(PersonBlueprint, address: true)).to eq(address: expanded_address_default_fieldset)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'expands to default fieldset for a subset of fields of an attribute that is a blueprint' do
|
|
56
|
+
expect(field_expander.expand(PersonBlueprint, address: { resident: true })).to eq(address: { resident: expanded_person_default_fieldset })
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
context 'collection attributes' do
|
|
61
|
+
it 'expands subfields by simply listing subfields the same as structs/blueprints' do
|
|
62
|
+
expect(field_expander.expand(PersonBlueprint, prior_addresses: { state: true })).to eq(prior_addresses: { state: true })
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'expands for for a primitive type' do
|
|
68
|
+
expect(field_expander.expand(String)).to eq(true)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it 'expands for an Attributor::Model' do
|
|
72
|
+
expect(field_expander.expand(FullName)).to eq(first: true, last: true)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'expands for a Blueprint' do
|
|
76
|
+
expect(field_expander.expand(PersonBlueprint, parents: true)).to eq(parents: { father: true, mother: true })
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it 'expands for an Attributor::Collection of an Attrbutor::Model' do
|
|
80
|
+
expect(field_expander.expand(Attributor::Collection.of(FullName))).to eq(first: true, last: true)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'expands for an Attributor::Collection of a Blueprint' do
|
|
84
|
+
expected = { name: true, resident: { full_name: { first: true, last: true } } }
|
|
85
|
+
expect(field_expander.expand(Attributor::Collection.of(AddressBlueprint), name: true, resident: { full_name: true })).to eq(expected)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it 'also expands array-wrapped field hashes for collections' do
|
|
89
|
+
expected = { name: true, resident: { full_name: { first: true, last: true } } }
|
|
90
|
+
expect(field_expander.expand(Attributor::Collection.of(AddressBlueprint), name: true, resident: { full_name: true })).to eq(expected)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it 'expands for an Attributor::Collection of a primitive type' do
|
|
94
|
+
expect(field_expander.expand(Attributor::Collection.of(String))).to eq(true)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
context 'expanding a two-dimensional collection' do
|
|
98
|
+
it 'expands the fields discarding the collection nexting nesting' do
|
|
99
|
+
matrix_type = Attributor::Collection.of(Attributor::Collection.of(FullName))
|
|
100
|
+
expect(field_expander.expand(matrix_type)).to eq(first: true, last: true)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
context 'circular expansions' do
|
|
105
|
+
it 'preserve field object identity for circular references' do
|
|
106
|
+
result = field_expander.expand(PersonBlueprint, address: {resident: true}, work_address: {resident: true})
|
|
107
|
+
expect(result[:address][:resident]).to be(result[:work_address][:resident])
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
context 'with collections of Blueprints' do
|
|
111
|
+
it 'still preserves object identity' do
|
|
112
|
+
result = field_expander.expand(PersonBlueprint, address: {resident: true}, prior_addresses: {resident: true})
|
|
113
|
+
expect(result[:address][:resident]).to be(result[:prior_addresses][:resident])
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it 'optimizes duplicate field expansions' do
|
|
119
|
+
expect(field_expander.expand(FullName, true)).to be(field_expander.expand(FullName, true))
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
context 'expanding hash attributes' do
|
|
123
|
+
let(:type) do
|
|
124
|
+
Class.new(Attributor::Model) do
|
|
125
|
+
attributes do
|
|
126
|
+
attribute :name, String
|
|
127
|
+
attribute :simple_hash, Hash
|
|
128
|
+
attribute :keyed_hash, Hash do
|
|
129
|
+
key :foo, String
|
|
130
|
+
key :bar, String
|
|
131
|
+
end
|
|
132
|
+
attribute :some_struct do
|
|
133
|
+
attribute :something, String
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it 'expands' do
|
|
140
|
+
expected = {
|
|
141
|
+
name: true,
|
|
142
|
+
simple_hash: true,
|
|
143
|
+
keyed_hash: { foo: true, bar: true },
|
|
144
|
+
some_struct: { something: true }
|
|
145
|
+
}
|
|
146
|
+
expect(field_expander.expand(type, true)).to eq(expected)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -8,7 +8,7 @@ describe Praxis::Mapper::SelectorGenerator do
|
|
|
8
8
|
context '#add' do
|
|
9
9
|
let(:resource) { SimpleResource }
|
|
10
10
|
shared_examples 'a proper selector' do
|
|
11
|
-
it { expect(generator.add(resource, fields).dump).to be_deep_equal selectors }
|
|
11
|
+
it { expect(generator.add(resource, fields).selectors.dump).to be_deep_equal selectors }
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
context 'basic combos' do
|
|
@@ -42,10 +42,11 @@ describe Praxis::MediaTypeIdentifier do
|
|
|
42
42
|
expect(described_class.new(unquoted_example).parameters['encoding']).to eq('martian')
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
# TODO: Maybe that's something we want to support in the future? seems a big hammer for such a thing
|
|
46
|
+
# it 'handles quoted semicolons in values' do
|
|
47
|
+
# pending("need to stop using a regexp to do a context-free parser's job")
|
|
48
|
+
# expect(described_class.new(tricky_example).parameters['sauce']).to eq('yes; absolutely')
|
|
49
|
+
# end
|
|
49
50
|
|
|
50
51
|
|
|
51
52
|
end
|
|
@@ -22,7 +22,6 @@ describe Praxis::MediaType do
|
|
|
22
22
|
|
|
23
23
|
subject(:address) { Address.new(resource) }
|
|
24
24
|
|
|
25
|
-
|
|
26
25
|
context 'attributes' do
|
|
27
26
|
its(:id) { should eq(1) }
|
|
28
27
|
its(:name) { should eq('Home') }
|
|
@@ -30,112 +29,24 @@ describe Praxis::MediaType do
|
|
|
30
29
|
end
|
|
31
30
|
|
|
32
31
|
|
|
33
|
-
context 'loading' do
|
|
34
|
-
it do
|
|
35
|
-
Person.load({id: 1})
|
|
36
|
-
Person.load(owner_resource)
|
|
37
|
-
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
|
|
42
32
|
context 'accessor methods' do
|
|
43
33
|
subject(:address_klass) { address.class }
|
|
44
34
|
|
|
45
35
|
context '#identifier' do
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
pending('interface-breaking change') if Praxis::VERSION =~ /^0/
|
|
49
|
-
expect(subject.identifier).to be_kind_of(Praxis::MediaTypeIdentifier)
|
|
50
|
-
end
|
|
36
|
+
it 'should be a kind of Praxis::MediaTypeIdentifier' do
|
|
37
|
+
expect(subject.identifier).to be_kind_of(Praxis::MediaTypeIdentifier)
|
|
51
38
|
end
|
|
52
39
|
end
|
|
53
40
|
|
|
54
41
|
its(:description) { should be_kind_of(String) }
|
|
55
|
-
|
|
56
42
|
end
|
|
57
43
|
|
|
58
44
|
context "rendering" do
|
|
59
|
-
subject(:output) { address.render
|
|
45
|
+
subject(:output) { address.render }
|
|
60
46
|
|
|
61
47
|
its([:id]) { should eq(address.id) }
|
|
62
48
|
its([:name]) { should eq(address.name) }
|
|
63
|
-
its([:owner]) { should eq(Person.dump(owner_resource
|
|
49
|
+
its([:owner]) { should eq(Person.dump(owner_resource)) }
|
|
64
50
|
its([:fields]) { should eq(address.fields.dump ) }
|
|
65
|
-
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
context 'describing' do
|
|
69
|
-
|
|
70
|
-
subject(:described){ Address.describe }
|
|
71
|
-
|
|
72
|
-
its(:keys) { should match_array( [:attributes, :description, :display_name, :family, :id, :identifier, :key, :name, :views, :requirements] ) }
|
|
73
|
-
its([:attributes]) { should be_kind_of(::Hash) }
|
|
74
|
-
its([:description]) { should be_kind_of(::String) }
|
|
75
|
-
its([:display_name]) { should be_kind_of(::String) }
|
|
76
|
-
its([:family]) { should be_kind_of(::String) }
|
|
77
|
-
its([:id]) { should be_kind_of(::String) }
|
|
78
|
-
its([:name]) { should be_kind_of(::String) }
|
|
79
|
-
its([:identifier]) { should be_kind_of(::String) }
|
|
80
|
-
its([:key]) { should be_kind_of(::Hash) }
|
|
81
|
-
its([:views]) { should be_kind_of(::Hash) }
|
|
82
|
-
|
|
83
|
-
its([:description]) { should eq(Address.description) }
|
|
84
|
-
its([:display_name]) { should eq(Address.display_name) }
|
|
85
|
-
its([:family]) { should eq(Address.family) }
|
|
86
|
-
its([:id]) { should eq(Address.id) }
|
|
87
|
-
its([:name]) { should eq(Address.name) }
|
|
88
|
-
its([:identifier]) { should eq(Address.identifier.to_s) }
|
|
89
|
-
it 'should include the defined views' do
|
|
90
|
-
expect( subject[:views].keys ).to match_array([:default, :master])
|
|
91
|
-
end
|
|
92
|
-
it 'should include the defined attributes' do
|
|
93
|
-
expect( subject[:attributes].keys ).to match_array([:id, :name, :owner, :custodian, :residents, :residents_summary, :fields])
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
context 'using blueprint caching' do
|
|
98
|
-
it 'has specs'
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
context Praxis::MediaType::FieldResolver do
|
|
102
|
-
let(:expander) { Praxis::FieldExpander }
|
|
103
|
-
let(:user_view) { User.views[:default] }
|
|
104
|
-
|
|
105
|
-
let(:fields) { expander.expand(user_view) }
|
|
106
|
-
|
|
107
|
-
let(:field_resolver) { Praxis::MediaType::FieldResolver }
|
|
108
|
-
|
|
109
|
-
subject(:output) { field_resolver.resolve(User,fields) }
|
|
110
|
-
|
|
111
|
-
context 'resolving collections' do
|
|
112
|
-
let(:fields) { {:id=>true, :posts=>[{:href=>true}]}}
|
|
113
|
-
it 'strips arrays from the incoming fields' do
|
|
114
|
-
expect(output).to eq(id: true, posts: {href: true})
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
it 'supports multi-dimensional collections' do
|
|
118
|
-
fields = {
|
|
119
|
-
id: true,
|
|
120
|
-
post_matrix:[[{title: true, href: true}]]
|
|
121
|
-
}
|
|
122
|
-
output = field_resolver.resolve(User,fields)
|
|
123
|
-
expect(output).to eq(id: true, post_matrix:{href: true, title: true})
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
it 'supports nesting structs and arrays collections' do
|
|
127
|
-
fields = {
|
|
128
|
-
id: true,
|
|
129
|
-
daily_posts: [
|
|
130
|
-
{day: true, posts: [{id: true}]}
|
|
131
|
-
]
|
|
132
|
-
}
|
|
133
|
-
output = field_resolver.resolve(User,fields)
|
|
134
|
-
expect(output).to eq(id: true, daily_posts:{day: true, posts: {id:true}})
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
|
|
138
51
|
end
|
|
139
|
-
|
|
140
|
-
|
|
141
52
|
end
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative '../spec_helper'
|
|
3
|
+
|
|
4
|
+
describe Praxis::Renderer do
|
|
5
|
+
let(:address) { AddressBlueprint.example }
|
|
6
|
+
let(:prior_addresses) { Array.new(2) { AddressBlueprint.example } }
|
|
7
|
+
let(:alias_one) { FullName.example }
|
|
8
|
+
let(:alias_two) { FullName.example }
|
|
9
|
+
let(:aliases) { [alias_one, alias_two] }
|
|
10
|
+
let(:metadata_hash) { { something: 'here' } }
|
|
11
|
+
let(:metadata) { Attributor::Hash.load(metadata_hash) }
|
|
12
|
+
|
|
13
|
+
let(:person) do
|
|
14
|
+
PersonBlueprint.example(
|
|
15
|
+
address: address,
|
|
16
|
+
email: nil,
|
|
17
|
+
prior_addresses: prior_addresses,
|
|
18
|
+
alive: false,
|
|
19
|
+
work_address: nil,
|
|
20
|
+
aliases: aliases,
|
|
21
|
+
metadata: metadata
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
let(:fields) do
|
|
26
|
+
{
|
|
27
|
+
name: true,
|
|
28
|
+
email: true,
|
|
29
|
+
full_name: { first: true, last: true },
|
|
30
|
+
address: {
|
|
31
|
+
state: true,
|
|
32
|
+
street: true,
|
|
33
|
+
resident: { name: true }
|
|
34
|
+
},
|
|
35
|
+
prior_addresses: { name: true },
|
|
36
|
+
work_address: true,
|
|
37
|
+
alive: true,
|
|
38
|
+
metadata: true,
|
|
39
|
+
aliases: true
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
let(:renderer) { Praxis::Renderer.new }
|
|
44
|
+
|
|
45
|
+
subject(:output) { renderer.render(person, fields) }
|
|
46
|
+
|
|
47
|
+
it 'renders existing attributes' do
|
|
48
|
+
expect(output.keys).to match_array([:name, :full_name, :alive, :address, :prior_addresses, :metadata, :aliases])
|
|
49
|
+
|
|
50
|
+
expect(output[:name]).to eq person.name
|
|
51
|
+
expect(output[:full_name]).to eq(first: person.full_name.first, last: person.full_name.last)
|
|
52
|
+
expect(output[:alive]).to be false
|
|
53
|
+
|
|
54
|
+
expect(output[:address]).to eq(state: person.address.state,
|
|
55
|
+
street: person.address.street,
|
|
56
|
+
resident: { name: person.address.resident.name })
|
|
57
|
+
|
|
58
|
+
expected_prior_addresses = prior_addresses.collect { |addr| { name: addr.name } }
|
|
59
|
+
expect(output[:prior_addresses]).to match_array(expected_prior_addresses)
|
|
60
|
+
|
|
61
|
+
expect(output[:aliases]).to match_array(aliases.collect(&:dump))
|
|
62
|
+
expect(output[:metadata]).to eq(metadata.dump)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
context 'calls dump for non-Blueprint, but still Dumpable instances' do
|
|
66
|
+
it 'when rendering them in full as array members' do
|
|
67
|
+
expect(alias_one).to receive(:dump).and_call_original
|
|
68
|
+
expect(output[:aliases].first).to eq(first: alias_one.first, last: alias_one.last)
|
|
69
|
+
end
|
|
70
|
+
it 'when rendering them in full as leaf object' do
|
|
71
|
+
expect(metadata).to receive(:dump).and_call_original
|
|
72
|
+
expect(output[:metadata]).to eq(metadata_hash)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it 'does not render attributes with nil values' do
|
|
77
|
+
expect(output).to_not have_key(:email)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
context 'with include_nil: true' do
|
|
81
|
+
let(:renderer) { Praxis::Renderer.new(include_nil: true) }
|
|
82
|
+
let(:address) { nil }
|
|
83
|
+
|
|
84
|
+
it 'renders attributes with nil values' do
|
|
85
|
+
expect(output).to have_key :email
|
|
86
|
+
expect(output[:email]).to be_nil
|
|
87
|
+
|
|
88
|
+
expect(output).to have_key :work_address
|
|
89
|
+
expect(output[:work_address]).to be_nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'renders nil directly for nil subobjects' do
|
|
93
|
+
expect(output).to have_key :address
|
|
94
|
+
expect(output[:address]).to be_nil
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
context 'rendering a two-dimmensional collection' do
|
|
99
|
+
let(:names) { Array.new(9) { |i| AddressBlueprint.example(i.to_s, name: i.to_s) } }
|
|
100
|
+
let(:matrix_type) do
|
|
101
|
+
Attributor::Collection.of(Attributor::Collection.of(AddressBlueprint))
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
let(:matrix) { matrix_type.load(names.each_slice(3).collect { |slice| slice }) }
|
|
105
|
+
|
|
106
|
+
let(:fields) { { name: true } }
|
|
107
|
+
|
|
108
|
+
it 'renders with render_collection and per-element field spec' do
|
|
109
|
+
rendered = renderer.render(matrix, fields)
|
|
110
|
+
expect(rendered.flatten.collect { |r| r[:name] }).to eq((0..8).collect(&:to_s))
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it 'renders with render and proper field spec' do
|
|
114
|
+
rendered = renderer.render(matrix, fields)
|
|
115
|
+
expect(rendered.flatten.collect { |r| r[:name] }).to eq((0..8).collect(&:to_s))
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
context 'rendering stuff that breaks badly' do
|
|
120
|
+
it 'does not break badly' do
|
|
121
|
+
expect{renderer.render(person, {tags: true})}.to_not raise_error
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
context 'caching rendered objects' do
|
|
126
|
+
let(:fields) { {full_name: true} }
|
|
127
|
+
it 'caches and returns identical results for the same field objects' do
|
|
128
|
+
expect(person).to receive(:full_name).once.and_call_original
|
|
129
|
+
|
|
130
|
+
render_1 = renderer.render(person, fields)
|
|
131
|
+
render_2 = renderer.render(person, fields)
|
|
132
|
+
expect(render_1).to be(render_2)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
context 'rendering hashes' do
|
|
137
|
+
let(:fields) do
|
|
138
|
+
{
|
|
139
|
+
id: true,
|
|
140
|
+
hash: true
|
|
141
|
+
}
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
let(:data) { { id: 10, hash: { foo: 'bar' } } }
|
|
145
|
+
let(:object) { SimpleHash.load(data) }
|
|
146
|
+
let(:renderer) { Praxis::Renderer.new }
|
|
147
|
+
|
|
148
|
+
subject(:output) { renderer.render(object, fields) }
|
|
149
|
+
|
|
150
|
+
its([:id]) { should eq data[:id] }
|
|
151
|
+
its([:hash]) { should eq data[:hash] }
|
|
152
|
+
its([:hash]) { should be_kind_of(Hash) }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
context 'rendering collections of hashes' do
|
|
156
|
+
let(:fields) do
|
|
157
|
+
{
|
|
158
|
+
id: true,
|
|
159
|
+
hash_collection: true
|
|
160
|
+
}
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
let(:data) { { id: 10, hash_collection: [{ foo: 'bar' }] } }
|
|
164
|
+
let(:object) { SimpleHashCollection.load(data) }
|
|
165
|
+
let(:renderer) { Praxis::Renderer.new }
|
|
166
|
+
|
|
167
|
+
subject(:output) { renderer.render(object, fields) }
|
|
168
|
+
|
|
169
|
+
its([:id]) { should eq data[:id] }
|
|
170
|
+
its([:hash_collection]) { should eq data[:hash_collection] }
|
|
171
|
+
its([:hash_collection]) { should be_kind_of(Array) }
|
|
172
|
+
|
|
173
|
+
it 'renders the hashes' do
|
|
174
|
+
expect(output[:hash_collection].first).to be_kind_of(Hash)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
context 'rendering a Blueprint with fields true' do
|
|
179
|
+
let(:fields) do
|
|
180
|
+
{
|
|
181
|
+
name: true,
|
|
182
|
+
address: true
|
|
183
|
+
}
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
its([:address]) { should eq person.address.dump }
|
|
187
|
+
end
|
|
188
|
+
end
|