praxis 2.0.pre.11 → 2.0.pre.12
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/.ruby-version +1 -1
- data/CHANGELOG.md +4 -0
- data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +130 -55
- data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +3 -2
- data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +43 -40
- data/lib/praxis/extensions/attribute_filtering/filters_parser.rb +153 -0
- data/lib/praxis/version.rb +1 -1
- data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +243 -173
- data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +25 -6
- data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +78 -17
- data/spec/praxis/extensions/attribute_filtering/filters_parser_spec.rb +131 -0
- metadata +4 -2
data/lib/praxis/version.rb
CHANGED
@@ -14,6 +14,7 @@ describe Praxis::Extensions::AttributeFiltering::ActiveRecordFilterQueryBuilder
|
|
14
14
|
shared_examples 'subject_equivalent_to' do |expected_result|
|
15
15
|
it do
|
16
16
|
loaded_ids = subject.all.map(&:id).sort
|
17
|
+
expected_result = expected_result.call if expected_result.is_a?(Proc)
|
17
18
|
expected_ids = expected_result.all.map(&:id).sort
|
18
19
|
expect(loaded_ids).to_not be_empty
|
19
20
|
expect(loaded_ids).to eq(expected_ids)
|
@@ -23,12 +24,11 @@ describe Praxis::Extensions::AttributeFiltering::ActiveRecordFilterQueryBuilder
|
|
23
24
|
# Poorman's way to compare SQL queries...
|
24
25
|
shared_examples 'subject_matches_sql' do |expected_sql|
|
25
26
|
it do
|
26
|
-
|
27
|
-
gen_sql = subject.all.to_sql.gsub(/[()]/,'')
|
27
|
+
gen_sql = subject.all.to_sql
|
28
28
|
# Strip blank at the beggining (and end) of every line
|
29
29
|
# ...and recompose it by adding an extra space at the beginning of each one instead
|
30
30
|
exp = expected_sql.split(/\n/).map do |line|
|
31
|
-
" " + line.strip
|
31
|
+
" " + line.strip
|
32
32
|
end.join.strip
|
33
33
|
expect(gen_sql).to eq(exp)
|
34
34
|
end
|
@@ -37,7 +37,7 @@ describe Praxis::Extensions::AttributeFiltering::ActiveRecordFilterQueryBuilder
|
|
37
37
|
context 'initialize' do
|
38
38
|
it 'sets the right things to the instance' do
|
39
39
|
instance
|
40
|
-
expect(instance.
|
40
|
+
expect(instance.instance_variable_get(:@initial_query)).to eq(base_query)
|
41
41
|
expect(instance.model).to eq(base_model)
|
42
42
|
expect(instance.filters_map).to eq(filters_map)
|
43
43
|
end
|
@@ -52,208 +52,278 @@ describe Praxis::Extensions::AttributeFiltering::ActiveRecordFilterQueryBuilder
|
|
52
52
|
expect(subject).to be(base_query)
|
53
53
|
end
|
54
54
|
end
|
55
|
-
context '
|
56
|
-
context '
|
57
|
-
|
58
|
-
it_behaves_like 'subject_equivalent_to', ActiveBook.where(category_uuid: 'deadbeef1')
|
59
|
-
end
|
60
|
-
context 'same-name filter mapping works' do
|
61
|
-
context 'even if ther was not a filter explicitly defined for it' do
|
55
|
+
context 'with flat AND conditions' do
|
56
|
+
context 'by a simple field' do
|
57
|
+
context 'that maps to the same name' do
|
62
58
|
let(:filters_string) { 'category_uuid=deadbeef1' }
|
63
59
|
it_behaves_like 'subject_equivalent_to', ActiveBook.where(category_uuid: 'deadbeef1')
|
64
60
|
end
|
61
|
+
context 'same-name filter mapping works' do
|
62
|
+
context 'even if ther was not a filter explicitly defined for it' do
|
63
|
+
let(:filters_string) { 'category_uuid=deadbeef1' }
|
64
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.where(category_uuid: 'deadbeef1')
|
65
|
+
end
|
65
66
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
67
|
+
context 'but if it is a field that does not exist in the model' do
|
68
|
+
let(:filters_string) { 'nonexisting=valuehere' }
|
69
|
+
it 'it blows up with the right error' do
|
70
|
+
expect{subject}.to raise_error(/Filtering by nonexisting is not allowed/)
|
71
|
+
end
|
70
72
|
end
|
71
73
|
end
|
74
|
+
context 'that maps to a different name' do
|
75
|
+
let(:filters_string) { 'name=Book1'}
|
76
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.where(simple_name: 'Book1')
|
77
|
+
end
|
78
|
+
context 'that is mapped as a nested struct' do
|
79
|
+
let(:filters_string) { 'fake_nested.name=Book1'}
|
80
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.where(simple_name: 'Book1')
|
81
|
+
end
|
72
82
|
end
|
73
|
-
context 'that maps to a different name' do
|
74
|
-
let(:filters_string) { 'name=Book1'}
|
75
|
-
it_behaves_like 'subject_equivalent_to', ActiveBook.where(simple_name: 'Book1')
|
76
|
-
end
|
77
|
-
context 'that is mapped as a nested struct' do
|
78
|
-
let(:filters_string) { 'fake_nested.name=Book1'}
|
79
|
-
it_behaves_like 'subject_equivalent_to', ActiveBook.where(simple_name: 'Book1')
|
80
|
-
end
|
81
|
-
end
|
82
83
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
84
|
+
context 'by a field or a related model' do
|
85
|
+
context 'for a belongs_to association' do
|
86
|
+
let(:filters_string) { 'author.name=author2'}
|
87
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.name' => 'author2')
|
88
|
+
end
|
89
|
+
context 'for a has_many association' do
|
90
|
+
let(:filters_string) { 'taggings.label=primary' }
|
91
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:taggings).where('active_taggings.label' => 'primary')
|
92
|
+
end
|
93
|
+
context 'for a has_many through association' do
|
94
|
+
let(:filters_string) { 'tags.name=blue' }
|
95
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:tags).where('active_tags.name' => 'blue')
|
96
|
+
end
|
95
97
|
end
|
96
|
-
end
|
97
98
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
end
|
168
|
-
context '!!' do
|
169
|
-
let(:filters_string) { 'author.name!!'}
|
170
|
-
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.name IS NULL')
|
171
|
-
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
172
|
-
WHERE "#{PREF}/author"."name" IS NULL
|
173
|
-
SQL
|
174
|
-
end
|
175
|
-
context 'including LIKE fuzzy queries' do
|
176
|
-
context 'LIKE' do
|
177
|
-
let(:filters_string) { 'author.name=author*'}
|
178
|
-
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.name LIKE "author%"')
|
99
|
+
# NOTE: apparently AR when conditions are build with strings in the where clauses (instead of names, etc)
|
100
|
+
# it decides to parenthesize them, even when there's only 1 condition. Hence the silly parentization of
|
101
|
+
# these SQL fragments here (and others)
|
102
|
+
context 'by using all supported operators' do
|
103
|
+
PREF = Praxis::Extensions::AttributeFiltering::ALIAS_TABLE_PREFIX
|
104
|
+
COMMON_SQL_PREFIX = <<~SQL
|
105
|
+
SELECT "active_books".* FROM "active_books"
|
106
|
+
INNER JOIN
|
107
|
+
"active_authors" "#{PREF}/author" ON "#{PREF}/author"."id" = "active_books"."author_id"
|
108
|
+
SQL
|
109
|
+
context '=' do
|
110
|
+
let(:filters_string) { 'author.id=11'}
|
111
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.id = 11')
|
112
|
+
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
113
|
+
WHERE ("#{PREF}/author"."id" = 11)
|
114
|
+
SQL
|
115
|
+
end
|
116
|
+
context '= (with array)' do
|
117
|
+
let(:filters_string) { 'author.id=11,22'}
|
118
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.id IN (11,22)')
|
119
|
+
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
120
|
+
WHERE ("#{PREF}/author"."id" IN (11,22))
|
121
|
+
SQL
|
122
|
+
end
|
123
|
+
context '!=' do
|
124
|
+
let(:filters_string) { 'author.id!=11'}
|
125
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.id <> 11')
|
126
|
+
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
127
|
+
WHERE ("#{PREF}/author"."id" <> 11)
|
128
|
+
SQL
|
129
|
+
end
|
130
|
+
context '!= (with array)' do
|
131
|
+
let(:filters_string) { 'author.id!=11,888'}
|
132
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.id NOT IN (11,888)')
|
133
|
+
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
134
|
+
WHERE ("#{PREF}/author"."id" NOT IN (11,888))
|
135
|
+
SQL
|
136
|
+
end
|
137
|
+
context '>' do
|
138
|
+
let(:filters_string) { 'author.id>1'}
|
139
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.id > 1')
|
140
|
+
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
141
|
+
WHERE ("#{PREF}/author"."id" > 1)
|
142
|
+
SQL
|
143
|
+
end
|
144
|
+
context '<' do
|
145
|
+
let(:filters_string) { 'author.id<22'}
|
146
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.id < 22')
|
147
|
+
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
148
|
+
WHERE ("#{PREF}/author"."id" < 22)
|
149
|
+
SQL
|
150
|
+
end
|
151
|
+
context '>=' do
|
152
|
+
let(:filters_string) { 'author.id>=22'}
|
153
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.id >= 22')
|
154
|
+
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
155
|
+
WHERE ("#{PREF}/author"."id" >= 22)
|
156
|
+
SQL
|
157
|
+
end
|
158
|
+
context '<=' do
|
159
|
+
let(:filters_string) { 'author.id<=22'}
|
160
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.id <= 22')
|
161
|
+
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
162
|
+
WHERE ("#{PREF}/author"."id" <= 22)
|
163
|
+
SQL
|
164
|
+
end
|
165
|
+
context '!' do
|
166
|
+
let(:filters_string) { 'author.id!'}
|
167
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.id IS NOT NULL')
|
179
168
|
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
180
|
-
|
181
|
-
|
169
|
+
WHERE ("#{PREF}/author"."id" IS NOT NULL)
|
170
|
+
SQL
|
182
171
|
end
|
183
|
-
context '
|
184
|
-
let(:filters_string) { 'author.name
|
185
|
-
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.name
|
172
|
+
context '!!' do
|
173
|
+
let(:filters_string) { 'author.name!!'}
|
174
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.name IS NULL')
|
186
175
|
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
187
|
-
|
188
|
-
|
176
|
+
WHERE ("#{PREF}/author"."name" IS NULL)
|
177
|
+
SQL
|
178
|
+
end
|
179
|
+
context 'including LIKE fuzzy queries' do
|
180
|
+
context 'LIKE' do
|
181
|
+
let(:filters_string) { 'author.name=author*'}
|
182
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.name LIKE "author%"')
|
183
|
+
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
184
|
+
WHERE ("#{PREF}/author"."name" LIKE 'author%')
|
185
|
+
SQL
|
186
|
+
end
|
187
|
+
context 'NOT LIKE' do
|
188
|
+
let(:filters_string) { 'author.name!=foobar*'}
|
189
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:author).where('active_authors.name NOT LIKE "foobar%"')
|
190
|
+
it_behaves_like 'subject_matches_sql', COMMON_SQL_PREFIX + <<~SQL
|
191
|
+
WHERE ("#{PREF}/author"."name" NOT LIKE 'foobar%')
|
192
|
+
SQL
|
193
|
+
end
|
189
194
|
end
|
190
195
|
end
|
191
|
-
end
|
192
196
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
+
context 'with a field mapping using a proc' do
|
198
|
+
let(:filters_string) { 'name_is_not=Book1' }
|
199
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.where.not(simple_name: 'Book1')
|
200
|
+
end
|
197
201
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
+
context 'with a deeply nested chains' do
|
203
|
+
context 'of depth 2' do
|
204
|
+
let(:filters_string) { 'category.books.name=Book2' }
|
205
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(category: :books).where('books_active_categories.simple_name': 'Book2')
|
206
|
+
end
|
207
|
+
context 'multiple conditions on a nested relationship' do
|
208
|
+
let(:filters_string) { 'category.books.taggings.tag_id=1&category.books.taggings.label=primary' }
|
209
|
+
it_behaves_like 'subject_equivalent_to',
|
210
|
+
ActiveBook.joins(category: { books: :taggings }).where('active_taggings.tag_id': 1).where('active_taggings.label': 'primary')
|
211
|
+
it_behaves_like 'subject_matches_sql', <<~SQL
|
212
|
+
SELECT "active_books".* FROM "active_books"
|
213
|
+
INNER JOIN "active_categories" ON "active_categories"."uuid" = "active_books"."category_uuid"
|
214
|
+
INNER JOIN "active_books" "books_active_categories" ON "books_active_categories"."category_uuid" = "active_categories"."uuid"
|
215
|
+
INNER JOIN "active_taggings" "#{PREF}/category/books/taggings" ON "/category/books/taggings"."book_id" = "books_active_categories"."id"
|
216
|
+
WHERE ("#{PREF}/category/books/taggings"."tag_id" = 1)
|
217
|
+
AND ("#{PREF}/category/books/taggings"."label" = 'primary')
|
218
|
+
SQL
|
219
|
+
end
|
220
|
+
context 'that contain multiple joins to the same table' do
|
221
|
+
let(:filters_string) { 'taggings.tag.taggings.tag_id=1' }
|
222
|
+
it_behaves_like 'subject_equivalent_to',
|
223
|
+
ActiveBook.joins(taggings: {tag: :taggings}).where('taggings_active_tags.tag_id=1')
|
224
|
+
end
|
202
225
|
end
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
AND ("#{PREF}/category/books/taggings"."label" = 'primary')
|
214
|
-
SQL
|
226
|
+
|
227
|
+
context 'by multiple fields' do
|
228
|
+
context 'adds the where clauses for the top model if fields belong to it' do
|
229
|
+
let(:filters_string) { 'category_uuid=deadbeef1&name=Book1' }
|
230
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.where(category_uuid: 'deadbeef1', simple_name: 'Book1')
|
231
|
+
end
|
232
|
+
context 'adds multiple where clauses for same nested relationship join (instead of multiple joins with 1 clause each)' do
|
233
|
+
let(:filters_string) { 'taggings.label=primary&taggings.tag_id=2' }
|
234
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:taggings).where('active_taggings.label' => 'primary', 'active_taggings.tag_id' => 2)
|
235
|
+
end
|
215
236
|
end
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
237
|
+
|
238
|
+
context 'uses fully qualified names for conditions (disambiguate fields)' do
|
239
|
+
context 'when we have a join table condition that has the same field' do
|
240
|
+
let(:filters_string) { 'name=Book1&category.books.name=Book3' }
|
241
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(category: :books)
|
242
|
+
.where('simple_name': 'Book1')
|
243
|
+
.where('books_active_categories.simple_name': 'Book3')
|
244
|
+
it_behaves_like 'subject_matches_sql', <<~SQL
|
245
|
+
SELECT "active_books".* FROM "active_books"
|
246
|
+
INNER JOIN "active_categories" ON "active_categories"."uuid" = "active_books"."category_uuid"
|
247
|
+
INNER JOIN "active_books" "#{PREF}/category/books" ON "#{PREF}/category/books"."category_uuid" = "active_categories"."uuid"
|
248
|
+
WHERE ("active_books"."simple_name" = 'Book1')
|
249
|
+
AND ("#{PREF}/category/books"."simple_name" = 'Book3')
|
250
|
+
SQL
|
251
|
+
end
|
252
|
+
|
253
|
+
context 'it qualifis them even if there are no joined tables/conditions at all' do
|
254
|
+
let(:filters_string) { 'id=11'}
|
255
|
+
it_behaves_like 'subject_matches_sql', <<~SQL
|
256
|
+
SELECT "active_books".* FROM "active_books"
|
257
|
+
WHERE ("active_books"."id" = 11)
|
258
|
+
SQL
|
259
|
+
end
|
260
|
+
|
220
261
|
end
|
221
262
|
end
|
222
263
|
|
223
|
-
context '
|
264
|
+
context 'with simple OR conditions' do
|
224
265
|
context 'adds the where clauses for the top model if fields belong to it' do
|
225
|
-
let(:filters_string) { 'category_uuid=deadbeef1
|
226
|
-
it_behaves_like 'subject_equivalent_to', ActiveBook.where(category_uuid: 'deadbeef1'
|
266
|
+
let(:filters_string) { 'category_uuid=deadbeef1|name=Book1' }
|
267
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.where(category_uuid: 'deadbeef1').or(ActiveBook.where(simple_name: 'Book1'))
|
268
|
+
end
|
269
|
+
context 'supports top level parenthesis' do
|
270
|
+
let(:filters_string) { '(category_uuid=deadbeef1|name=Book1)' }
|
271
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.where(category_uuid: 'deadbeef1').or(ActiveBook.where(simple_name: 'Book1'))
|
227
272
|
end
|
228
273
|
context 'adds multiple where clauses for same nested relationship join (instead of multiple joins with 1 clause each)' do
|
229
|
-
let(:filters_string) { 'taggings.label=primary
|
230
|
-
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:taggings).where('active_taggings.label' => 'primary'
|
274
|
+
let(:filters_string) { 'taggings.label=primary|taggings.tag_id=2' }
|
275
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:taggings).where('active_taggings.label' => 'primary')
|
276
|
+
.or(ActiveBook.joins(:taggings).where('active_taggings.tag_id' => 2))
|
231
277
|
end
|
232
278
|
end
|
233
279
|
|
234
|
-
context '
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
INNER JOIN "active_books" "#{PREF}/category/books" ON "#{PREF}/category/books"."category_uuid" = "active_categories"."uuid"
|
244
|
-
WHERE ("#{PREF}/category/books"."simple_name" = 'Book3')
|
245
|
-
AND ("active_books"."simple_name" = 'Book1')
|
246
|
-
SQL
|
247
|
-
end
|
280
|
+
context 'with combined AND and OR conditions' do
|
281
|
+
let(:filters_string) { '(category_uuid=deadbeef1|category_uuid=deadbeef2)&(name=Book1|name=Book2)' }
|
282
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.where(category_uuid: 'deadbeef1').or(ActiveBook.where(category_uuid: 'deadbeef2'))
|
283
|
+
.and(ActiveBook.where(simple_name: 'Book1').or(ActiveBook.where(simple_name: 'Book2')))
|
284
|
+
it_behaves_like 'subject_matches_sql', <<~SQL
|
285
|
+
SELECT "active_books".* FROM "active_books"
|
286
|
+
WHERE ("active_books"."category_uuid" = 'deadbeef1' OR "active_books"."category_uuid" = 'deadbeef2')
|
287
|
+
AND ("active_books"."simple_name" = 'Book1' OR "active_books"."simple_name" = 'Book2')
|
288
|
+
SQL
|
248
289
|
|
249
|
-
context '
|
250
|
-
let(:filters_string) { '
|
290
|
+
context 'adds multiple where clauses for same nested relationship join (instead of multiple joins with 1 clause each)' do
|
291
|
+
let(:filters_string) { 'taggings.label=primary|taggings.tag_id=2' }
|
292
|
+
it_behaves_like 'subject_equivalent_to', ActiveBook.joins(:taggings).where('active_taggings.label' => 'primary')
|
293
|
+
.or(ActiveBook.joins(:taggings).where('active_taggings.tag_id' => 2))
|
251
294
|
it_behaves_like 'subject_matches_sql', <<~SQL
|
252
295
|
SELECT "active_books".* FROM "active_books"
|
253
|
-
|
254
|
-
|
296
|
+
INNER JOIN "active_taggings" "/taggings" ON "/taggings"."book_id" = "active_books"."id"
|
297
|
+
WHERE ("/taggings"."label" = 'primary' OR "/taggings"."tag_id" = 2)
|
298
|
+
SQL
|
255
299
|
end
|
256
300
|
|
301
|
+
context '3-deep AND and OR conditions' do
|
302
|
+
let(:filters_string) { '(category.name=cat2|(taggings.label=primary&tags.name=red))&category_uuid=deadbeef1' }
|
303
|
+
it_behaves_like('subject_equivalent_to', Proc.new do
|
304
|
+
base=ActiveBook.joins(:category,:taggings,:tags)
|
305
|
+
|
306
|
+
and1_or1 = base.where('category.name': 'cat2')
|
307
|
+
|
308
|
+
and1_or2_and1 = base.where('taggings.label': 'primary')
|
309
|
+
and1_or2_and2 = base.where('tags.name': 'red')
|
310
|
+
and1_or2 = and1_or2_and1.and(and1_or2_and2)
|
311
|
+
|
312
|
+
and1 = and1_or1.or(and1_or2)
|
313
|
+
and2=base.where(category_uuid: 'deadbeef1')
|
314
|
+
|
315
|
+
query = and1.and(and2)
|
316
|
+
end)
|
317
|
+
|
318
|
+
it_behaves_like 'subject_matches_sql', <<~SQL
|
319
|
+
SELECT "active_books".* FROM "active_books"
|
320
|
+
INNER JOIN "active_categories" "/category" ON "/category"."uuid" = "active_books"."category_uuid"
|
321
|
+
INNER JOIN "active_taggings" "/taggings" ON "/taggings"."book_id" = "active_books"."id"
|
322
|
+
INNER JOIN "active_taggings" "taggings_active_books_join" ON "taggings_active_books_join"."book_id" = "active_books"."id"
|
323
|
+
INNER JOIN "active_tags" "/tags" ON "/tags"."id" = "taggings_active_books_join"."tag_id"
|
324
|
+
WHERE ("/category"."name" = 'cat2' OR ("/taggings"."label" = 'primary') AND ("/tags"."name" = 'red')) AND ("active_books"."category_uuid" = 'deadbeef1')
|
325
|
+
SQL
|
326
|
+
end
|
257
327
|
end
|
258
328
|
|
259
329
|
context 'ActiveRecord continues to work as expected (with our patches)' do
|