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,14 @@
|
|
|
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/attribute_filtering'
|
|
5
7
|
require 'praxis/extensions/attribute_filtering/active_record_filter_query_builder'
|
|
6
8
|
|
|
7
9
|
describe Praxis::Extensions::AttributeFiltering::ActiveRecordFilterQueryBuilder do
|
|
8
10
|
let(:root_resource) { ActiveBookResource }
|
|
9
|
-
let(:filters_map) { root_resource.instance_variable_get(:@_filters_map)}
|
|
11
|
+
let(:filters_map) { root_resource.instance_variable_get(:@_filters_map) }
|
|
10
12
|
let(:base_model) { root_resource.model }
|
|
11
13
|
let(:base_query) { base_model }
|
|
12
14
|
let(:instance) { described_class.new(query: base_query, model: base_model, filters_map: filters_map) }
|
|
@@ -28,7 +30,7 @@ describe Praxis::Extensions::AttributeFiltering::ActiveRecordFilterQueryBuilder
|
|
|
28
30
|
# Strip blank at the beggining (and end) of every line
|
|
29
31
|
# ...and recompose it by adding an extra space at the beginning of each one instead
|
|
30
32
|
exp = expected_sql.split(/\n/).map do |line|
|
|
31
|
-
"
|
|
33
|
+
" #{line.strip}"
|
|
32
34
|
end.join.strip
|
|
33
35
|
expect(gen_sql).to eq(exp)
|
|
34
36
|
end
|
|
@@ -44,7 +46,7 @@ describe Praxis::Extensions::AttributeFiltering::ActiveRecordFilterQueryBuilder
|
|
|
44
46
|
end
|
|
45
47
|
context 'generate' do
|
|
46
48
|
subject { instance.generate(filters) }
|
|
47
|
-
let(:filters) { Praxis::Types::FilteringParams.load(filters_string)}
|
|
49
|
+
let(:filters) { Praxis::Types::FilteringParams.load(filters_string) }
|
|
48
50
|
|
|
49
51
|
context 'with no filters' do
|
|
50
52
|
let(:filters_string) { '' }
|
|
@@ -67,29 +69,29 @@ describe Praxis::Extensions::AttributeFiltering::ActiveRecordFilterQueryBuilder
|
|
|
67
69
|
context 'but if it is a field that does not exist in the model' do
|
|
68
70
|
let(:filters_string) { 'nonexisting=valuehere' }
|
|
69
71
|
it 'it blows up with the right error' do
|
|
70
|
-
expect{subject}.to raise_error(/Filtering by nonexisting is not allowed/)
|
|
72
|
+
expect { subject }.to raise_error(/Filtering by nonexisting is not allowed/)
|
|
71
73
|
end
|
|
72
74
|
end
|
|
73
75
|
end
|
|
74
76
|
context 'that maps to a different name' do
|
|
75
|
-
let(:filters_string) { 'name=Book1'}
|
|
77
|
+
let(:filters_string) { 'name=Book1' }
|
|
76
78
|
it_behaves_like 'subject_equivalent_to', ActiveBook.where(simple_name: 'Book1')
|
|
77
79
|
end
|
|
78
80
|
context 'that is mapped as a nested struct' do
|
|
79
|
-
let(:filters_string) { 'fake_nested.name=Book1'}
|
|
81
|
+
let(:filters_string) { 'fake_nested.name=Book1' }
|
|
80
82
|
it_behaves_like 'subject_equivalent_to', ActiveBook.where(simple_name: 'Book1')
|
|
81
83
|
end
|
|
82
84
|
context 'passing multiple values' do
|
|
83
85
|
context 'without fuzzy matching' do
|
|
84
86
|
let(:filters_string) { 'category_uuid=deadbeef1,deadbeef2' }
|
|
85
|
-
it_behaves_like 'subject_equivalent_to', ActiveBook.where(category_uuid: [
|
|
87
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.where(category_uuid: %w[deadbeef1 deadbeef2])
|
|
86
88
|
end
|
|
87
89
|
context 'with fuzzy matching' do
|
|
88
90
|
let(:filters_string) { 'category_uuid=*deadbeef1,deadbeef2*' }
|
|
89
91
|
it 'is not supported' do
|
|
90
|
-
expect
|
|
92
|
+
expect do
|
|
91
93
|
subject
|
|
92
|
-
|
|
94
|
+
end.to raise_error(
|
|
93
95
|
Praxis::Extensions::AttributeFiltering::MultiMatchWithFuzzyNotAllowedByAdapter,
|
|
94
96
|
/Please use multiple OR clauses instead/
|
|
95
97
|
)
|
|
@@ -100,7 +102,7 @@ describe Praxis::Extensions::AttributeFiltering::ActiveRecordFilterQueryBuilder
|
|
|
100
102
|
|
|
101
103
|
context 'by a field or a related model' do
|
|
102
104
|
context 'for a belongs_to association' do
|
|
103
|
-
let(:filters_string) { 'author.name=author2'}
|
|
105
|
+
let(:filters_string) { 'author.name=author2' }
|
|
104
106
|
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.name' => 'author2')
|
|
105
107
|
end
|
|
106
108
|
context 'for a has_many association' do
|
|
@@ -114,19 +116,19 @@ describe Praxis::Extensions::AttributeFiltering::ActiveRecordFilterQueryBuilder
|
|
|
114
116
|
|
|
115
117
|
context 'by just an association filter condition' do
|
|
116
118
|
context 'for a belongs_to association with NO ROWS' do
|
|
117
|
-
let(:filters_string) { 'category!!'}
|
|
119
|
+
let(:filters_string) { 'category!!' }
|
|
118
120
|
it_behaves_like 'subject_equivalent_to', ActiveBook.where.missing(:category)
|
|
119
121
|
end
|
|
120
122
|
|
|
121
123
|
context 'for a direct has_many association asking for missing rows' do
|
|
122
124
|
let(:filters_string) { 'primary_tags!!' }
|
|
123
|
-
it_behaves_like 'subject_equivalent_to',
|
|
124
|
-
|
|
125
|
+
it_behaves_like 'subject_equivalent_to',
|
|
126
|
+
ActiveBook.where.missing(:primary_tags)
|
|
125
127
|
end
|
|
126
128
|
context 'for a direct has_many association asking for non-missing rows' do
|
|
127
129
|
let(:filters_string) { 'primary_tags!' }
|
|
128
|
-
it_behaves_like 'subject_equivalent_to',
|
|
129
|
-
|
|
130
|
+
it_behaves_like 'subject_equivalent_to',
|
|
131
|
+
ActiveBook.left_outer_joins(:primary_tags).where.not('primary_tags.id' => nil)
|
|
130
132
|
end
|
|
131
133
|
|
|
132
134
|
context 'for a has_many through association with NO ROWS' do
|
|
@@ -141,110 +143,110 @@ describe Praxis::Extensions::AttributeFiltering::ActiveRecordFilterQueryBuilder
|
|
|
141
143
|
|
|
142
144
|
context 'for a 3 levels deep has_many association with NO ROWS' do
|
|
143
145
|
let(:filters_string) { 'category.books.taggings!!' }
|
|
144
|
-
it_behaves_like 'subject_equivalent_to',
|
|
145
|
-
|
|
146
|
+
it_behaves_like 'subject_equivalent_to',
|
|
147
|
+
ActiveBook.left_outer_joins(category: { books: :taggings }).where('category.books.taggings.id' => nil)
|
|
146
148
|
end
|
|
147
|
-
|
|
149
|
+
|
|
148
150
|
context 'for a 3 levels deep has_many association WITH SIME ROWS' do
|
|
149
151
|
let(:filters_string) { 'category.books.taggings!' }
|
|
150
|
-
it_behaves_like 'subject_equivalent_to',
|
|
151
|
-
|
|
152
|
+
it_behaves_like 'subject_equivalent_to',
|
|
153
|
+
ActiveBook.left_outer_joins(category: { books: :taggings }).where.not('category.books.taggings.id' => nil)
|
|
152
154
|
end
|
|
153
|
-
|
|
154
155
|
end
|
|
155
|
-
|
|
156
156
|
end
|
|
157
157
|
|
|
158
158
|
# NOTE: apparently AR when conditions are build with strings in the where clauses (instead of names, etc)
|
|
159
159
|
# it decides to parenthesize them, even when there's only 1 condition. Hence the silly parentization of
|
|
160
160
|
# these SQL fragments here (and others)
|
|
161
161
|
context 'by using all supported operators' do
|
|
162
|
+
# rubocop:disable Lint/ConstantDefinitionInBlock
|
|
162
163
|
PREF = Praxis::Extensions::AttributeFiltering::ALIAS_TABLE_PREFIX
|
|
163
164
|
COMMON_SQL_PREFIX = <<~SQL
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
SELECT "active_books".* FROM "active_books"
|
|
166
|
+
LEFT OUTER JOIN
|
|
167
|
+
"active_authors" "#{PREF}/author" ON "#{PREF}/author"."id" = "active_books"."author_id"
|
|
168
|
+
SQL
|
|
169
|
+
# rubocop:enable Lint/ConstantDefinitionInBlock
|
|
168
170
|
context '=' do
|
|
169
|
-
let(:filters_string) { 'author.id=11'}
|
|
171
|
+
let(:filters_string) { 'author.id=11' }
|
|
170
172
|
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.id = 11')
|
|
171
173
|
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
|
172
|
-
|
|
173
|
-
|
|
174
|
+
WHERE ("#{PREF}/author"."id" IS NOT NULL) AND ("#{PREF}/author"."id" = 11)
|
|
175
|
+
SQL
|
|
174
176
|
end
|
|
175
177
|
context '= (with array)' do
|
|
176
|
-
let(:filters_string) { 'author.id=11,22'}
|
|
178
|
+
let(:filters_string) { 'author.id=11,22' }
|
|
177
179
|
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.id IN (11,22)')
|
|
178
180
|
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
end
|
|
181
|
+
WHERE ("#{PREF}/author"."id" IS NOT NULL) AND ("#{PREF}/author"."id" IN (11,22))
|
|
182
|
+
SQL
|
|
183
|
+
end
|
|
182
184
|
context '!=' do
|
|
183
|
-
let(:filters_string) { 'author.id!=11'}
|
|
185
|
+
let(:filters_string) { 'author.id!=11' }
|
|
184
186
|
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.id <> 11')
|
|
185
187
|
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
|
186
|
-
|
|
187
|
-
|
|
188
|
+
WHERE ("#{PREF}/author"."id" IS NOT NULL) AND ("#{PREF}/author"."id" <> 11)
|
|
189
|
+
SQL
|
|
188
190
|
end
|
|
189
191
|
context '!= (with array)' do
|
|
190
|
-
let(:filters_string) { 'author.id!=11,888'}
|
|
192
|
+
let(:filters_string) { 'author.id!=11,888' }
|
|
191
193
|
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.id NOT IN (11,888)')
|
|
192
194
|
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
|
193
|
-
|
|
194
|
-
|
|
195
|
+
WHERE ("#{PREF}/author"."id" IS NOT NULL) AND ("#{PREF}/author"."id" NOT IN (11,888))
|
|
196
|
+
SQL
|
|
195
197
|
end
|
|
196
198
|
context '>' do
|
|
197
|
-
let(:filters_string) { 'author.id>1'}
|
|
199
|
+
let(:filters_string) { 'author.id>1' }
|
|
198
200
|
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.id > 1')
|
|
199
201
|
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
|
200
|
-
|
|
201
|
-
|
|
202
|
+
WHERE ("#{PREF}/author"."id" IS NOT NULL) AND ("#{PREF}/author"."id" > 1)
|
|
203
|
+
SQL
|
|
202
204
|
end
|
|
203
205
|
context '<' do
|
|
204
|
-
let(:filters_string) { 'author.id<22'}
|
|
206
|
+
let(:filters_string) { 'author.id<22' }
|
|
205
207
|
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.id < 22')
|
|
206
208
|
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
|
207
|
-
|
|
208
|
-
|
|
209
|
+
WHERE ("#{PREF}/author"."id" IS NOT NULL) AND ("#{PREF}/author"."id" < 22)
|
|
210
|
+
SQL
|
|
209
211
|
end
|
|
210
212
|
context '>=' do
|
|
211
|
-
let(:filters_string) { 'author.id>=22'}
|
|
213
|
+
let(:filters_string) { 'author.id>=22' }
|
|
212
214
|
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.id >= 22')
|
|
213
215
|
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
|
214
|
-
|
|
215
|
-
|
|
216
|
+
WHERE ("#{PREF}/author"."id" IS NOT NULL) AND ("#{PREF}/author"."id" >= 22)
|
|
217
|
+
SQL
|
|
216
218
|
end
|
|
217
219
|
context '<=' do
|
|
218
|
-
let(:filters_string) { 'author.id<=22'}
|
|
220
|
+
let(:filters_string) { 'author.id<=22' }
|
|
219
221
|
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.id <= 22')
|
|
220
222
|
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
|
221
|
-
|
|
222
|
-
|
|
223
|
+
WHERE ("#{PREF}/author"."id" IS NOT NULL) AND ("#{PREF}/author"."id" <= 22)
|
|
224
|
+
SQL
|
|
223
225
|
end
|
|
224
226
|
context '!' do
|
|
225
|
-
let(:filters_string) { 'author.id!'}
|
|
227
|
+
let(:filters_string) { 'author.id!' }
|
|
226
228
|
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.id IS NOT NULL')
|
|
227
229
|
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
|
228
|
-
|
|
229
|
-
|
|
230
|
+
WHERE ("#{PREF}/author"."id" IS NOT NULL) AND ("#{PREF}/author"."id" IS NOT NULL)
|
|
231
|
+
SQL
|
|
230
232
|
end
|
|
231
233
|
context '!!' do
|
|
232
|
-
let(:filters_string) { 'author.name!!'}
|
|
234
|
+
let(:filters_string) { 'author.name!!' }
|
|
233
235
|
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.name IS NULL')
|
|
234
236
|
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
end
|
|
237
|
+
WHERE ("#{PREF}/author"."id" IS NOT NULL) AND ("#{PREF}/author"."name" IS NULL)
|
|
238
|
+
SQL
|
|
239
|
+
end
|
|
238
240
|
context 'including LIKE fuzzy queries' do
|
|
239
241
|
context 'LIKE' do
|
|
240
|
-
let(:filters_string) { 'author.name=author*'}
|
|
242
|
+
let(:filters_string) { 'author.name=author*' }
|
|
241
243
|
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.name LIKE "author%"')
|
|
242
244
|
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
|
243
245
|
WHERE ("#{PREF}/author"."id" IS NOT NULL) AND ("#{PREF}/author"."name" LIKE 'author%')
|
|
244
246
|
SQL
|
|
245
247
|
end
|
|
246
248
|
context 'NOT LIKE' do
|
|
247
|
-
let(:filters_string) { 'author.name!=foobar*'}
|
|
249
|
+
let(:filters_string) { 'author.name!=foobar*' }
|
|
248
250
|
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.name NOT LIKE "foobar%"')
|
|
249
251
|
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
|
250
252
|
WHERE ("#{PREF}/author"."id" IS NOT NULL) AND ("#{PREF}/author"."name" NOT LIKE 'foobar%')
|
|
@@ -265,28 +267,28 @@ describe Praxis::Extensions::AttributeFiltering::ActiveRecordFilterQueryBuilder
|
|
|
265
267
|
end
|
|
266
268
|
context 'multiple conditions on a nested relationship' do
|
|
267
269
|
let(:filters_string) { 'category.books.taggings.tag_id=1&category.books.taggings.label=primary' }
|
|
268
|
-
it_behaves_like 'subject_equivalent_to',
|
|
269
|
-
|
|
270
|
+
it_behaves_like 'subject_equivalent_to',
|
|
271
|
+
ActiveBook.joins(category: { books: :taggings }).where('active_taggings.tag_id': 1).where('active_taggings.label': 'primary')
|
|
270
272
|
it_behaves_like 'subject_matches_sql', <<~SQL
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
273
|
+
SELECT "active_books".* FROM "active_books"
|
|
274
|
+
LEFT OUTER JOIN "active_categories" ON "active_categories"."uuid" = "active_books"."category_uuid"
|
|
275
|
+
LEFT OUTER JOIN "active_books" "books_active_categories" ON "books_active_categories"."category_uuid" = "active_categories"."uuid"
|
|
276
|
+
LEFT OUTER JOIN "active_taggings" "#{PREF}/category/books/taggings" ON "/category/books/taggings"."book_id" = "books_active_categories"."id"
|
|
277
|
+
WHERE ("#{PREF}/category/books/taggings"."id" IS NOT NULL) AND ("#{PREF}/category/books/taggings"."tag_id" = 1)
|
|
278
|
+
AND ("#{PREF}/category/books/taggings"."label" = 'primary')
|
|
279
|
+
SQL
|
|
278
280
|
end
|
|
279
281
|
context 'that contain multiple joins to the same table' do
|
|
280
282
|
let(:filters_string) { 'taggings.tag.taggings.tag_id=1' }
|
|
281
|
-
it_behaves_like 'subject_equivalent_to',
|
|
282
|
-
|
|
283
|
+
it_behaves_like 'subject_equivalent_to',
|
|
284
|
+
ActiveBook.joins(taggings: { tag: :taggings }).where('taggings_active_tags.tag_id=1')
|
|
283
285
|
end
|
|
284
286
|
end
|
|
285
287
|
|
|
286
288
|
context 'by multiple fields' do
|
|
287
289
|
context 'adds the where clauses for the top model if fields belong to it' do
|
|
288
290
|
let(:filters_string) { 'category_uuid=deadbeef1&name=Book1' }
|
|
289
|
-
it_behaves_like 'subject_equivalent_to', ActiveBook.where(category_uuid: 'deadbeef1',
|
|
291
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.where(category_uuid: 'deadbeef1', simple_name: 'Book1')
|
|
290
292
|
end
|
|
291
293
|
context 'adds multiple where clauses for same nested relationship join (instead of multiple joins with 1 clause each)' do
|
|
292
294
|
let(:filters_string) { 'taggings.label=primary&taggings.tag_id=2' }
|
|
@@ -298,25 +300,24 @@ describe Praxis::Extensions::AttributeFiltering::ActiveRecordFilterQueryBuilder
|
|
|
298
300
|
context 'when we have a join table condition that has the same field' do
|
|
299
301
|
let(:filters_string) { 'name=Book1&category.books.name=Book3' }
|
|
300
302
|
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(category: :books)
|
|
301
|
-
|
|
302
|
-
|
|
303
|
+
.where('simple_name': 'Book1')
|
|
304
|
+
.where('books_active_categories.simple_name': 'Book3')
|
|
303
305
|
it_behaves_like 'subject_matches_sql', <<~SQL
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
306
|
+
SELECT "active_books".* FROM "active_books"
|
|
307
|
+
LEFT OUTER JOIN "active_categories" ON "active_categories"."uuid" = "active_books"."category_uuid"
|
|
308
|
+
LEFT OUTER JOIN "active_books" "#{PREF}/category/books" ON "#{PREF}/category/books"."category_uuid" = "active_categories"."uuid"#{' '}
|
|
309
|
+
WHERE ("active_books"."simple_name" = 'Book1')
|
|
310
|
+
AND ("#{PREF}/category/books"."id" IS NOT NULL) AND ("#{PREF}/category/books"."simple_name" = 'Book3')
|
|
311
|
+
SQL
|
|
310
312
|
end
|
|
311
313
|
|
|
312
314
|
context 'it qualifies them even if there are no joined tables/conditions at all' do
|
|
313
|
-
let(:filters_string) { 'id=11'}
|
|
315
|
+
let(:filters_string) { 'id=11' }
|
|
314
316
|
it_behaves_like 'subject_matches_sql', <<~SQL
|
|
315
317
|
SELECT "active_books".* FROM "active_books"
|
|
316
318
|
WHERE ("active_books"."id" = 11)
|
|
317
|
-
|
|
319
|
+
SQL
|
|
318
320
|
end
|
|
319
|
-
|
|
320
321
|
end
|
|
321
322
|
end
|
|
322
323
|
|
|
@@ -332,14 +333,14 @@ describe Praxis::Extensions::AttributeFiltering::ActiveRecordFilterQueryBuilder
|
|
|
332
333
|
context 'adds multiple where clauses for same nested relationship join (instead of multiple joins with 1 clause each)' do
|
|
333
334
|
let(:filters_string) { 'taggings.label=primary|taggings.tag_id=2' }
|
|
334
335
|
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:taggings).where('active_taggings.label' => 'primary')
|
|
335
|
-
|
|
336
|
+
.or(ActiveBook.joins(:taggings).where('active_taggings.tag_id' => 2))
|
|
336
337
|
end
|
|
337
338
|
end
|
|
338
339
|
|
|
339
340
|
context 'with combined AND and OR conditions' do
|
|
340
341
|
let(:filters_string) { '(category_uuid=deadbeef1|category_uuid=deadbeef2)&(name=Book1|name=Book2)' }
|
|
341
342
|
it_behaves_like 'subject_equivalent_to', ActiveBook.where(category_uuid: 'deadbeef1').or(ActiveBook.where(category_uuid: 'deadbeef2'))
|
|
342
|
-
|
|
343
|
+
.and(ActiveBook.where(simple_name: 'Book1').or(ActiveBook.where(simple_name: 'Book2')))
|
|
343
344
|
it_behaves_like 'subject_matches_sql', <<~SQL
|
|
344
345
|
SELECT "active_books".* FROM "active_books"
|
|
345
346
|
WHERE ("active_books"."category_uuid" = 'deadbeef1' OR "active_books"."category_uuid" = 'deadbeef2')
|
|
@@ -349,7 +350,7 @@ describe Praxis::Extensions::AttributeFiltering::ActiveRecordFilterQueryBuilder
|
|
|
349
350
|
context 'adds multiple where clauses for same nested relationship join (instead of multiple joins with 1 clause each)' do
|
|
350
351
|
let(:filters_string) { 'taggings.label=primary|taggings.tag_id=2' }
|
|
351
352
|
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:taggings).where('active_taggings.label' => 'primary')
|
|
352
|
-
|
|
353
|
+
.or(ActiveBook.joins(:taggings).where('active_taggings.tag_id' => 2))
|
|
353
354
|
it_behaves_like 'subject_matches_sql', <<~SQL
|
|
354
355
|
SELECT "active_books".* FROM "active_books"
|
|
355
356
|
LEFT OUTER JOIN "active_taggings" "/taggings" ON "/taggings"."book_id" = "active_books"."id"
|
|
@@ -360,7 +361,7 @@ describe Praxis::Extensions::AttributeFiltering::ActiveRecordFilterQueryBuilder
|
|
|
360
361
|
context 'works well with ORs at a parent table along with joined associations with no rows' do
|
|
361
362
|
let(:filters_string) { 'name=Book1005|category!!' }
|
|
362
363
|
it_behaves_like 'subject_equivalent_to', ActiveBook.where.missing(:category)
|
|
363
|
-
|
|
364
|
+
.or(ActiveBook.where.missing(:category).where(simple_name: 'Book1005'))
|
|
364
365
|
it_behaves_like 'subject_matches_sql', <<~SQL
|
|
365
366
|
SELECT "active_books".* FROM "active_books"
|
|
366
367
|
LEFT OUTER JOIN "active_categories" "/category" ON "/category"."uuid" = "active_books"."category_uuid"
|
|
@@ -370,8 +371,8 @@ describe Praxis::Extensions::AttributeFiltering::ActiveRecordFilterQueryBuilder
|
|
|
370
371
|
|
|
371
372
|
context '3-deep AND and OR conditions' do
|
|
372
373
|
let(:filters_string) { '(category.name=cat2|(taggings.label=primary&tags.name=red))&category_uuid=deadbeef1' }
|
|
373
|
-
it_behaves_like('subject_equivalent_to',
|
|
374
|
-
base=ActiveBook.left_outer_joins(:category
|
|
374
|
+
it_behaves_like('subject_equivalent_to', proc do
|
|
375
|
+
base = ActiveBook.left_outer_joins(:category, :taggings, :tags)
|
|
375
376
|
|
|
376
377
|
and1_or1 = base.where('category.name': 'cat2').where.not('category.uuid': nil)
|
|
377
378
|
|
|
@@ -380,32 +381,32 @@ describe Praxis::Extensions::AttributeFiltering::ActiveRecordFilterQueryBuilder
|
|
|
380
381
|
and1_or2 = and1_or2_and1.and(and1_or2_and2)
|
|
381
382
|
|
|
382
383
|
and1 = and1_or1.or(and1_or2)
|
|
383
|
-
and2=base.where(category_uuid: 'deadbeef1')
|
|
384
|
+
and2 = base.where(category_uuid: 'deadbeef1')
|
|
384
385
|
|
|
385
|
-
|
|
386
|
+
and1.and(and2)
|
|
386
387
|
end)
|
|
387
388
|
|
|
388
389
|
it_behaves_like 'subject_matches_sql', <<~SQL
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
390
|
+
SELECT "active_books".* FROM "active_books"#{' '}
|
|
391
|
+
LEFT OUTER JOIN "active_categories" "/category" ON "/category"."uuid" = "active_books"."category_uuid"#{' '}
|
|
392
|
+
LEFT OUTER JOIN "active_taggings" "/taggings" ON "/taggings"."book_id" = "active_books"."id"#{' '}
|
|
393
|
+
LEFT OUTER JOIN "active_tags" "/tags" ON "/tags"."id" = "/taggings"."tag_id"#{' '}
|
|
394
|
+
WHERE (("/category"."uuid" IS NOT NULL)
|
|
395
|
+
AND ("/category"."name" = 'cat2')
|
|
396
|
+
OR ("/taggings"."id" IS NOT NULL)
|
|
397
|
+
AND ("/taggings"."label" = 'primary')
|
|
398
|
+
AND ("/tags"."id" IS NOT NULL)
|
|
399
|
+
AND ("/tags"."name" = 'red'))
|
|
400
|
+
AND ("active_books"."category_uuid" = 'deadbeef1')#{' '}
|
|
400
401
|
SQL
|
|
401
402
|
end
|
|
402
403
|
end
|
|
403
404
|
|
|
404
405
|
context 'ActiveRecord continues to work as expected (with our patches)' do
|
|
405
406
|
context 'using a deep join with repeated tables' do
|
|
406
|
-
subject{ ActiveBook.joins(taggings: {tag: :taggings}).where('taggings_active_tags.tag_id=1') }
|
|
407
|
+
subject { ActiveBook.joins(taggings: { tag: :taggings }).where('taggings_active_tags.tag_id=1') }
|
|
407
408
|
it 'performs query' do
|
|
408
|
-
expect(subject.to_a).to_not be_empty
|
|
409
|
+
expect(subject.to_a).to_not be_empty
|
|
409
410
|
end
|
|
410
411
|
it_behaves_like 'subject_matches_sql', <<~SQL
|
|
411
412
|
SELECT "active_books".* FROM "active_books"
|
|
@@ -416,19 +417,19 @@ describe Praxis::Extensions::AttributeFiltering::ActiveRecordFilterQueryBuilder
|
|
|
416
417
|
SQL
|
|
417
418
|
end
|
|
418
419
|
context 'a deep join with repeated tables with the root AND the join, along with :through joins as well' do
|
|
419
|
-
subject!{ ActiveBook.joins(tags: {books: {taggings: :book}}).where('books_active_taggings.simple_name="Book2"') }
|
|
420
|
+
subject! { ActiveBook.joins(tags: { books: { taggings: :book } }).where('books_active_taggings.simple_name="Book2"') }
|
|
420
421
|
it 'performs query' do
|
|
421
|
-
expect(subject.to_a).to_not be_empty
|
|
422
|
+
expect(subject.to_a).to_not be_empty
|
|
422
423
|
end
|
|
423
424
|
it_behaves_like 'subject_matches_sql', <<~SQL
|
|
424
|
-
SELECT "active_books".* FROM "active_books"
|
|
425
|
+
SELECT "active_books".* FROM "active_books"#{' '}
|
|
425
426
|
INNER JOIN "active_taggings" ON "active_taggings"."book_id" = "active_books"."id"
|
|
426
427
|
INNER JOIN "active_tags" ON "active_tags"."id" = "active_taggings"."tag_id"
|
|
427
|
-
INNER JOIN "active_taggings" "taggings_active_tags_join" ON "taggings_active_tags_join"."tag_id" = "active_tags"."id"
|
|
428
|
-
INNER JOIN "active_books" "books_active_tags" ON "books_active_tags"."id" = "taggings_active_tags_join"."book_id"
|
|
429
|
-
INNER JOIN "active_taggings" "taggings_active_books" ON "taggings_active_books"."book_id" = "books_active_tags"."id"
|
|
428
|
+
INNER JOIN "active_taggings" "taggings_active_tags_join" ON "taggings_active_tags_join"."tag_id" = "active_tags"."id"#{' '}
|
|
429
|
+
INNER JOIN "active_books" "books_active_tags" ON "books_active_tags"."id" = "taggings_active_tags_join"."book_id"#{' '}
|
|
430
|
+
INNER JOIN "active_taggings" "taggings_active_books" ON "taggings_active_books"."book_id" = "books_active_tags"."id"#{' '}
|
|
430
431
|
INNER JOIN "active_books" "books_active_taggings" ON "books_active_taggings"."id" = "taggings_active_books"."book_id"
|
|
431
|
-
WHERE (books_active_taggings.simple_name="Book2")
|
|
432
|
+
WHERE (books_active_taggings.simple_name="Book2")#{' '}
|
|
432
433
|
SQL
|
|
433
434
|
end
|
|
434
435
|
end
|
|
@@ -436,8 +437,8 @@ describe Praxis::Extensions::AttributeFiltering::ActiveRecordFilterQueryBuilder
|
|
|
436
437
|
context 'respects scopes' do
|
|
437
438
|
context 'for a has_many through association' do
|
|
438
439
|
let(:filters_string) { 'primary_tags.name=blue' }
|
|
439
|
-
it_behaves_like 'subject_equivalent_to',
|
|
440
|
-
|
|
440
|
+
it_behaves_like 'subject_equivalent_to',
|
|
441
|
+
ActiveBook.joins(:primary_tags).where('active_tags.name="blue"')
|
|
441
442
|
|
|
442
443
|
it 'adds the association scope clause to the join' do
|
|
443
444
|
inner_join_pieces = subject.to_sql.split('INNER')
|
|
@@ -457,4 +458,60 @@ describe Praxis::Extensions::AttributeFiltering::ActiveRecordFilterQueryBuilder
|
|
|
457
458
|
end
|
|
458
459
|
end
|
|
459
460
|
end
|
|
461
|
+
|
|
462
|
+
context '.valid_path?' do
|
|
463
|
+
it 'suceeds for reachable model columns' do
|
|
464
|
+
expect(described_class.valid_path?(ActiveBook, ['added_column'])).to be_truthy
|
|
465
|
+
expect(described_class.valid_path?(ActiveBook, %w[author books added_column])).to be_truthy
|
|
466
|
+
expect(described_class.valid_path?(ActiveBook, %w[author books simple_name])).to be_truthy
|
|
467
|
+
end
|
|
468
|
+
it 'suceeds for reachable leaf associations' do
|
|
469
|
+
expect(described_class.valid_path?(ActiveBook, ['author'])).to be_truthy
|
|
470
|
+
expect(described_class.valid_path?(ActiveBook, %w[author books])).to be_truthy
|
|
471
|
+
end
|
|
472
|
+
it 'returns false for invalid model columns' do
|
|
473
|
+
expect(described_class.valid_path?(ActiveBook, ['not_a_column'])).to be_falsy
|
|
474
|
+
expect(described_class.valid_path?(ActiveBook, %w[author books not_here])).to be_falsy
|
|
475
|
+
expect(described_class.valid_path?(ActiveBook, %w[author books name])).to be_falsy
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
context '_mapped_filter' do
|
|
480
|
+
let(:root_resource) { ActiveBookResource }
|
|
481
|
+
let(:filters_map) { root_resource.instance_variable_get(:@_filters_map) }
|
|
482
|
+
|
|
483
|
+
context 'for explicitly mapped values' do
|
|
484
|
+
%i[id name name_is_not author.name category.books.taggings.label]
|
|
485
|
+
.each do |name|
|
|
486
|
+
it "suceeds for #{name}" do
|
|
487
|
+
mapped_value = filters_map[name]
|
|
488
|
+
expect(mapped_value).to_not be_nil
|
|
489
|
+
expect(instance.send(:_mapped_filter, name)).to eq(mapped_value)
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
context 'for not mapped values' do
|
|
495
|
+
context 'that are valid model columns/associations paths' do
|
|
496
|
+
%i[added_column author.books.added_column author.books].each do |name|
|
|
497
|
+
it "returns (and caches) the same valid path for #{name}" do
|
|
498
|
+
expect(filters_map[name]).to be_nil
|
|
499
|
+
expect(instance.send(:_mapped_filter, name)).to eq(name)
|
|
500
|
+
|
|
501
|
+
expect(filters_map[name]).to eq(name)
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
context 'that are not model columns/associations paths' do
|
|
506
|
+
%i[not_a_column author.books.not_here].each do |name|
|
|
507
|
+
it "returns nil (and does not cache) for #{name}" do
|
|
508
|
+
expect(filters_map[name]).to be_nil
|
|
509
|
+
expect(instance.send(:_mapped_filter, name)).to eq(nil)
|
|
510
|
+
|
|
511
|
+
expect(filters_map[name]).to eq(nil)
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
end
|
|
515
|
+
end
|
|
516
|
+
end
|
|
460
517
|
end
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'spec_helper'
|
|
2
4
|
|
|
3
5
|
require 'praxis/extensions/attribute_filtering'
|
|
4
6
|
|
|
5
7
|
describe Praxis::Extensions::AttributeFiltering::FilterTreeNode do
|
|
6
|
-
|
|
7
|
-
let(:dummy_object) { double("Fake NodeObject")}
|
|
8
|
+
let(:dummy_object) { double('Fake NodeObject') }
|
|
8
9
|
let(:filters) do
|
|
9
10
|
[
|
|
10
|
-
{name: 'one', op: '>', value: 1, node_object: dummy_object},
|
|
11
|
-
{name: 'one', op: '<', value: 10},
|
|
12
|
-
{name: 'rel1.a1', op: '=', value: 1},
|
|
13
|
-
{name: 'rel1.a2', op: '=', value: 2},
|
|
14
|
-
{name: 'rel1.rel2.b1', op: '=', value: 11},
|
|
15
|
-
{name: 'rel1.rel2.b2', op: '=', value: 12, node_object: dummy_object}
|
|
11
|
+
{ name: 'one', op: '>', value: 1, node_object: dummy_object },
|
|
12
|
+
{ name: 'one', op: '<', value: 10 },
|
|
13
|
+
{ name: 'rel1.a1', op: '=', value: 1 },
|
|
14
|
+
{ name: 'rel1.a2', op: '=', value: 2 },
|
|
15
|
+
{ name: 'rel1.rel2.b1', op: '=', value: 11 },
|
|
16
|
+
{ name: 'rel1.rel2.b2', op: '=', value: 12, node_object: dummy_object }
|
|
16
17
|
]
|
|
17
18
|
end
|
|
18
19
|
context 'initialization' do
|
|
@@ -20,10 +21,10 @@ describe Praxis::Extensions::AttributeFiltering::FilterTreeNode do
|
|
|
20
21
|
it 'holds the top conditions and the child in a TreeNode' do
|
|
21
22
|
expect(subject.path).to eq([])
|
|
22
23
|
expect(subject.conditions.size).to eq(2)
|
|
23
|
-
expect(subject.conditions.map{|i| i.slice(:name
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
expect(subject.conditions.map { |i| i.slice(:name, :op, :value) }).to eq([
|
|
25
|
+
{ name: 'one', op: '>', value: 1 },
|
|
26
|
+
{ name: 'one', op: '<', value: 10 }
|
|
27
|
+
])
|
|
27
28
|
expect(subject.children.keys).to eq(['rel1'])
|
|
28
29
|
expect(subject.children['rel1']).to be_kind_of(described_class)
|
|
29
30
|
end
|
|
@@ -38,20 +39,20 @@ describe Praxis::Extensions::AttributeFiltering::FilterTreeNode do
|
|
|
38
39
|
rel1 = subject.children['rel1']
|
|
39
40
|
expect(rel1.path).to eq(['rel1'])
|
|
40
41
|
expect(rel1.conditions.size).to eq(2)
|
|
41
|
-
expect(rel1.conditions.map{|i| i.slice(:name
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
expect(rel1.conditions.map { |i| i.slice(:name, :op, :value) }).to eq([
|
|
43
|
+
{ name: 'a1', op: '=', value: 1 },
|
|
44
|
+
{ name: 'a2', op: '=', value: 2 }
|
|
45
|
+
])
|
|
45
46
|
expect(rel1.children.keys).to eq(['rel2'])
|
|
46
47
|
expect(rel1.children['rel2']).to be_kind_of(described_class)
|
|
47
48
|
|
|
48
49
|
rel1rel2 = rel1.children['rel2']
|
|
49
|
-
expect(rel1rel2.path).to eq([
|
|
50
|
+
expect(rel1rel2.path).to eq(%w[rel1 rel2])
|
|
50
51
|
expect(rel1rel2.conditions.size).to eq(2)
|
|
51
|
-
expect(rel1rel2.conditions.map{|i| i.slice(:name
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
expect(rel1rel2.conditions.map { |i| i.slice(:name, :op, :value) }).to eq([
|
|
53
|
+
{ name: 'b1', op: '=', value: 11 },
|
|
54
|
+
{ name: 'b2', op: '=', value: 12 }
|
|
55
|
+
])
|
|
55
56
|
expect(rel1rel2.children.keys).to be_empty
|
|
56
57
|
end
|
|
57
58
|
end
|