praxis 2.0.pre.6 → 2.0.pre.11
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/.travis.yml +1 -3
- data/CHANGELOG.md +25 -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 +57 -8
- data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +3 -16
- 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 +2 -2
- 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 +19 -8
- data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +106 -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 +62 -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
@@ -24,8 +24,7 @@ describe Praxis::Extensions::FieldExpansion do
|
|
24
24
|
|
25
25
|
let(:request_params) do
|
26
26
|
double('params',
|
27
|
-
fields: Praxis::Extensions::FieldSelection::FieldSelector.for(Person).load(fields)
|
28
|
-
view: view
|
27
|
+
fields: Praxis::Extensions::FieldSelection::FieldSelector.for(Person).load(fields)
|
29
28
|
)
|
30
29
|
end
|
31
30
|
|
@@ -33,7 +32,6 @@ describe Praxis::Extensions::FieldExpansion do
|
|
33
32
|
let(:media_type) { Person }
|
34
33
|
|
35
34
|
let(:fields) { nil }
|
36
|
-
let(:view) { nil }
|
37
35
|
|
38
36
|
let(:test_attributes) { }
|
39
37
|
let(:test_params) { double('test_params', attributes: test_attributes) }
|
@@ -43,43 +41,26 @@ describe Praxis::Extensions::FieldExpansion do
|
|
43
41
|
context '#expanded_fields' do
|
44
42
|
|
45
43
|
context 'with fields and view params defined' do
|
46
|
-
let(:test_attributes) { {
|
44
|
+
let(:test_attributes) { {} }
|
47
45
|
|
48
|
-
context '
|
46
|
+
context 'with no fields provided' do
|
49
47
|
it 'returns the fields for the default view' do
|
50
48
|
expect(expansion).to eq({id: true, name: true})
|
51
49
|
end
|
52
|
-
|
53
|
-
pending 'and a view'
|
54
50
|
end
|
55
51
|
|
56
52
|
context 'with a set of fields provided' do
|
57
53
|
let(:fields) { 'id,name,owner{name}' }
|
58
|
-
it 'returns the subset of fields
|
54
|
+
it 'returns the subset of fields' do
|
59
55
|
expected = {id: true, name: true }
|
60
56
|
expect(expansion).to eq expected
|
61
57
|
end
|
62
|
-
|
63
|
-
pending 'and a view'
|
64
58
|
end
|
65
59
|
end
|
66
60
|
|
67
|
-
context 'with only a view param defined' do
|
68
|
-
let(:test_attributes) { {view: true} }
|
69
|
-
|
70
|
-
it 'returns the fields for the default view' do
|
71
|
-
expect(expansion).to eq({id: true, name: true})
|
72
|
-
end
|
73
|
-
|
74
|
-
pending 'and a view'
|
75
|
-
end
|
76
|
-
|
77
|
-
|
78
61
|
context 'with an action with no params' do
|
79
62
|
let(:test_params) { nil }
|
80
|
-
|
81
|
-
let(:view){ nil }
|
82
|
-
it 'ignores incoming parameters and expands for the default view' do
|
63
|
+
it 'ignores incoming parameters and expands for the default fieldset' do
|
83
64
|
expect(expansion).to eq({id: true, name: true})
|
84
65
|
end
|
85
66
|
end
|
@@ -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
|