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.
Files changed (235) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +54 -0
  3. data/.simplecov +3 -1
  4. data/.travis.yml +2 -1
  5. data/CHANGELOG.md +19 -0
  6. data/CONTRIBUTING.md +2 -79
  7. data/Gemfile +5 -1
  8. data/Guardfile +6 -4
  9. data/LICENSE +0 -2
  10. data/MAINTAINERS.md +1 -0
  11. data/README.md +15 -22
  12. data/Rakefile +4 -2
  13. data/bin/praxis +55 -58
  14. data/lib/praxis/action_definition/headers_dsl_compiler.rb +5 -6
  15. data/lib/praxis/action_definition.rb +65 -95
  16. data/lib/praxis/api_definition.rb +21 -29
  17. data/lib/praxis/api_general_info.rb +55 -66
  18. data/lib/praxis/application.rb +15 -32
  19. data/lib/praxis/blueprint.rb +80 -73
  20. data/lib/praxis/bootloader.rb +24 -33
  21. data/lib/praxis/bootloader_stages/environment.rb +5 -10
  22. data/lib/praxis/bootloader_stages/file_loader.rb +3 -6
  23. data/lib/praxis/bootloader_stages/plugin_config_load.rb +4 -6
  24. data/lib/praxis/bootloader_stages/plugin_config_prepare.rb +2 -2
  25. data/lib/praxis/bootloader_stages/plugin_loader.rb +3 -7
  26. data/lib/praxis/bootloader_stages/plugin_setup.rb +3 -3
  27. data/lib/praxis/bootloader_stages/routing.rb +5 -8
  28. data/lib/praxis/bootloader_stages/subgroup_loader.rb +2 -10
  29. data/lib/praxis/bootloader_stages/warn_unloaded_files.rb +15 -19
  30. data/lib/praxis/callbacks.rb +12 -11
  31. data/lib/praxis/collection.rb +11 -14
  32. data/lib/praxis/config.rb +17 -28
  33. data/lib/praxis/config_hash.rb +2 -1
  34. data/lib/praxis/controller.rb +7 -6
  35. data/lib/praxis/dispatcher.rb +34 -42
  36. data/lib/praxis/docs/open_api/info_object.rb +11 -8
  37. data/lib/praxis/docs/open_api/media_type_object.rb +18 -17
  38. data/lib/praxis/docs/open_api/operation_object.rb +7 -4
  39. data/lib/praxis/docs/open_api/parameter_object.rb +17 -14
  40. data/lib/praxis/docs/open_api/paths_object.rb +11 -9
  41. data/lib/praxis/docs/open_api/request_body_object.rb +14 -13
  42. data/lib/praxis/docs/open_api/response_object.rb +24 -18
  43. data/lib/praxis/docs/open_api/responses_object.rb +3 -1
  44. data/lib/praxis/docs/open_api/schema_object.rb +61 -29
  45. data/lib/praxis/docs/open_api/server_object.rb +5 -2
  46. data/lib/praxis/docs/open_api/tag_object.rb +9 -6
  47. data/lib/praxis/docs/open_api_generator.rb +114 -150
  48. data/lib/praxis/endpoint_definition.rb +60 -77
  49. data/lib/praxis/error_handler.rb +2 -2
  50. data/lib/praxis/exception.rb +2 -0
  51. data/lib/praxis/exceptions/config.rb +3 -1
  52. data/lib/praxis/exceptions/config_load.rb +2 -0
  53. data/lib/praxis/exceptions/config_validation.rb +3 -1
  54. data/lib/praxis/exceptions/invalid_configuration.rb +3 -1
  55. data/lib/praxis/exceptions/invalid_response.rb +3 -1
  56. data/lib/praxis/exceptions/invalid_trait.rb +3 -1
  57. data/lib/praxis/exceptions/stage_not_found.rb +3 -1
  58. data/lib/praxis/exceptions/validation.rb +4 -3
  59. data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +163 -149
  60. data/lib/praxis/extensions/attribute_filtering/active_record_patches/5x.rb +18 -13
  61. data/lib/praxis/extensions/attribute_filtering/active_record_patches/6_0.rb +13 -9
  62. data/lib/praxis/extensions/attribute_filtering/active_record_patches/6_1_plus.rb +14 -11
  63. data/lib/praxis/extensions/attribute_filtering/active_record_patches.rb +12 -9
  64. data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +8 -5
  65. data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +89 -65
  66. data/lib/praxis/extensions/attribute_filtering/filters_parser.rb +68 -62
  67. data/lib/praxis/extensions/attribute_filtering.rb +3 -1
  68. data/lib/praxis/extensions/field_expansion.rb +6 -4
  69. data/lib/praxis/extensions/field_selection/active_record_query_selector.rb +10 -8
  70. data/lib/praxis/extensions/field_selection/field_selector.rb +91 -92
  71. data/lib/praxis/extensions/field_selection/sequel_query_selector.rb +12 -12
  72. data/lib/praxis/extensions/field_selection.rb +3 -1
  73. data/lib/praxis/extensions/pagination/active_record_pagination_handler.rb +6 -4
  74. data/lib/praxis/extensions/pagination/header_generator.rb +16 -11
  75. data/lib/praxis/extensions/pagination/ordering_params.rb +29 -28
  76. data/lib/praxis/extensions/pagination/pagination_handler.rb +44 -42
  77. data/lib/praxis/extensions/pagination/pagination_params.rb +29 -48
  78. data/lib/praxis/extensions/pagination/sequel_pagination_handler.rb +8 -7
  79. data/lib/praxis/extensions/pagination.rb +10 -15
  80. data/lib/praxis/extensions/rails_compat/request_methods.rb +3 -4
  81. data/lib/praxis/extensions/rails_compat.rb +2 -0
  82. data/lib/praxis/extensions/rendering.rb +12 -12
  83. data/lib/praxis/field_expander.rb +8 -9
  84. data/lib/praxis/file_group.rb +8 -12
  85. data/lib/praxis/finalizable.rb +1 -0
  86. data/lib/praxis/handlers/json.rb +5 -2
  87. data/lib/praxis/handlers/plain.rb +2 -1
  88. data/lib/praxis/handlers/www_form.rb +6 -3
  89. data/lib/praxis/handlers/{xml-sample.rb → xml_sample.rb} +26 -22
  90. data/lib/praxis/mapper/active_model_compat.rb +13 -10
  91. data/lib/praxis/mapper/resource.rb +196 -181
  92. data/lib/praxis/mapper/selector_generator.rb +106 -112
  93. data/lib/praxis/mapper/sequel_compat.rb +70 -67
  94. data/lib/praxis/media_type.rb +2 -2
  95. data/lib/praxis/media_type_identifier.rb +26 -22
  96. data/lib/praxis/middleware_app.rb +18 -15
  97. data/lib/praxis/multipart/parser.rb +46 -51
  98. data/lib/praxis/multipart/part.rb +78 -110
  99. data/lib/praxis/notifications.rb +2 -4
  100. data/lib/praxis/plugin.rb +11 -18
  101. data/lib/praxis/plugin_concern.rb +12 -15
  102. data/lib/praxis/plugins/mapper_plugin.rb +15 -13
  103. data/lib/praxis/plugins/pagination_plugin.rb +8 -6
  104. data/lib/praxis/plugins/rails_plugin.rb +33 -28
  105. data/lib/praxis/renderer.rb +11 -15
  106. data/lib/praxis/request.rb +48 -44
  107. data/lib/praxis/request_stages/action.rb +4 -6
  108. data/lib/praxis/request_stages/load_request.rb +2 -4
  109. data/lib/praxis/request_stages/request_stage.rb +19 -23
  110. data/lib/praxis/request_stages/response.rb +4 -6
  111. data/lib/praxis/request_stages/validate.rb +3 -5
  112. data/lib/praxis/request_stages/validate_params_and_headers.rb +15 -22
  113. data/lib/praxis/request_stages/validate_payload.rb +25 -28
  114. data/lib/praxis/request_superclassing.rb +3 -3
  115. data/lib/praxis/resource_definition.rb +1 -0
  116. data/lib/praxis/response.rb +24 -26
  117. data/lib/praxis/response_definition.rb +77 -122
  118. data/lib/praxis/response_template.rb +11 -15
  119. data/lib/praxis/responses/http.rb +23 -44
  120. data/lib/praxis/responses/internal_server_error.rb +18 -21
  121. data/lib/praxis/responses/multipart_ok.rb +4 -9
  122. data/lib/praxis/responses/validation_error.rb +8 -15
  123. data/lib/praxis/route.rb +8 -10
  124. data/lib/praxis/router/rack.rb +13 -7
  125. data/lib/praxis/router/simple.rb +10 -5
  126. data/lib/praxis/router.rb +27 -34
  127. data/lib/praxis/routing_config.rb +52 -29
  128. data/lib/praxis/simple_media_type.rb +5 -8
  129. data/lib/praxis/stage.rb +17 -25
  130. data/lib/praxis/tasks/api_docs.rb +17 -16
  131. data/lib/praxis/tasks/console.rb +3 -1
  132. data/lib/praxis/tasks/environment.rb +2 -0
  133. data/lib/praxis/tasks/routes.rb +26 -24
  134. data/lib/praxis/tasks.rb +3 -1
  135. data/lib/praxis/trait.rb +37 -46
  136. data/lib/praxis/types/fuzzy_hash.rb +13 -14
  137. data/lib/praxis/types/media_type_common.rb +11 -10
  138. data/lib/praxis/types/multipart_array/part_definition.rb +14 -17
  139. data/lib/praxis/types/multipart_array.rb +100 -115
  140. data/lib/praxis/validation_handler.rb +5 -3
  141. data/lib/praxis/version.rb +3 -1
  142. data/lib/praxis.rb +4 -5
  143. data/praxis.gemspec +22 -21
  144. data/spec/functional_spec.rb +44 -56
  145. data/spec/praxis/action_definition_spec.rb +39 -48
  146. data/spec/praxis/api_definition_spec.rb +45 -47
  147. data/spec/praxis/api_general_info_spec.rb +28 -29
  148. data/spec/praxis/application_spec.rb +18 -14
  149. data/spec/praxis/blueprint_spec.rb +33 -34
  150. data/spec/praxis/bootloader_spec.rb +32 -30
  151. data/spec/praxis/callbacks_spec.rb +37 -37
  152. data/spec/praxis/collection_spec.rb +18 -25
  153. data/spec/praxis/config_hash_spec.rb +5 -4
  154. data/spec/praxis/config_spec.rb +27 -26
  155. data/spec/praxis/controller_spec.rb +8 -9
  156. data/spec/praxis/endpoint_definition_spec.rb +25 -32
  157. data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +171 -114
  158. data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +22 -21
  159. data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +112 -60
  160. data/spec/praxis/extensions/attribute_filtering/filters_parser_spec.rb +37 -38
  161. data/spec/praxis/extensions/field_expansion_spec.rb +8 -10
  162. data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +14 -13
  163. data/spec/praxis/extensions/field_selection/field_selector_spec.rb +9 -16
  164. data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +50 -49
  165. data/spec/praxis/extensions/pagination/active_record_pagination_handler_spec.rb +32 -31
  166. data/spec/praxis/extensions/rendering_spec.rb +9 -9
  167. data/spec/praxis/extensions/support/spec_resources_active_model.rb +32 -49
  168. data/spec/praxis/extensions/support/spec_resources_sequel.rb +48 -48
  169. data/spec/praxis/field_expander_spec.rb +6 -5
  170. data/spec/praxis/file_group_spec.rb +3 -1
  171. data/spec/praxis/handlers/json_spec.rb +6 -5
  172. data/spec/praxis/mapper/resource_spec.rb +39 -29
  173. data/spec/praxis/mapper/selector_generator_spec.rb +80 -46
  174. data/spec/praxis/media_type_identifier_spec.rb +13 -10
  175. data/spec/praxis/media_type_spec.rb +12 -12
  176. data/spec/praxis/middleware_app_spec.rb +23 -22
  177. data/spec/praxis/multipart/parser_spec.rb +7 -9
  178. data/spec/praxis/notifications_spec.rb +4 -4
  179. data/spec/praxis/plugin_concern_spec.rb +5 -6
  180. data/spec/praxis/renderer_spec.rb +10 -9
  181. data/spec/praxis/request_spec.rb +38 -41
  182. data/spec/praxis/request_stages/action_spec.rb +14 -15
  183. data/spec/praxis/request_stages/request_stage_spec.rb +30 -41
  184. data/spec/praxis/request_stages/validate_spec.rb +3 -1
  185. data/spec/praxis/response_definition_spec.rb +79 -92
  186. data/spec/praxis/response_spec.rb +35 -40
  187. data/spec/praxis/responses/internal_server_error_spec.rb +6 -9
  188. data/spec/praxis/responses/validation_error_spec.rb +17 -18
  189. data/spec/praxis/route_spec.rb +4 -7
  190. data/spec/praxis/router_spec.rb +69 -79
  191. data/spec/praxis/routing_config_spec.rb +15 -14
  192. data/spec/praxis/stage_spec.rb +56 -53
  193. data/spec/praxis/trait_spec.rb +17 -17
  194. data/spec/praxis/types/fuzzy_hash_spec.rb +11 -9
  195. data/spec/praxis/types/multipart_array/part_definition_spec.rb +3 -2
  196. data/spec/praxis/types/multipart_array_spec.rb +33 -48
  197. data/spec/spec_app/app/concerns/authenticated.rb +5 -5
  198. data/spec/spec_app/app/concerns/basic_api.rb +3 -1
  199. data/spec/spec_app/app/concerns/log_wrapper.rb +5 -3
  200. data/spec/spec_app/app/controllers/base_class.rb +6 -5
  201. data/spec/spec_app/app/controllers/instances.rb +31 -34
  202. data/spec/spec_app/app/controllers/volumes.rb +6 -6
  203. data/spec/spec_app/app/responses/multipart.rb +1 -2
  204. data/spec/spec_app/app/responses/other_response.rb +2 -2
  205. data/spec/spec_app/config/environment.rb +19 -6
  206. data/spec/spec_app/config.ru +4 -3
  207. data/spec/spec_app/design/api.rb +13 -15
  208. data/spec/spec_app/design/media_types/instance.rb +6 -6
  209. data/spec/spec_app/design/media_types/volume.rb +2 -1
  210. data/spec/spec_app/design/media_types/volume_snapshot.rb +2 -1
  211. data/spec/spec_app/design/resources/instances.rb +11 -17
  212. data/spec/spec_app/design/resources/volume_snapshots.rb +4 -5
  213. data/spec/spec_app/design/resources/volumes.rb +4 -5
  214. data/spec/spec_helper.rb +11 -13
  215. data/spec/support/be_deep_equal_matcher.rb +5 -0
  216. data/spec/support/spec_authorization_plugin.rb +7 -12
  217. data/spec/support/spec_blueprints.rb +5 -4
  218. data/spec/support/spec_complex_authentication_plugin.rb +17 -34
  219. data/spec/support/spec_endpoint_definitions.rb +2 -3
  220. data/spec/support/spec_media_types.rb +28 -35
  221. data/spec/support/spec_resources.rb +22 -16
  222. data/spec/support/spec_simple_authentication_plugin.rb +5 -9
  223. data/tasks/loader.thor +4 -2
  224. data/tasks/thor/app.rb +7 -5
  225. data/tasks/thor/example.rb +23 -22
  226. data/tasks/thor/model.rb +7 -7
  227. data/tasks/thor/scaffold.rb +23 -23
  228. data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +0 -8
  229. data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +1 -2
  230. metadata +72 -84
  231. data/MAINTAINERS +0 -2
  232. data/TODO.md +0 -25
  233. data/spec/praxis/api_resource_spec.rb +0 -0
  234. data/spec/praxis/dispatcher_spec.rb +0 -0
  235. 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.rb'
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
- " " + line.strip
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: ['deadbeef1','deadbeef2'])
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
- }.to raise_error(
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
- ActiveBook.where.missing(:primary_tags)
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
- ActiveBook.left_outer_joins(:primary_tags).where.not('primary_tags.id' => nil)
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
- ActiveBook.left_outer_joins(category: { books: :taggings }).where('category.books.taggings.id' => nil)
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
- ActiveBook.left_outer_joins(category: { books: :taggings }).where.not('category.books.taggings.id' => nil)
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
- SELECT "active_books".* FROM "active_books"
165
- LEFT OUTER JOIN
166
- "active_authors" "#{PREF}/author" ON "#{PREF}/author"."id" = "active_books"."author_id"
167
- SQL
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
- WHERE ("#{PREF}/author"."id" IS NOT NULL) AND ("#{PREF}/author"."id" = 11)
173
- SQL
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
- WHERE ("#{PREF}/author"."id" IS NOT NULL) AND ("#{PREF}/author"."id" IN (11,22))
180
- SQL
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
- WHERE ("#{PREF}/author"."id" IS NOT NULL) AND ("#{PREF}/author"."id" <> 11)
187
- SQL
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
- WHERE ("#{PREF}/author"."id" IS NOT NULL) AND ("#{PREF}/author"."id" NOT IN (11,888))
194
- SQL
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
- WHERE ("#{PREF}/author"."id" IS NOT NULL) AND ("#{PREF}/author"."id" > 1)
201
- SQL
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
- WHERE ("#{PREF}/author"."id" IS NOT NULL) AND ("#{PREF}/author"."id" < 22)
208
- SQL
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
- WHERE ("#{PREF}/author"."id" IS NOT NULL) AND ("#{PREF}/author"."id" >= 22)
215
- SQL
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
- WHERE ("#{PREF}/author"."id" IS NOT NULL) AND ("#{PREF}/author"."id" <= 22)
222
- SQL
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
- WHERE ("#{PREF}/author"."id" IS NOT NULL) AND ("#{PREF}/author"."id" IS NOT NULL)
229
- SQL
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
- WHERE ("#{PREF}/author"."id" IS NOT NULL) AND ("#{PREF}/author"."name" IS NULL)
236
- SQL
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
- ActiveBook.joins(category: { books: :taggings }).where('active_taggings.tag_id': 1).where('active_taggings.label': 'primary')
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
- SELECT "active_books".* FROM "active_books"
272
- LEFT OUTER JOIN "active_categories" ON "active_categories"."uuid" = "active_books"."category_uuid"
273
- LEFT OUTER JOIN "active_books" "books_active_categories" ON "books_active_categories"."category_uuid" = "active_categories"."uuid"
274
- LEFT OUTER JOIN "active_taggings" "#{PREF}/category/books/taggings" ON "/category/books/taggings"."book_id" = "books_active_categories"."id"
275
- WHERE ("#{PREF}/category/books/taggings"."id" IS NOT NULL) AND ("#{PREF}/category/books/taggings"."tag_id" = 1)
276
- AND ("#{PREF}/category/books/taggings"."label" = 'primary')
277
- SQL
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
- ActiveBook.joins(taggings: {tag: :taggings}).where('taggings_active_tags.tag_id=1')
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', simple_name: 'Book1')
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
- .where('simple_name': 'Book1')
302
- .where('books_active_categories.simple_name': 'Book3')
303
+ .where('simple_name': 'Book1')
304
+ .where('books_active_categories.simple_name': 'Book3')
303
305
  it_behaves_like 'subject_matches_sql', <<~SQL
304
- SELECT "active_books".* FROM "active_books"
305
- LEFT OUTER JOIN "active_categories" ON "active_categories"."uuid" = "active_books"."category_uuid"
306
- LEFT OUTER JOIN "active_books" "#{PREF}/category/books" ON "#{PREF}/category/books"."category_uuid" = "active_categories"."uuid"
307
- WHERE ("active_books"."simple_name" = 'Book1')
308
- AND ("#{PREF}/category/books"."id" IS NOT NULL) AND ("#{PREF}/category/books"."simple_name" = 'Book3')
309
- SQL
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
- SQL
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
- .or(ActiveBook.joins(:taggings).where('active_taggings.tag_id' => 2))
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
- .and(ActiveBook.where(simple_name: 'Book1').or(ActiveBook.where(simple_name: 'Book2')))
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
- .or(ActiveBook.joins(:taggings).where('active_taggings.tag_id' => 2))
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
- .or(ActiveBook.where.missing(:category).where(simple_name: "Book1005"))
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', Proc.new do
374
- base=ActiveBook.left_outer_joins(:category,:taggings,:tags)
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
- query = and1.and(and2)
386
+ and1.and(and2)
386
387
  end)
387
388
 
388
389
  it_behaves_like 'subject_matches_sql', <<~SQL
389
- SELECT "active_books".* FROM "active_books"
390
- LEFT OUTER JOIN "active_categories" "/category" ON "/category"."uuid" = "active_books"."category_uuid"
391
- LEFT OUTER JOIN "active_taggings" "/taggings" ON "/taggings"."book_id" = "active_books"."id"
392
- LEFT OUTER JOIN "active_tags" "/tags" ON "/tags"."id" = "/taggings"."tag_id"
393
- WHERE (("/category"."uuid" IS NOT NULL)
394
- AND ("/category"."name" = 'cat2')
395
- OR ("/taggings"."id" IS NOT NULL)
396
- AND ("/taggings"."label" = 'primary')
397
- AND ("/tags"."id" IS NOT NULL)
398
- AND ("/tags"."name" = 'red'))
399
- AND ("active_books"."category_uuid" = 'deadbeef1')
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
- ActiveBook.joins(:primary_tags).where('active_tags.name="blue"')
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,:op,:value)}).to eq([
24
- {name: 'one', op: '>', value: 1},
25
- {name: 'one', op: '<', value: 10},
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,:op,:value)}).to eq([
42
- {name: 'a1', op: '=', value: 1},
43
- {name: 'a2', op: '=', value: 2},
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(['rel1','rel2'])
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,:op,:value)}).to eq([
52
- {name: 'b1', op: '=', value: 11},
53
- {name: 'b2', op: '=', value: 12}
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