praxis 2.0.pre.17 → 2.0.pre.21
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/.rubocop.yml +54 -0
- data/.simplecov +3 -1
- data/.travis.yml +2 -1
- data/CHANGELOG.md +19 -0
- data/CONTRIBUTING.md +2 -79
- data/Gemfile +5 -1
- data/Guardfile +6 -4
- data/LICENSE +0 -2
- data/MAINTAINERS.md +1 -0
- data/README.md +15 -22
- data/Rakefile +4 -2
- data/bin/praxis +55 -58
- data/lib/praxis/action_definition/headers_dsl_compiler.rb +5 -6
- data/lib/praxis/action_definition.rb +65 -95
- data/lib/praxis/api_definition.rb +21 -29
- data/lib/praxis/api_general_info.rb +55 -66
- data/lib/praxis/application.rb +15 -32
- data/lib/praxis/blueprint.rb +80 -73
- data/lib/praxis/bootloader.rb +24 -33
- data/lib/praxis/bootloader_stages/environment.rb +5 -10
- data/lib/praxis/bootloader_stages/file_loader.rb +3 -6
- data/lib/praxis/bootloader_stages/plugin_config_load.rb +4 -6
- data/lib/praxis/bootloader_stages/plugin_config_prepare.rb +2 -2
- data/lib/praxis/bootloader_stages/plugin_loader.rb +3 -7
- data/lib/praxis/bootloader_stages/plugin_setup.rb +3 -3
- data/lib/praxis/bootloader_stages/routing.rb +5 -8
- data/lib/praxis/bootloader_stages/subgroup_loader.rb +2 -10
- data/lib/praxis/bootloader_stages/warn_unloaded_files.rb +15 -19
- data/lib/praxis/callbacks.rb +12 -11
- data/lib/praxis/collection.rb +11 -14
- data/lib/praxis/config.rb +17 -28
- data/lib/praxis/config_hash.rb +2 -1
- data/lib/praxis/controller.rb +7 -6
- data/lib/praxis/dispatcher.rb +34 -42
- data/lib/praxis/docs/open_api/info_object.rb +11 -8
- data/lib/praxis/docs/open_api/media_type_object.rb +18 -17
- data/lib/praxis/docs/open_api/operation_object.rb +7 -4
- data/lib/praxis/docs/open_api/parameter_object.rb +17 -14
- data/lib/praxis/docs/open_api/paths_object.rb +11 -9
- data/lib/praxis/docs/open_api/request_body_object.rb +14 -13
- data/lib/praxis/docs/open_api/response_object.rb +24 -18
- data/lib/praxis/docs/open_api/responses_object.rb +3 -1
- data/lib/praxis/docs/open_api/schema_object.rb +61 -29
- data/lib/praxis/docs/open_api/server_object.rb +5 -2
- data/lib/praxis/docs/open_api/tag_object.rb +9 -6
- data/lib/praxis/docs/open_api_generator.rb +114 -150
- data/lib/praxis/endpoint_definition.rb +60 -77
- data/lib/praxis/error_handler.rb +2 -2
- data/lib/praxis/exception.rb +2 -0
- data/lib/praxis/exceptions/config.rb +3 -1
- data/lib/praxis/exceptions/config_load.rb +2 -0
- data/lib/praxis/exceptions/config_validation.rb +3 -1
- data/lib/praxis/exceptions/invalid_configuration.rb +3 -1
- data/lib/praxis/exceptions/invalid_response.rb +3 -1
- data/lib/praxis/exceptions/invalid_trait.rb +3 -1
- data/lib/praxis/exceptions/stage_not_found.rb +3 -1
- data/lib/praxis/exceptions/validation.rb +4 -3
- data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +163 -149
- data/lib/praxis/extensions/attribute_filtering/active_record_patches/5x.rb +18 -13
- data/lib/praxis/extensions/attribute_filtering/active_record_patches/6_0.rb +13 -9
- data/lib/praxis/extensions/attribute_filtering/active_record_patches/6_1_plus.rb +14 -11
- data/lib/praxis/extensions/attribute_filtering/active_record_patches.rb +12 -9
- data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +8 -5
- data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +89 -65
- data/lib/praxis/extensions/attribute_filtering/filters_parser.rb +68 -62
- data/lib/praxis/extensions/attribute_filtering.rb +3 -1
- data/lib/praxis/extensions/field_expansion.rb +6 -4
- data/lib/praxis/extensions/field_selection/active_record_query_selector.rb +10 -8
- data/lib/praxis/extensions/field_selection/field_selector.rb +91 -92
- data/lib/praxis/extensions/field_selection/sequel_query_selector.rb +12 -12
- data/lib/praxis/extensions/field_selection.rb +3 -1
- data/lib/praxis/extensions/pagination/active_record_pagination_handler.rb +6 -4
- data/lib/praxis/extensions/pagination/header_generator.rb +16 -11
- data/lib/praxis/extensions/pagination/ordering_params.rb +29 -28
- data/lib/praxis/extensions/pagination/pagination_handler.rb +44 -42
- data/lib/praxis/extensions/pagination/pagination_params.rb +29 -48
- data/lib/praxis/extensions/pagination/sequel_pagination_handler.rb +8 -7
- data/lib/praxis/extensions/pagination.rb +10 -15
- data/lib/praxis/extensions/rails_compat/request_methods.rb +3 -4
- data/lib/praxis/extensions/rails_compat.rb +2 -0
- data/lib/praxis/extensions/rendering.rb +12 -12
- data/lib/praxis/field_expander.rb +8 -9
- data/lib/praxis/file_group.rb +8 -12
- data/lib/praxis/finalizable.rb +1 -0
- data/lib/praxis/handlers/json.rb +5 -2
- data/lib/praxis/handlers/plain.rb +2 -1
- data/lib/praxis/handlers/www_form.rb +6 -3
- data/lib/praxis/handlers/{xml-sample.rb → xml_sample.rb} +26 -22
- data/lib/praxis/mapper/active_model_compat.rb +13 -10
- data/lib/praxis/mapper/resource.rb +196 -181
- data/lib/praxis/mapper/selector_generator.rb +106 -112
- data/lib/praxis/mapper/sequel_compat.rb +70 -67
- data/lib/praxis/media_type.rb +2 -2
- data/lib/praxis/media_type_identifier.rb +26 -22
- data/lib/praxis/middleware_app.rb +18 -15
- data/lib/praxis/multipart/parser.rb +46 -51
- data/lib/praxis/multipart/part.rb +78 -110
- data/lib/praxis/notifications.rb +2 -4
- data/lib/praxis/plugin.rb +11 -18
- data/lib/praxis/plugin_concern.rb +12 -15
- data/lib/praxis/plugins/mapper_plugin.rb +15 -13
- data/lib/praxis/plugins/pagination_plugin.rb +8 -6
- data/lib/praxis/plugins/rails_plugin.rb +33 -28
- data/lib/praxis/renderer.rb +11 -15
- data/lib/praxis/request.rb +48 -44
- data/lib/praxis/request_stages/action.rb +4 -6
- data/lib/praxis/request_stages/load_request.rb +2 -4
- data/lib/praxis/request_stages/request_stage.rb +19 -23
- data/lib/praxis/request_stages/response.rb +4 -6
- data/lib/praxis/request_stages/validate.rb +3 -5
- data/lib/praxis/request_stages/validate_params_and_headers.rb +15 -22
- data/lib/praxis/request_stages/validate_payload.rb +25 -28
- data/lib/praxis/request_superclassing.rb +3 -3
- data/lib/praxis/resource_definition.rb +1 -0
- data/lib/praxis/response.rb +24 -26
- data/lib/praxis/response_definition.rb +77 -122
- data/lib/praxis/response_template.rb +11 -15
- data/lib/praxis/responses/http.rb +23 -44
- data/lib/praxis/responses/internal_server_error.rb +18 -21
- data/lib/praxis/responses/multipart_ok.rb +4 -9
- data/lib/praxis/responses/validation_error.rb +8 -15
- data/lib/praxis/route.rb +8 -10
- data/lib/praxis/router/rack.rb +13 -7
- data/lib/praxis/router/simple.rb +10 -5
- data/lib/praxis/router.rb +27 -34
- data/lib/praxis/routing_config.rb +52 -29
- data/lib/praxis/simple_media_type.rb +5 -8
- data/lib/praxis/stage.rb +17 -25
- data/lib/praxis/tasks/api_docs.rb +17 -16
- data/lib/praxis/tasks/console.rb +3 -1
- data/lib/praxis/tasks/environment.rb +2 -0
- data/lib/praxis/tasks/routes.rb +26 -24
- data/lib/praxis/tasks.rb +3 -1
- data/lib/praxis/trait.rb +37 -46
- data/lib/praxis/types/fuzzy_hash.rb +13 -14
- data/lib/praxis/types/media_type_common.rb +11 -10
- data/lib/praxis/types/multipart_array/part_definition.rb +14 -17
- data/lib/praxis/types/multipart_array.rb +100 -115
- data/lib/praxis/validation_handler.rb +5 -3
- data/lib/praxis/version.rb +3 -1
- data/lib/praxis.rb +4 -5
- data/praxis.gemspec +22 -21
- data/spec/functional_spec.rb +44 -56
- data/spec/praxis/action_definition_spec.rb +39 -48
- data/spec/praxis/api_definition_spec.rb +45 -47
- data/spec/praxis/api_general_info_spec.rb +28 -29
- data/spec/praxis/application_spec.rb +18 -14
- data/spec/praxis/blueprint_spec.rb +33 -34
- data/spec/praxis/bootloader_spec.rb +32 -30
- data/spec/praxis/callbacks_spec.rb +37 -37
- data/spec/praxis/collection_spec.rb +18 -25
- data/spec/praxis/config_hash_spec.rb +5 -4
- data/spec/praxis/config_spec.rb +27 -26
- data/spec/praxis/controller_spec.rb +8 -9
- data/spec/praxis/endpoint_definition_spec.rb +25 -32
- data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +171 -114
- data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +22 -21
- data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +112 -60
- data/spec/praxis/extensions/attribute_filtering/filters_parser_spec.rb +37 -38
- data/spec/praxis/extensions/field_expansion_spec.rb +8 -10
- data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +14 -13
- data/spec/praxis/extensions/field_selection/field_selector_spec.rb +9 -16
- data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +50 -49
- data/spec/praxis/extensions/pagination/active_record_pagination_handler_spec.rb +32 -31
- data/spec/praxis/extensions/rendering_spec.rb +9 -9
- data/spec/praxis/extensions/support/spec_resources_active_model.rb +32 -49
- data/spec/praxis/extensions/support/spec_resources_sequel.rb +48 -48
- data/spec/praxis/field_expander_spec.rb +6 -5
- data/spec/praxis/file_group_spec.rb +3 -1
- data/spec/praxis/handlers/json_spec.rb +6 -5
- data/spec/praxis/mapper/resource_spec.rb +39 -29
- data/spec/praxis/mapper/selector_generator_spec.rb +80 -46
- data/spec/praxis/media_type_identifier_spec.rb +13 -10
- data/spec/praxis/media_type_spec.rb +12 -12
- data/spec/praxis/middleware_app_spec.rb +23 -22
- data/spec/praxis/multipart/parser_spec.rb +7 -9
- data/spec/praxis/notifications_spec.rb +4 -4
- data/spec/praxis/plugin_concern_spec.rb +5 -6
- data/spec/praxis/renderer_spec.rb +10 -9
- data/spec/praxis/request_spec.rb +38 -41
- data/spec/praxis/request_stages/action_spec.rb +14 -15
- data/spec/praxis/request_stages/request_stage_spec.rb +30 -41
- data/spec/praxis/request_stages/validate_spec.rb +3 -1
- data/spec/praxis/response_definition_spec.rb +79 -92
- data/spec/praxis/response_spec.rb +35 -40
- data/spec/praxis/responses/internal_server_error_spec.rb +6 -9
- data/spec/praxis/responses/validation_error_spec.rb +17 -18
- data/spec/praxis/route_spec.rb +4 -7
- data/spec/praxis/router_spec.rb +69 -79
- data/spec/praxis/routing_config_spec.rb +15 -14
- data/spec/praxis/stage_spec.rb +56 -53
- data/spec/praxis/trait_spec.rb +17 -17
- data/spec/praxis/types/fuzzy_hash_spec.rb +11 -9
- data/spec/praxis/types/multipart_array/part_definition_spec.rb +3 -2
- data/spec/praxis/types/multipart_array_spec.rb +33 -48
- data/spec/spec_app/app/concerns/authenticated.rb +5 -5
- data/spec/spec_app/app/concerns/basic_api.rb +3 -1
- data/spec/spec_app/app/concerns/log_wrapper.rb +5 -3
- data/spec/spec_app/app/controllers/base_class.rb +6 -5
- data/spec/spec_app/app/controllers/instances.rb +31 -34
- data/spec/spec_app/app/controllers/volumes.rb +6 -6
- data/spec/spec_app/app/responses/multipart.rb +1 -2
- data/spec/spec_app/app/responses/other_response.rb +2 -2
- data/spec/spec_app/config/environment.rb +19 -6
- data/spec/spec_app/config.ru +4 -3
- data/spec/spec_app/design/api.rb +13 -15
- data/spec/spec_app/design/media_types/instance.rb +6 -6
- data/spec/spec_app/design/media_types/volume.rb +2 -1
- data/spec/spec_app/design/media_types/volume_snapshot.rb +2 -1
- data/spec/spec_app/design/resources/instances.rb +11 -17
- data/spec/spec_app/design/resources/volume_snapshots.rb +4 -5
- data/spec/spec_app/design/resources/volumes.rb +4 -5
- data/spec/spec_helper.rb +11 -13
- data/spec/support/be_deep_equal_matcher.rb +5 -0
- data/spec/support/spec_authorization_plugin.rb +7 -12
- data/spec/support/spec_blueprints.rb +5 -4
- data/spec/support/spec_complex_authentication_plugin.rb +17 -34
- data/spec/support/spec_endpoint_definitions.rb +2 -3
- data/spec/support/spec_media_types.rb +28 -35
- data/spec/support/spec_resources.rb +22 -16
- data/spec/support/spec_simple_authentication_plugin.rb +5 -9
- data/tasks/loader.thor +4 -2
- data/tasks/thor/app.rb +7 -5
- data/tasks/thor/example.rb +23 -22
- data/tasks/thor/model.rb +7 -7
- data/tasks/thor/scaffold.rb +23 -23
- data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +0 -8
- data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +1 -2
- metadata +72 -84
- data/MAINTAINERS +0 -2
- data/TODO.md +0 -25
- data/spec/praxis/api_resource_spec.rb +0 -0
- data/spec/praxis/dispatcher_spec.rb +0 -0
- data/spec/spec_app/app/responses/bulk_response.rb +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'spec_helper'
|
|
2
4
|
|
|
3
5
|
require 'praxis/extensions/field_selection'
|
|
4
6
|
|
|
5
7
|
describe Praxis::Extensions::FieldSelection::FieldSelector do
|
|
6
|
-
|
|
7
8
|
let(:type) { described_class.for(Address) }
|
|
8
9
|
|
|
9
|
-
|
|
10
10
|
subject(:field_selector) { type.load(fields) }
|
|
11
11
|
|
|
12
12
|
context '.example' do
|
|
@@ -21,7 +21,6 @@ describe Praxis::Extensions::FieldSelection::FieldSelector do
|
|
|
21
21
|
it 'validates' do
|
|
22
22
|
expect(type.example.validate).to be_empty
|
|
23
23
|
end
|
|
24
|
-
|
|
25
24
|
end
|
|
26
25
|
|
|
27
26
|
context '.load' do
|
|
@@ -32,22 +31,20 @@ describe Praxis::Extensions::FieldSelection::FieldSelector do
|
|
|
32
31
|
expect(type.load('').fields).to be(true)
|
|
33
32
|
end
|
|
34
33
|
|
|
35
|
-
|
|
36
34
|
it 'loads fields' do
|
|
37
35
|
fields = 'id,name,owner{name}'
|
|
38
36
|
|
|
39
|
-
expect(Attributor::FieldSelector).to receive(:load)
|
|
40
|
-
with(fields).and_return(parsed_fields)
|
|
37
|
+
expect(Attributor::FieldSelector).to receive(:load)
|
|
38
|
+
.with(fields).and_return(parsed_fields)
|
|
41
39
|
|
|
42
40
|
result = type.load(fields)
|
|
43
41
|
expect(result.fields).to be parsed_fields
|
|
44
42
|
end
|
|
45
|
-
|
|
46
43
|
end
|
|
47
44
|
|
|
48
45
|
context '#dump' do
|
|
49
46
|
it 'is dumpable' do
|
|
50
|
-
expect(
|
|
47
|
+
expect(type.load('id')).to be_kind_of(Attributor::Dumpable)
|
|
51
48
|
end
|
|
52
49
|
|
|
53
50
|
it 'dumps nested fields properly' do
|
|
@@ -72,14 +69,13 @@ describe Praxis::Extensions::FieldSelection::FieldSelector do
|
|
|
72
69
|
context 'validating subattributes' do
|
|
73
70
|
let(:selector_string) { 'id,resident,owner{age}' }
|
|
74
71
|
it 'validates subattributes' do
|
|
75
|
-
errors =
|
|
72
|
+
errors = type.validate(selector_string)
|
|
76
73
|
expect(errors).to match_array([
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
'Attribute with name resident not found in Address',
|
|
75
|
+
'Attribute with name age not found in Person'
|
|
76
|
+
])
|
|
80
77
|
end
|
|
81
78
|
end
|
|
82
|
-
|
|
83
79
|
end
|
|
84
80
|
|
|
85
81
|
context '#validate' do
|
|
@@ -90,7 +86,4 @@ describe Praxis::Extensions::FieldSelection::FieldSelector do
|
|
|
90
86
|
expect(type.validate('id,owner{foo}')).to have(1).items
|
|
91
87
|
end
|
|
92
88
|
end
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
89
|
end
|
|
@@ -1,46 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'spec_helper'
|
|
2
4
|
require 'sequel'
|
|
3
5
|
|
|
4
6
|
require 'praxis/extensions/field_selection/sequel_query_selector'
|
|
5
7
|
|
|
8
|
+
class QTest
|
|
9
|
+
attr_reader :object, :cols
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
end
|
|
25
|
-
def dump
|
|
26
|
-
eagers = @object.each_with_object({}) do |(name, val), hash|
|
|
27
|
-
hash[name] = val.dump
|
|
28
|
-
end
|
|
29
|
-
{
|
|
30
|
-
columns: @cols,
|
|
31
|
-
eagers: eagers
|
|
32
|
-
}
|
|
33
|
-
end
|
|
11
|
+
def initialize
|
|
12
|
+
@object = {}
|
|
13
|
+
@cols = []
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def eager(hash)
|
|
17
|
+
raise 'we are only calling eager one at a time!' if hash.keys.size > 1
|
|
18
|
+
|
|
19
|
+
name = hash.keys.first
|
|
20
|
+
# Actually call the incoming proc with an instance of QTest, to collect the further select/eager calls
|
|
21
|
+
@object[name] = hash[name].call(QTest.new)
|
|
22
|
+
self
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def select(*names)
|
|
26
|
+
@cols += names.map(&:column)
|
|
27
|
+
self
|
|
34
28
|
end
|
|
35
29
|
|
|
30
|
+
def dump
|
|
31
|
+
eagers = @object.transform_values(&:dump)
|
|
32
|
+
{
|
|
33
|
+
columns: @cols,
|
|
34
|
+
eagers: eagers
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
end
|
|
36
38
|
|
|
39
|
+
describe Praxis::Extensions::FieldSelection::SequelQuerySelector do
|
|
37
40
|
# Pay the price for creating and connecting only in this spec instead in spec helper
|
|
38
41
|
# this way all other specs do not need to be slower and it's a better TDD experience
|
|
39
42
|
|
|
40
|
-
require_relative '../support/spec_resources_sequel
|
|
41
|
-
|
|
43
|
+
require_relative '../support/spec_resources_sequel'
|
|
44
|
+
|
|
42
45
|
let(:selector_fields) do
|
|
43
|
-
{
|
|
46
|
+
{
|
|
44
47
|
name: true,
|
|
45
48
|
other_model: {
|
|
46
49
|
id: true
|
|
@@ -64,15 +67,15 @@ describe Praxis::Extensions::FieldSelection::SequelQuerySelector do
|
|
|
64
67
|
]
|
|
65
68
|
end
|
|
66
69
|
|
|
67
|
-
let(:selector_node) { Praxis::Mapper::SelectorGenerator.new.add(SequelSimpleResource,selector_fields).selectors
|
|
68
|
-
subject {described_class.new(query: query, selectors: selector_node, debug: debug) }
|
|
70
|
+
let(:selector_node) { Praxis::Mapper::SelectorGenerator.new.add(SequelSimpleResource, selector_fields).selectors }
|
|
71
|
+
subject { described_class.new(query: query, selectors: selector_node, debug: debug) }
|
|
69
72
|
|
|
70
73
|
context 'generate' do
|
|
71
74
|
let(:debug) { false }
|
|
72
75
|
context 'using the real models and DB' do
|
|
73
76
|
let(:query) { SequelSimpleModel }
|
|
74
77
|
|
|
75
|
-
it 'calls the select columns for the top level, and includes the right association hashes' do
|
|
78
|
+
it 'calls the select columns for the top level, and includes the right association hashes' do
|
|
76
79
|
ds = subject.generate
|
|
77
80
|
opts = ds.opts
|
|
78
81
|
# Top model is our simplemodel
|
|
@@ -80,26 +83,26 @@ describe Praxis::Extensions::FieldSelection::SequelQuerySelector do
|
|
|
80
83
|
selected_column_names = opts[:select].map(&:column)
|
|
81
84
|
expect(selected_column_names).to match_array(expected_select_from_to_query)
|
|
82
85
|
# 2 Eager loaded associations as well
|
|
83
|
-
expect(opts[:eager].keys).to match_array([
|
|
86
|
+
expect(opts[:eager].keys).to match_array(%i[other_model parent tags])
|
|
84
87
|
# We can not introspect those eagers, as they are procs...but at least validate they are
|
|
85
88
|
expect(opts[:eager][:other_model]).to be_a Proc
|
|
86
89
|
expect(opts[:eager][:parent]).to be_a Proc
|
|
87
|
-
|
|
90
|
+
|
|
88
91
|
# Also, let's make sure the query actually works by making Sequel attempt to retrieve it and finding the right things.
|
|
89
92
|
result = ds.all
|
|
90
93
|
# 2 simple models
|
|
91
|
-
expect(result.size).to be 2
|
|
94
|
+
expect(result.size).to be 2
|
|
92
95
|
# First simple model points to other_model 11 and parent 1
|
|
93
|
-
simple_one = result.find{|i| i.id == 1}
|
|
96
|
+
simple_one = result.find { |i| i.id == 1 }
|
|
94
97
|
expect(simple_one.other_model.id).to be 11
|
|
95
98
|
expect(simple_one.parent.id).to be 1
|
|
96
99
|
# also, its' parent in turn has 2 children (1 and 2) linked by its parent_uuid
|
|
97
|
-
expect(simple_one.parent.children.map(&:id)).to match_array([1,2])
|
|
100
|
+
expect(simple_one.parent.children.map(&:id)).to match_array([1, 2])
|
|
98
101
|
# Has the blue and red tags
|
|
99
|
-
expect(simple_one.tags.map(&:tag_name)).to match_array([
|
|
102
|
+
expect(simple_one.tags.map(&:tag_name)).to match_array(%w[blue red])
|
|
100
103
|
|
|
101
104
|
# second simple model points to other_model 22 and parent 2
|
|
102
|
-
simple_two = result.find{|i| i.id == 2}
|
|
105
|
+
simple_two = result.find { |i| i.id == 2 }
|
|
103
106
|
expect(simple_two.other_model.id).to be 22
|
|
104
107
|
expect(simple_two.parent.id).to be 2
|
|
105
108
|
# also, its' parent in turn has no children (as no simple models point to it by uuid)
|
|
@@ -112,36 +115,34 @@ describe Praxis::Extensions::FieldSelection::SequelQuerySelector do
|
|
|
112
115
|
# Actually make it run all the way...but suppressing the output
|
|
113
116
|
subject.generate
|
|
114
117
|
end
|
|
115
|
-
end
|
|
118
|
+
end
|
|
116
119
|
end
|
|
117
120
|
context 'just mocking the query' do
|
|
118
|
-
let(:query) {
|
|
121
|
+
let(:query) { QTest.new }
|
|
119
122
|
|
|
120
|
-
it 'creates the right recursive lambdas for the eager loading' do
|
|
121
|
-
|
|
123
|
+
it 'creates the right recursive lambdas for the eager loading' do
|
|
122
124
|
ds = subject.generate
|
|
123
125
|
result = ds.dump
|
|
124
126
|
expect(result[:columns]).to match_array(expected_select_from_to_query)
|
|
125
127
|
# 2 eager loads
|
|
126
|
-
expect(result[:eagers].keys).to match_array([
|
|
128
|
+
expect(result[:eagers].keys).to match_array(%i[other_model parent tags])
|
|
127
129
|
# 1 - other model
|
|
128
130
|
other_model_eagers = result[:eagers][:other_model]
|
|
129
131
|
expect(other_model_eagers[:columns]).to match_array([:id])
|
|
130
132
|
|
|
131
133
|
# 2 - parent association
|
|
132
134
|
parent_eagers = result[:eagers][:parent]
|
|
133
|
-
expect(parent_eagers[:columns]).to match_array([
|
|
135
|
+
expect(parent_eagers[:columns]).to match_array(%i[id uuid]) # uuid is necessary for the "children" assoc
|
|
134
136
|
expect(parent_eagers[:eagers].keys).to match_array([:children])
|
|
135
137
|
# 2.1 - children association off of the parent
|
|
136
138
|
parent_children_eagers = parent_eagers[:eagers][:children]
|
|
137
|
-
expect(parent_children_eagers[:columns]).to match_array([
|
|
139
|
+
expect(parent_children_eagers[:columns]).to match_array(%i[id parent_uuid]) # parent_uuid is required for the assoc
|
|
138
140
|
expect(parent_children_eagers[:eagers]).to be_empty
|
|
139
141
|
|
|
140
142
|
# 3 - tags association
|
|
141
143
|
tags_eagers = result[:eagers][:tags]
|
|
142
|
-
expect(tags_eagers[:columns]).to match_array([
|
|
144
|
+
expect(tags_eagers[:columns]).to match_array(%i[id tag_name]) # uuid is necessary for the "children" assoc
|
|
143
145
|
expect(tags_eagers[:eagers].keys).to be_empty
|
|
144
|
-
|
|
145
146
|
end
|
|
146
147
|
end
|
|
147
148
|
end
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'spec_helper'
|
|
2
4
|
|
|
3
|
-
require_relative '../support/spec_resources_active_model
|
|
5
|
+
require_relative '../support/spec_resources_active_model'
|
|
4
6
|
require 'praxis/extensions/pagination'
|
|
5
7
|
|
|
6
8
|
class Book < Praxis::MediaType
|
|
@@ -15,7 +17,7 @@ BookPaginationParamsAttribute = Attributor::Attribute.new(Praxis::Types::Paginat
|
|
|
15
17
|
max_items 3
|
|
16
18
|
page_size 2
|
|
17
19
|
# disallow :paging
|
|
18
|
-
default by: :id
|
|
20
|
+
default by: :id
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
BookOrderingParamsAttribute = Attributor::Attribute.new(Praxis::Types::OrderingParams.for(Book)) do
|
|
@@ -49,10 +51,9 @@ describe Praxis::Extensions::Pagination::ActiveRecordPaginationHandler do
|
|
|
49
51
|
Praxis::Extensions::Pagination::PaginationStruct.new(paginator_params, order_params)
|
|
50
52
|
end
|
|
51
53
|
|
|
52
|
-
|
|
53
54
|
context '.paginate' do
|
|
54
|
-
subject {described_class.paginate(query, pagination) }
|
|
55
|
-
|
|
55
|
+
subject { described_class.paginate(query, pagination) }
|
|
56
|
+
|
|
56
57
|
context 'empty struct' do
|
|
57
58
|
let(:paginator_params) { nil }
|
|
58
59
|
|
|
@@ -63,68 +64,68 @@ describe Praxis::Extensions::Pagination::ActiveRecordPaginationHandler do
|
|
|
63
64
|
|
|
64
65
|
context 'page-based' do
|
|
65
66
|
it_behaves_like 'paginates_the_same', 'page=1,items=3',
|
|
66
|
-
|
|
67
|
+
::ActiveBook.limit(3)
|
|
67
68
|
it_behaves_like 'paginates_the_same', 'page=2,items=3',
|
|
68
|
-
|
|
69
|
+
::ActiveBook.offset(3).limit(3)
|
|
69
70
|
end
|
|
70
71
|
|
|
71
72
|
context 'page-based with defaults' do
|
|
72
73
|
it_behaves_like 'paginates_the_same', '',
|
|
73
|
-
|
|
74
|
+
::ActiveBook.offset(0).limit(2)
|
|
74
75
|
it_behaves_like 'paginates_the_same', 'page=2',
|
|
75
|
-
|
|
76
|
+
::ActiveBook.offset(2).limit(2)
|
|
76
77
|
end
|
|
77
78
|
|
|
78
79
|
context 'cursor-based' do
|
|
79
80
|
it_behaves_like 'paginates_the_same', 'by=id,items=3',
|
|
80
|
-
|
|
81
|
+
::ActiveBook.limit(3).order(id: :asc)
|
|
81
82
|
it_behaves_like 'paginates_the_same', 'by=id,from=1000,items=3',
|
|
82
|
-
|
|
83
|
+
::ActiveBook.where('id > 1000').limit(3).order(id: :asc)
|
|
83
84
|
it_behaves_like 'paginates_the_same', 'by=simple_name,from=Book1000,items=3',
|
|
84
|
-
|
|
85
|
+
::ActiveBook.where("simple_name > 'Book1000'").limit(3).order(simple_name: :asc)
|
|
85
86
|
end
|
|
86
87
|
|
|
87
88
|
context 'cursor-based with defaults' do
|
|
88
89
|
it_behaves_like 'paginates_the_same', '',
|
|
89
|
-
|
|
90
|
+
::ActiveBook.limit(2).order(id: :asc)
|
|
90
91
|
it_behaves_like 'paginates_the_same', 'by=id,from=1000',
|
|
91
|
-
|
|
92
|
+
::ActiveBook.where('id > 1000').limit(2).order(id: :asc)
|
|
92
93
|
end
|
|
93
94
|
|
|
94
95
|
context 'including order' do
|
|
95
96
|
let(:order_params) { BookOrderingParamsAttribute.load(op_string) }
|
|
96
97
|
|
|
97
98
|
context 'when compatible with cursor' do
|
|
98
|
-
let(:op_string){ 'id'}
|
|
99
|
+
let(:op_string) { 'id' }
|
|
99
100
|
# Compatible cursor field
|
|
100
101
|
it_behaves_like 'paginates_the_same', 'by=id,items=3',
|
|
101
|
-
|
|
102
|
+
::ActiveBook.limit(3).order(id: :asc)
|
|
102
103
|
end
|
|
103
|
-
|
|
104
|
+
|
|
104
105
|
context 'when incompatible with cursor' do
|
|
105
|
-
let(:op_string){ 'id'}
|
|
106
|
+
let(:op_string) { 'id' }
|
|
106
107
|
let(:paginator_params) { BookPaginationParamsAttribute.load('by=simple_name,items=3') }
|
|
107
|
-
it do
|
|
108
|
-
expect{subject.all}.to raise_error(described_class::PaginationException, /is incompatible with pagination/)
|
|
108
|
+
it do
|
|
109
|
+
expect { subject.all }.to raise_error(described_class::PaginationException, /is incompatible with pagination/)
|
|
109
110
|
end
|
|
110
111
|
end
|
|
111
112
|
end
|
|
112
113
|
end
|
|
113
114
|
|
|
114
|
-
context '.order' do
|
|
115
|
-
subject {described_class.order(query, pagination.order) }
|
|
116
|
-
|
|
115
|
+
context '.order' do
|
|
116
|
+
subject { described_class.order(query, pagination.order) }
|
|
117
|
+
|
|
117
118
|
it 'does not change the query with an empty struct' do
|
|
118
119
|
expect(subject).to be(query)
|
|
119
120
|
end
|
|
120
121
|
|
|
121
|
-
it_behaves_like 'sorts_the_same', 'simple_name',
|
|
122
|
-
|
|
123
|
-
it_behaves_like 'sorts_the_same', '-simple_name',
|
|
124
|
-
|
|
125
|
-
it_behaves_like 'sorts_the_same', '-simple_name,id',
|
|
126
|
-
|
|
127
|
-
it_behaves_like 'sorts_the_same', '-simple_name,-id',
|
|
128
|
-
|
|
122
|
+
it_behaves_like 'sorts_the_same', 'simple_name',
|
|
123
|
+
::ActiveBook.order(simple_name: :asc)
|
|
124
|
+
it_behaves_like 'sorts_the_same', '-simple_name',
|
|
125
|
+
::ActiveBook.order(simple_name: :desc)
|
|
126
|
+
it_behaves_like 'sorts_the_same', '-simple_name,id',
|
|
127
|
+
::ActiveBook.order(simple_name: :desc, id: :asc)
|
|
128
|
+
it_behaves_like 'sorts_the_same', '-simple_name,-id',
|
|
129
|
+
::ActiveBook.order(simple_name: :desc, id: :desc)
|
|
129
130
|
end
|
|
130
131
|
end
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'spec_helper'
|
|
2
4
|
|
|
3
5
|
describe Praxis::Extensions::Rendering do
|
|
4
|
-
|
|
5
6
|
let(:test_class) do
|
|
6
|
-
Struct.new(:media_type, :expanded_fields, :response, :request) do |
|
|
7
|
+
Struct.new(:media_type, :expanded_fields, :response, :request) do |_klass|
|
|
7
8
|
include Praxis::Extensions::Rendering
|
|
8
9
|
end
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
let(:media_type) { Person }
|
|
12
|
-
let(:expanded_fields) { {id: true, name: true} }
|
|
13
|
+
let(:expanded_fields) { { id: true, name: true } }
|
|
13
14
|
let(:response) { double('response', headers: {}) }
|
|
14
15
|
let(:request) { double('request') }
|
|
15
16
|
|
|
16
|
-
let(:object) { {id: '1', name: 'bob', href: '/people/bob'} }
|
|
17
|
+
let(:object) { { id: '1', name: 'bob', href: '/people/bob' } }
|
|
17
18
|
subject(:instance) { test_class.new(media_type, expanded_fields, response, request) }
|
|
18
19
|
|
|
19
20
|
context '#render' do
|
|
@@ -26,7 +27,7 @@ describe Praxis::Extensions::Rendering do
|
|
|
26
27
|
context '#display' do
|
|
27
28
|
context 'without exception' do
|
|
28
29
|
before do
|
|
29
|
-
expect(response).to receive(:body=).with({id: 1, name: 'bob'})
|
|
30
|
+
expect(response).to receive(:body=).with({ id: 1, name: 'bob' })
|
|
30
31
|
end
|
|
31
32
|
|
|
32
33
|
subject!(:output) { instance.display(object) }
|
|
@@ -43,15 +44,15 @@ describe Praxis::Extensions::Rendering do
|
|
|
43
44
|
context 'with a rendering exception' do
|
|
44
45
|
let(:handler_params) do
|
|
45
46
|
{
|
|
46
|
-
summary:
|
|
47
|
-
|
|
47
|
+
summary: 'Circular Rendering Error when rendering response. ' \
|
|
48
|
+
'Please especify a view to narrow the dependent fields, or narrow your field set.',
|
|
48
49
|
exception: circular_exception,
|
|
49
50
|
request: request,
|
|
50
51
|
stage: :action,
|
|
51
52
|
errors: nil
|
|
52
53
|
}
|
|
53
54
|
end
|
|
54
|
-
let(:circular_exception){ Praxis::Renderer::CircularRenderingError.new(object,
|
|
55
|
+
let(:circular_exception) { Praxis::Renderer::CircularRenderingError.new(object, 'ctx') }
|
|
55
56
|
it 'catches a circular rendering exception' do
|
|
56
57
|
expect(instance).to receive(:render).and_raise(circular_exception)
|
|
57
58
|
expect(Praxis::Application.instance.validation_handler).to receive(:handle!).with(handler_params)
|
|
@@ -59,5 +60,4 @@ describe Praxis::Extensions::Rendering do
|
|
|
59
60
|
end
|
|
60
61
|
end
|
|
61
62
|
end
|
|
62
|
-
|
|
63
63
|
end
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'active_record'
|
|
2
4
|
|
|
3
5
|
require 'praxis/mapper/active_model_compat'
|
|
4
6
|
|
|
5
7
|
# Creates a new in-memory DB, and the necessary tables (and mini-seeds) for the models in this file
|
|
6
8
|
def create_tables
|
|
7
|
-
|
|
8
9
|
ActiveRecord::Base.establish_connection(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
adapter: 'sqlite3',
|
|
11
|
+
dbfile: ':memory:',
|
|
12
|
+
database: ':memory:'
|
|
12
13
|
)
|
|
13
|
-
|
|
14
|
+
|
|
14
15
|
ActiveRecord::Schema.define do
|
|
15
16
|
ActiveRecord::Migration.suppress_messages do
|
|
16
17
|
create_table :active_books do |table|
|
|
@@ -19,20 +20,20 @@ def create_tables
|
|
|
19
20
|
table.column :category_uuid, :string
|
|
20
21
|
table.column :author_id, :integer
|
|
21
22
|
end
|
|
22
|
-
|
|
23
|
+
|
|
23
24
|
create_table :active_authors do |table|
|
|
24
25
|
table.column :name, :string
|
|
25
26
|
end
|
|
26
|
-
|
|
27
|
+
|
|
27
28
|
create_table :active_categories do |table|
|
|
28
29
|
table.column :uuid, :string
|
|
29
30
|
table.column :name, :string
|
|
30
31
|
end
|
|
31
|
-
|
|
32
|
+
|
|
32
33
|
create_table :active_tags do |table|
|
|
33
34
|
table.column :name, :string
|
|
34
35
|
end
|
|
35
|
-
|
|
36
|
+
|
|
36
37
|
create_table :active_taggings do |table|
|
|
37
38
|
table.column :book_id, :integer
|
|
38
39
|
table.column :tag_id, :integer
|
|
@@ -51,7 +52,7 @@ class ActiveBook < ActiveRecord::Base
|
|
|
51
52
|
belongs_to :author, class_name: 'ActiveAuthor'
|
|
52
53
|
|
|
53
54
|
has_many :taggings, class_name: 'ActiveTagging', foreign_key: :book_id
|
|
54
|
-
has_many :primary_taggings,
|
|
55
|
+
has_many :primary_taggings, -> { where(label: 'primary') }, class_name: 'ActiveTagging', foreign_key: :book_id
|
|
55
56
|
|
|
56
57
|
has_many :tags, class_name: 'ActiveTag', through: :taggings
|
|
57
58
|
has_many :primary_tags, class_name: 'ActiveTag', through: :primary_taggings, source: :tag
|
|
@@ -79,7 +80,6 @@ class ActiveTagging < ActiveRecord::Base
|
|
|
79
80
|
belongs_to :tag, class_name: 'ActiveTag', foreign_key: :tag_id
|
|
80
81
|
end
|
|
81
82
|
|
|
82
|
-
|
|
83
83
|
# A set of resource classes for use in specs
|
|
84
84
|
class ActiveBaseResource < Praxis::Mapper::Resource
|
|
85
85
|
end
|
|
@@ -102,63 +102,47 @@ class ActiveBookResource < ActiveBaseResource
|
|
|
102
102
|
model ActiveBook
|
|
103
103
|
|
|
104
104
|
filters_mapping(
|
|
105
|
-
id: :id,
|
|
106
|
-
# category_uuid: :category_uuid #NOTE: we do not need to define same-name-mappings if they exist
|
|
107
105
|
'fake_nested.name': 'simple_name',
|
|
108
106
|
'name': 'simple_name',
|
|
109
107
|
'name_is_not': lambda do |spec| # Silly way to use a proc, but good enough for testing
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
'
|
|
113
|
-
'author.name': 'author.name',
|
|
114
|
-
'taggings.label': 'taggings.label',
|
|
115
|
-
'taggings.tag_id': 'taggings.tag_id',
|
|
116
|
-
'taggings.tag.taggings.tag_id': 'taggings.tag.taggings.tag_id',
|
|
117
|
-
'tags.name': 'tags.name',
|
|
118
|
-
'primary_tags.name': 'primary_tags.name',
|
|
119
|
-
'category.name': 'category.name',
|
|
120
|
-
'category.books.name': 'category.books.simple_name',
|
|
121
|
-
'category.books.taggings.tag_id': 'category.books.taggings.tag_id',
|
|
122
|
-
'category.books.taggings.label': 'category.books.taggings.label',
|
|
123
|
-
'primary_tags': 'primary_tags',
|
|
124
|
-
'category.books.taggings': 'category.books.taggings',
|
|
108
|
+
{ name: :simple_name, value: spec[:value], op: '!=' } # Can be an array for multiple conditions as well
|
|
109
|
+
end,
|
|
110
|
+
'category.books.name': 'category.books.simple_name'
|
|
125
111
|
)
|
|
126
112
|
# Forces to add an extra column (added_column)...and yet another (author_id) that will serve
|
|
127
113
|
# to check that if that's already automatically added due to an association, it won't interfere or duplicate
|
|
128
|
-
property :author, dependencies: [
|
|
114
|
+
property :author, dependencies: %i[author added_column author_id]
|
|
129
115
|
|
|
130
116
|
property :name, dependencies: [:simple_name]
|
|
131
117
|
end
|
|
132
118
|
|
|
133
|
-
|
|
134
119
|
def seed_data
|
|
135
|
-
cat1 = ActiveCategory.create(
|
|
136
|
-
cat2 = ActiveCategory.create(
|
|
137
|
-
|
|
138
|
-
author1 = ActiveAuthor.create(
|
|
139
|
-
author2 = ActiveAuthor.create(
|
|
140
|
-
author3 = ActiveAuthor.create(
|
|
141
|
-
|
|
142
|
-
tag_blue = ActiveTag.create(id: 1
|
|
143
|
-
tag_red = ActiveTag.create(id: 2
|
|
144
|
-
tag_green = ActiveTag.create(id: 3
|
|
145
|
-
|
|
146
|
-
book1 = ActiveBook.create(
|
|
120
|
+
cat1 = ActiveCategory.create(id: 1, uuid: 'deadbeef1', name: 'cat1')
|
|
121
|
+
cat2 = ActiveCategory.create(id: 2, uuid: 'deadbeef2', name: 'cat2')
|
|
122
|
+
|
|
123
|
+
author1 = ActiveAuthor.create(id: 11, name: 'author1')
|
|
124
|
+
author2 = ActiveAuthor.create(id: 22, name: 'author2')
|
|
125
|
+
author3 = ActiveAuthor.create(id: 33, name: nil)
|
|
126
|
+
|
|
127
|
+
tag_blue = ActiveTag.create(id: 1, name: 'blue')
|
|
128
|
+
tag_red = ActiveTag.create(id: 2, name: 'red')
|
|
129
|
+
tag_green = ActiveTag.create(id: 3, name: 'green')
|
|
130
|
+
|
|
131
|
+
book1 = ActiveBook.create(id: 1, simple_name: 'Book1', category_uuid: 'deadbeef1')
|
|
147
132
|
book1.author = author1
|
|
148
133
|
book1.category = cat1
|
|
149
134
|
book1.save
|
|
150
135
|
ActiveTagging.create(book: book1, tag: tag_blue, label: 'primary')
|
|
151
136
|
ActiveTagging.create(book: book1, tag: tag_red)
|
|
152
137
|
ActiveTagging.create(book: book1, tag: tag_green, label: 'primary')
|
|
153
|
-
|
|
154
138
|
|
|
155
|
-
book2 = ActiveBook.create(
|
|
139
|
+
book2 = ActiveBook.create(id: 2, simple_name: 'Book2', category_uuid: 'deadbeef1')
|
|
156
140
|
book2.author = author2
|
|
157
141
|
book2.category = cat2
|
|
158
142
|
book2.save
|
|
159
143
|
ActiveTagging.create(book: book2, tag: tag_red, label: 'primary')
|
|
160
144
|
|
|
161
|
-
book3 = ActiveBook.create(
|
|
145
|
+
book3 = ActiveBook.create(id: 3, simple_name: 'Book3', category_uuid: 'deadbeef1')
|
|
162
146
|
book3.author = author3
|
|
163
147
|
book3.save
|
|
164
148
|
ActiveTagging.create(book: book3, tag: tag_red, label: 'primary')
|
|
@@ -166,10 +150,9 @@ def seed_data
|
|
|
166
150
|
# More stuff
|
|
167
151
|
|
|
168
152
|
10.times do |i|
|
|
169
|
-
bid = 1000+i
|
|
170
|
-
ActiveBook.create(
|
|
153
|
+
bid = 1000 + i
|
|
154
|
+
ActiveBook.create(id: bid, simple_name: "Book#{bid}")
|
|
171
155
|
end
|
|
172
|
-
|
|
173
156
|
end
|
|
174
157
|
|
|
175
|
-
seed_data
|
|
158
|
+
seed_data
|