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,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
|