praxis 2.0.pre.17 → 2.0.pre.21
Sign up to get free protection for your applications and to get access to all the features.
- 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
|