kiroshi 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +10 -8
- data/Gemfile +1 -0
- data/README.md +261 -7
- data/config/yardstick.yml +1 -1
- data/kiroshi.jpg +0 -0
- data/lib/kiroshi/filter.rb +110 -0
- data/lib/kiroshi/filter_query/exact.rb +38 -0
- data/lib/kiroshi/filter_query/like.rb +42 -0
- data/lib/kiroshi/filter_query.rb +131 -0
- data/lib/kiroshi/filter_runner.rb +152 -0
- data/lib/kiroshi/filters.rb +184 -0
- data/lib/kiroshi/version.rb +2 -2
- data/lib/kiroshi.rb +158 -2
- data/spec/lib/kiroshi/filter_query/exact_spec.rb +280 -0
- data/spec/lib/kiroshi/filter_query/like_spec.rb +275 -0
- data/spec/lib/kiroshi/filter_query_spec.rb +39 -0
- data/spec/lib/kiroshi/filter_runner_spec.rb +110 -0
- data/spec/lib/kiroshi/filter_spec.rb +63 -0
- data/spec/lib/kiroshi/filters_spec.rb +157 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/support/db/schema.rb +19 -0
- data/spec/support/factories/document.rb +7 -0
- data/spec/support/factories/tag.rb +7 -0
- data/spec/support/factory_bot.rb +7 -0
- data/spec/support/models/document.rb +7 -0
- data/spec/support/models/tag.rb +7 -0
- metadata +20 -2
@@ -0,0 +1,280 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Kiroshi::FilterQuery::Exact, type: :model do
|
6
|
+
describe '#apply' do
|
7
|
+
subject(:query) { described_class.new(filter_runner) }
|
8
|
+
|
9
|
+
let(:filter_runner) { Kiroshi::FilterRunner.new(filter: filter, scope: scope, filters: filters) }
|
10
|
+
let(:filter) { Kiroshi::Filter.new(:name, match: :exact) }
|
11
|
+
let(:scope) { Document.all }
|
12
|
+
let(:filter_value) { 'test_document' }
|
13
|
+
let(:filters) { { name: filter_value } }
|
14
|
+
|
15
|
+
let!(:matching_document) { create(:document, name: 'test_document') }
|
16
|
+
let!(:non_matching_document) { create(:document, name: 'other_document') }
|
17
|
+
|
18
|
+
let(:expected_sql) do
|
19
|
+
<<~SQL.squish
|
20
|
+
SELECT "documents".* FROM "documents" WHERE "documents"."name" = 'test_document'
|
21
|
+
SQL
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'returns records that exactly match the filter value' do
|
25
|
+
expect(query.apply).to include(matching_document)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'does not return records that do not exactly match' do
|
29
|
+
expect(query.apply).not_to include(non_matching_document)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'generates correct SQL with exact equality' do
|
33
|
+
expect(query.apply.to_sql).to eq(expected_sql)
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'when filtering by status attribute' do
|
37
|
+
let(:filter) { Kiroshi::Filter.new(:status, match: :exact) }
|
38
|
+
let(:filter_value) { 'published' }
|
39
|
+
let(:filters) { { status: filter_value } }
|
40
|
+
|
41
|
+
let!(:published_document) { create(:document, status: 'published') }
|
42
|
+
let!(:draft_document) { create(:document, status: 'draft') }
|
43
|
+
|
44
|
+
let(:expected_sql) do
|
45
|
+
<<~SQL.squish
|
46
|
+
SELECT "documents".* FROM "documents" WHERE "documents"."status" = 'published'
|
47
|
+
SQL
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'returns documents with exact status match' do
|
51
|
+
expect(query.apply).to include(published_document)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'does not return documents without exact status match' do
|
55
|
+
expect(query.apply).not_to include(draft_document)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'generates correct SQL for status filtering' do
|
59
|
+
expect(query.apply.to_sql).to eq(expected_sql)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'when filtering with numeric values' do
|
64
|
+
let(:filter) { Kiroshi::Filter.new(:priority, match: :exact) }
|
65
|
+
let(:filter_value) { 1 }
|
66
|
+
let(:filters) { { priority: filter_value } }
|
67
|
+
|
68
|
+
let!(:high_priority_document) { create(:document, priority: 1) }
|
69
|
+
let!(:medium_priority_document) { create(:document, priority: 2) }
|
70
|
+
|
71
|
+
let(:expected_sql) do
|
72
|
+
<<~SQL.squish
|
73
|
+
SELECT "documents".* FROM "documents" WHERE "documents"."priority" = 1
|
74
|
+
SQL
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'returns documents with exact numeric match' do
|
78
|
+
expect(query.apply).to include(high_priority_document)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'does not return documents without exact numeric match' do
|
82
|
+
expect(query.apply).not_to include(medium_priority_document)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'generates correct SQL for numeric filtering' do
|
86
|
+
expect(query.apply.to_sql).to eq(expected_sql)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'when filtering with boolean values' do
|
91
|
+
let(:filter) { Kiroshi::Filter.new(:active, match: :exact) }
|
92
|
+
let(:filter_value) { true }
|
93
|
+
let(:filters) { { active: filter_value } }
|
94
|
+
|
95
|
+
let!(:active_document) { create(:document, active: true) }
|
96
|
+
let!(:inactive_document) { create(:document, active: false) }
|
97
|
+
|
98
|
+
let(:expected_sql) do
|
99
|
+
<<~SQL.squish
|
100
|
+
SELECT "documents".* FROM "documents" WHERE "documents"."active" = 1
|
101
|
+
SQL
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'returns documents with exact boolean match' do
|
105
|
+
expect(query.apply).to include(active_document)
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'does not return documents without exact boolean match' do
|
109
|
+
expect(query.apply).not_to include(inactive_document)
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'generates correct SQL for boolean filtering' do
|
113
|
+
expect(query.apply.to_sql).to eq(expected_sql)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context 'when no records match' do
|
118
|
+
let(:filter_value) { 'nonexistent_value' }
|
119
|
+
|
120
|
+
let(:expected_sql) do
|
121
|
+
<<~SQL.squish
|
122
|
+
SELECT "documents".* FROM "documents" WHERE "documents"."name" = 'nonexistent_value'
|
123
|
+
SQL
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'returns empty relation' do
|
127
|
+
expect(query.apply).to be_empty
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'still generates valid SQL' do
|
131
|
+
expect(query.apply.to_sql).to eq(expected_sql)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context 'with case sensitivity' do
|
136
|
+
let(:filter_value) { 'Test_Document' }
|
137
|
+
let!(:lowercase_document) { create(:document, name: 'test_document') }
|
138
|
+
let!(:uppercase_document) { create(:document, name: 'TEST_DOCUMENT') }
|
139
|
+
let!(:mixedcase_document) { create(:document, name: 'Test_Document') }
|
140
|
+
|
141
|
+
it 'includes documents with exact case match' do
|
142
|
+
expect(query.apply).to include(mixedcase_document)
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'excludes documents with lowercase' do
|
146
|
+
expect(query.apply).not_to include(lowercase_document)
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'excludes documents with upcase' do
|
150
|
+
expect(query.apply).not_to include(uppercase_document)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context 'when filter has table configured' do
|
155
|
+
let(:scope) { Document.joins(:tags) }
|
156
|
+
let(:filter_value) { 'ruby' }
|
157
|
+
let(:filters) { { name: filter_value } }
|
158
|
+
|
159
|
+
let!(:first_tag) { Tag.find_or_create_by(name: 'ruby') }
|
160
|
+
let!(:second_tag) { Tag.find_or_create_by(name: 'javascript') }
|
161
|
+
|
162
|
+
let!(:document_with_ruby_tag) { create(:document, name: 'My Document') }
|
163
|
+
let!(:document_with_js_tag) { create(:document, name: 'JS Guide') }
|
164
|
+
let!(:document_without_tag) { create(:document, name: 'Other Document') }
|
165
|
+
|
166
|
+
before do
|
167
|
+
Tag.find_or_create_by(name: 'programming')
|
168
|
+
document_with_ruby_tag.tags << [first_tag]
|
169
|
+
document_with_js_tag.tags << [second_tag]
|
170
|
+
end
|
171
|
+
|
172
|
+
context 'when filtering by tags table' do
|
173
|
+
let(:filter) { Kiroshi::Filter.new(:name, match: :exact, table: :tags) }
|
174
|
+
|
175
|
+
let(:expected_sql) do
|
176
|
+
<<~SQL.squish
|
177
|
+
SELECT "documents".* FROM "documents"#{' '}
|
178
|
+
INNER JOIN "documents_tags" ON "documents_tags"."document_id" = "documents"."id"#{' '}
|
179
|
+
INNER JOIN "tags" ON "tags"."id" = "documents_tags"."tag_id"#{' '}
|
180
|
+
WHERE "tags"."name" = 'ruby'
|
181
|
+
SQL
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'returns documents with tags that exactly match the filter value' do
|
185
|
+
expect(query.apply).to include(document_with_ruby_tag)
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'does not return documents with tags that do not exactly match' do
|
189
|
+
expect(query.apply).not_to include(document_with_js_tag)
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'does not return documents without matching tags' do
|
193
|
+
expect(query.apply).not_to include(document_without_tag)
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'generates SQL with tags table qualification' do
|
197
|
+
expect(query.apply.to_sql).to eq(expected_sql)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
context 'when filtering by documents table explicitly' do
|
202
|
+
let(:filter) { Kiroshi::Filter.new(:name, match: :exact, table: :documents) }
|
203
|
+
let(:filter_value) { 'JS Guide' }
|
204
|
+
|
205
|
+
let(:expected_sql) do
|
206
|
+
<<~SQL.squish
|
207
|
+
SELECT "documents".* FROM "documents"#{' '}
|
208
|
+
INNER JOIN "documents_tags" ON "documents_tags"."document_id" = "documents"."id"#{' '}
|
209
|
+
INNER JOIN "tags" ON "tags"."id" = "documents_tags"."tag_id"#{' '}
|
210
|
+
WHERE "documents"."name" = 'JS Guide'
|
211
|
+
SQL
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'returns documents that exactly match the filter value in document name' do
|
215
|
+
expect(query.apply).to include(document_with_js_tag)
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'does not return documents that do not exactly match document name' do
|
219
|
+
expect(query.apply).not_to include(document_with_ruby_tag)
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'does not return documents without exact document name match' do
|
223
|
+
expect(query.apply).not_to include(document_without_tag)
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'generates SQL with documents table qualification' do
|
227
|
+
expect(query.apply.to_sql).to eq(expected_sql)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
context 'when table is specified as string' do
|
232
|
+
let(:filter) { Kiroshi::Filter.new(:name, match: :exact, table: 'tags') }
|
233
|
+
|
234
|
+
let(:expected_sql) do
|
235
|
+
<<~SQL.squish
|
236
|
+
SELECT "documents".* FROM "documents"#{' '}
|
237
|
+
INNER JOIN "documents_tags" ON "documents_tags"."document_id" = "documents"."id"#{' '}
|
238
|
+
INNER JOIN "tags" ON "tags"."id" = "documents_tags"."tag_id"#{' '}
|
239
|
+
WHERE "tags"."name" = 'ruby'
|
240
|
+
SQL
|
241
|
+
end
|
242
|
+
|
243
|
+
it 'works the same as with symbol table name' do
|
244
|
+
expect(query.apply).to include(document_with_ruby_tag)
|
245
|
+
end
|
246
|
+
|
247
|
+
it 'generates SQL with string table qualification' do
|
248
|
+
expect(query.apply.to_sql).to eq(expected_sql)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
context 'when filtering by different attributes with table qualification' do
|
253
|
+
let(:filter) { Kiroshi::Filter.new(:id, match: :exact, table: :tags) }
|
254
|
+
let(:filter_value) { first_tag.id }
|
255
|
+
let(:filters) { { id: filter_value } }
|
256
|
+
|
257
|
+
let(:expected_sql) do
|
258
|
+
<<~SQL.squish
|
259
|
+
SELECT "documents".* FROM "documents"#{' '}
|
260
|
+
INNER JOIN "documents_tags" ON "documents_tags"."document_id" = "documents"."id"#{' '}
|
261
|
+
INNER JOIN "tags" ON "tags"."id" = "documents_tags"."tag_id"#{' '}
|
262
|
+
WHERE "tags"."id" = #{first_tag.id}
|
263
|
+
SQL
|
264
|
+
end
|
265
|
+
|
266
|
+
it 'returns documents with tags that match the tag id' do
|
267
|
+
expect(query.apply).to include(document_with_ruby_tag)
|
268
|
+
end
|
269
|
+
|
270
|
+
it 'does not return documents without the matching tag id' do
|
271
|
+
expect(query.apply).not_to include(document_with_js_tag)
|
272
|
+
end
|
273
|
+
|
274
|
+
it 'generates SQL with tags table qualification for id attribute' do
|
275
|
+
expect(query.apply.to_sql).to eq(expected_sql)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
@@ -0,0 +1,275 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Kiroshi::FilterQuery::Like, type: :model do
|
6
|
+
describe '#apply' do
|
7
|
+
subject(:query) { described_class.new(filter_runner) }
|
8
|
+
|
9
|
+
let(:filter_runner) { Kiroshi::FilterRunner.new(filter: filter, scope: scope, filters: filters) }
|
10
|
+
let(:filter) { Kiroshi::Filter.new(:name, match: :like) }
|
11
|
+
let(:scope) { Document.all }
|
12
|
+
let(:filter_value) { 'test' }
|
13
|
+
let(:filters) { { name: filter_value } }
|
14
|
+
|
15
|
+
let!(:matching_document) { create(:document, name: 'test_document') }
|
16
|
+
let!(:another_match) { create(:document, name: 'my_test_file') }
|
17
|
+
let!(:non_matching_document) { create(:document, name: 'other_document') }
|
18
|
+
|
19
|
+
let(:expected_sql) do
|
20
|
+
<<~SQL.squish
|
21
|
+
SELECT "documents".* FROM "documents" WHERE (documents.name LIKE '%test%')
|
22
|
+
SQL
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'returns records that partially match the filter value' do
|
26
|
+
expect(query.apply).to include(matching_document)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'returns multiple records that contain the filter value' do
|
30
|
+
expect(query.apply).to include(another_match)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'does not return records that do not contain the filter value' do
|
34
|
+
expect(query.apply).not_to include(non_matching_document)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'generates correct SQL with LIKE operation' do
|
38
|
+
expect(query.apply.to_sql).to eq(expected_sql)
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'when filtering by status attribute' do
|
42
|
+
let(:filter) { Kiroshi::Filter.new(:status, match: :like) }
|
43
|
+
let(:filter_value) { 'pub' }
|
44
|
+
let(:filters) { { status: filter_value } }
|
45
|
+
|
46
|
+
let!(:published_document) { create(:document, status: 'published') }
|
47
|
+
let!(:republished_document) { create(:document, status: 'republished') }
|
48
|
+
let!(:draft_document) { create(:document, status: 'draft') }
|
49
|
+
|
50
|
+
let(:expected_sql) do
|
51
|
+
<<~SQL.squish
|
52
|
+
SELECT "documents".* FROM "documents" WHERE (documents.status LIKE '%pub%')
|
53
|
+
SQL
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'returns documents with partial status match' do
|
57
|
+
expect(query.apply).to include(published_document)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'returns documents with partial match in different positions' do
|
61
|
+
expect(query.apply).to include(republished_document)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'does not return documents without partial status match' do
|
65
|
+
expect(query.apply).not_to include(draft_document)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'generates correct SQL for status filtering' do
|
69
|
+
expect(query.apply.to_sql).to eq(expected_sql)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'when filtering with numeric values as strings' do
|
74
|
+
let(:filter) { Kiroshi::Filter.new(:version, match: :like) }
|
75
|
+
let(:filter_value) { '1.2' }
|
76
|
+
let(:filters) { { version: filter_value } }
|
77
|
+
|
78
|
+
let!(:version_match) { create(:document, version: '1.2.3') }
|
79
|
+
let!(:another_version) { create(:document, version: '2.1.2') }
|
80
|
+
let!(:different_version) { create(:document, version: '3.0.0') }
|
81
|
+
|
82
|
+
let(:expected_sql) do
|
83
|
+
<<~SQL.squish
|
84
|
+
SELECT "documents".* FROM "documents" WHERE (documents.version LIKE '%1.2%')
|
85
|
+
SQL
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'returns documents with partial numeric match' do
|
89
|
+
expect(query.apply).to include(version_match)
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'returns documents with partial match in different positions' do
|
93
|
+
expect(query.apply).to include(another_version)
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'does not return documents without partial numeric match' do
|
97
|
+
expect(query.apply).not_to include(different_version)
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'generates correct SQL for numeric string filtering' do
|
101
|
+
expect(query.apply.to_sql).to eq(expected_sql)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'when no records match' do
|
106
|
+
let(:filter_value) { 'nonexistent' }
|
107
|
+
|
108
|
+
let(:expected_sql) do
|
109
|
+
<<~SQL.squish
|
110
|
+
SELECT "documents".* FROM "documents" WHERE (documents.name LIKE '%nonexistent%')
|
111
|
+
SQL
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'returns empty relation' do
|
115
|
+
expect(query.apply).to be_empty
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'still generates valid SQL' do
|
119
|
+
expect(query.apply.to_sql).to eq(expected_sql)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
context 'with case sensitivity' do
|
124
|
+
let(:filter_value) { 'Test' }
|
125
|
+
let!(:lowercase_document) { create(:document, name: 'test_document') }
|
126
|
+
let!(:uppercase_document) { create(:document, name: 'TEST_FILE') }
|
127
|
+
let!(:mixedcase_document) { create(:document, name: 'Test_Document') }
|
128
|
+
let!(:no_match_document) { create(:document, name: 'example') }
|
129
|
+
|
130
|
+
it 'includes documents with exact case match' do
|
131
|
+
expect(query.apply).to include(mixedcase_document)
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'includes documents with lowercase match' do
|
135
|
+
expect(query.apply).to include(lowercase_document)
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'includes documents with uppercase match' do
|
139
|
+
expect(query.apply).to include(uppercase_document)
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'excludes documents without any case match' do
|
143
|
+
expect(query.apply).not_to include(no_match_document)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
context 'with special characters in filter value' do
|
148
|
+
let(:filter_value) { 'user@' }
|
149
|
+
let!(:email_document) { create(:document, name: 'user@example.com') }
|
150
|
+
let!(:partial_email_document) { create(:document, name: 'admin_user@test.org') }
|
151
|
+
let!(:no_match_document) { create(:document, name: 'username') }
|
152
|
+
|
153
|
+
it 'includes documents with special character match' do
|
154
|
+
expect(query.apply).to include(email_document)
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'includes documents with partial special character match' do
|
158
|
+
expect(query.apply).to include(partial_email_document)
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'excludes documents without special character match' do
|
162
|
+
expect(query.apply).not_to include(no_match_document)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
context 'with single character filter' do
|
167
|
+
let(:filter_value) { 'a' }
|
168
|
+
let!(:start_match) { create(:document, name: 'apple') }
|
169
|
+
let!(:middle_match) { create(:document, name: 'banana') }
|
170
|
+
let!(:end_match) { create(:document, name: 'extra') }
|
171
|
+
let!(:no_match_document) { create(:document, name: 'test') }
|
172
|
+
|
173
|
+
it 'includes documents with character at start' do
|
174
|
+
expect(query.apply).to include(start_match)
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'includes documents with character in middle' do
|
178
|
+
expect(query.apply).to include(middle_match)
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'includes documents with character at end' do
|
182
|
+
expect(query.apply).to include(end_match)
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'excludes documents without the character' do
|
186
|
+
expect(query.apply).not_to include(no_match_document)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context 'when filter has table configured' do
|
191
|
+
let(:scope) { Document.joins(:tags) }
|
192
|
+
let(:filter_value) { 'ruby' }
|
193
|
+
let(:filters) { { name: filter_value } }
|
194
|
+
|
195
|
+
let!(:first_tag) { Tag.find_or_create_by(name: 'ruby') }
|
196
|
+
let!(:second_tag) { Tag.find_or_create_by(name: 'ruby_on_rails') }
|
197
|
+
|
198
|
+
let!(:document_with_ruby_tag) { create(:document, name: 'My Document') }
|
199
|
+
let!(:document_with_rails_tag) { create(:document, name: 'Rails Guide') }
|
200
|
+
let!(:document_without_tag) { create(:document, name: 'Other Document') }
|
201
|
+
|
202
|
+
before do
|
203
|
+
Tag.find_or_create_by(name: 'programming')
|
204
|
+
document_with_ruby_tag.tags << [first_tag]
|
205
|
+
document_with_rails_tag.tags << [second_tag]
|
206
|
+
end
|
207
|
+
|
208
|
+
context 'when filtering by tags table' do
|
209
|
+
let(:filter) { Kiroshi::Filter.new(:name, match: :like, table: :tags) }
|
210
|
+
|
211
|
+
it 'returns documents with tags that partially match the filter value' do
|
212
|
+
expect(query.apply).to include(document_with_ruby_tag)
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'returns documents with tags that contain the filter value' do
|
216
|
+
expect(query.apply).to include(document_with_rails_tag)
|
217
|
+
end
|
218
|
+
|
219
|
+
it 'does not return documents without matching tags' do
|
220
|
+
expect(query.apply).not_to include(document_without_tag)
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'generates SQL with tags table qualification' do
|
224
|
+
result_sql = query.apply.to_sql
|
225
|
+
expect(result_sql).to include('tags.name LIKE')
|
226
|
+
end
|
227
|
+
|
228
|
+
it 'generates SQL with correct LIKE pattern for tag name' do
|
229
|
+
result_sql = query.apply.to_sql
|
230
|
+
expect(result_sql).to include("'%ruby%'")
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
context 'when filtering by documents table explicitly' do
|
235
|
+
let(:filter) { Kiroshi::Filter.new(:name, match: :like, table: :documents) }
|
236
|
+
let(:filter_value) { 'Guide' }
|
237
|
+
|
238
|
+
it 'returns documents that partially match the filter value in document name' do
|
239
|
+
expect(query.apply).to include(document_with_rails_tag)
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'does not return documents that do not match document name' do
|
243
|
+
expect(query.apply).not_to include(document_with_ruby_tag)
|
244
|
+
end
|
245
|
+
|
246
|
+
it 'does not return documents without matching document name' do
|
247
|
+
expect(query.apply).not_to include(document_without_tag)
|
248
|
+
end
|
249
|
+
|
250
|
+
it 'generates SQL with documents table qualification' do
|
251
|
+
result_sql = query.apply.to_sql
|
252
|
+
expect(result_sql).to include('documents.name LIKE')
|
253
|
+
end
|
254
|
+
|
255
|
+
it 'generates SQL with correct LIKE pattern for document name' do
|
256
|
+
result_sql = query.apply.to_sql
|
257
|
+
expect(result_sql).to include("'%Guide%'")
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
context 'when table is specified as string' do
|
262
|
+
let(:filter) { Kiroshi::Filter.new(:name, match: :like, table: 'tags') }
|
263
|
+
|
264
|
+
it 'works the same as with symbol table name' do
|
265
|
+
expect(query.apply).to include(document_with_ruby_tag)
|
266
|
+
end
|
267
|
+
|
268
|
+
it 'generates SQL with string table qualification' do
|
269
|
+
result_sql = query.apply.to_sql
|
270
|
+
expect(result_sql).to include('tags.name LIKE')
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Kiroshi::FilterQuery, type: :model do
|
6
|
+
describe '.for' do
|
7
|
+
context 'when match is :exact' do
|
8
|
+
it 'returns the Exact class' do
|
9
|
+
expect(described_class.for(:exact)).to eq(Kiroshi::FilterQuery::Exact)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'when match is :like' do
|
14
|
+
it 'returns the Like class' do
|
15
|
+
expect(described_class.for(:like)).to eq(Kiroshi::FilterQuery::Like)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'when match is an unsupported type' do
|
20
|
+
it 'raises ArgumentError for unsupported match type' do
|
21
|
+
expect { described_class.for(:invalid) }.to raise_error(
|
22
|
+
ArgumentError, 'Unsupported match type: invalid'
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'raises ArgumentError for nil match type' do
|
27
|
+
expect { described_class.for(nil) }.to raise_error(
|
28
|
+
ArgumentError, 'Unsupported match type: '
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'raises ArgumentError for string match type' do
|
33
|
+
expect { described_class.for('exact') }.to raise_error(
|
34
|
+
ArgumentError, 'Unsupported match type: exact'
|
35
|
+
)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|