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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3df16fe842c4fcd160633af8a5e4d91fa19cbb857dc135a31160c1f389a5f6c1
|
4
|
+
data.tar.gz: b3f98740895ae5c611d616c36472d77ce412a1086d9d6a9ffe4532a8b3fa2235
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 328641f23218cf3a3a3841af7027f42981dced635c03e47642e5f7bd3a9194d94e13e59a87f51f71adc51344642a682148606daff113ebf80f638e0cb0f5be19
|
7
|
+
data.tar.gz: e3cb7920836e0ad2d86d05875c74a57759c5b28d9309124539c61c902adaaaa3a9bf1d0f094e9b19500d23f4034563ac9de234b9fd6f36a21084054bfed09c13
|
data/.rubocop_todo.yml
CHANGED
@@ -1,21 +1,23 @@
|
|
1
1
|
# This configuration was generated by
|
2
2
|
# `rubocop --auto-gen-config`
|
3
|
-
# on 2025-08-15
|
3
|
+
# on 2025-08-17 15:11:59 UTC using RuboCop version 1.79.2.
|
4
4
|
# The point is for the user to remove these configuration records
|
5
5
|
# one by one as the offenses are removed from the code base.
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
7
7
|
# versions of RuboCop, may require this file to be generated again.
|
8
8
|
|
9
|
-
# Offense count: 1
|
10
|
-
# Configuration parameters: AllowComments, AllowEmptyLambdas.
|
11
|
-
Lint/EmptyBlock:
|
12
|
-
Exclude:
|
13
|
-
- 'spec/**/*_spec.rb'
|
14
|
-
- 'spec/dummy/config/routes.rb'
|
15
|
-
|
16
9
|
# Offense count: 1
|
17
10
|
# Configuration parameters: AllowedPatterns.
|
18
11
|
# AllowedPatterns: ^expect_, ^assert_
|
19
12
|
RSpec/NoExpectationExample:
|
20
13
|
Exclude:
|
21
14
|
- 'spec/lib/kiroshi_spec.rb'
|
15
|
+
|
16
|
+
# Offense count: 2
|
17
|
+
# Configuration parameters: AllowedConstants.
|
18
|
+
Style/Documentation:
|
19
|
+
Exclude:
|
20
|
+
- 'spec/**/*'
|
21
|
+
- 'test/**/*'
|
22
|
+
- 'lib/kiroshi/filter.rb'
|
23
|
+
- 'lib/kiroshi/filters.rb'
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,16 +1,24 @@
|
|
1
|
-
Kiroshi
|
2
|
-
====
|
1
|
+
# Kiroshi
|
3
2
|
[](https://circleci.com/gh/darthjee/kiroshi)
|
4
3
|
[](https://app.codacy.com/gh/darthjee/kiroshi/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
|
5
4
|
|
6
5
|

|
7
6
|
|
8
|
-
Yard Documentation
|
9
|
-
-------------------
|
10
|
-
[https://www.rubydoc.info/gems/kiroshi/0.0.1](https://www.rubydoc.info/gems/kiroshi/0.0.1)
|
11
7
|
|
12
|
-
|
13
|
-
|
8
|
+
## Yard Documentation
|
9
|
+
|
10
|
+
[https://www.rubydoc.info/gems/kiroshi/0.1.1](https://www.rubydoc.info/gems/kiroshi/0.1.1)
|
11
|
+
|
12
|
+
Kiroshi has been designed to make filtering ActiveRecord queries easier
|
13
|
+
by providing a flexible and reusable filtering system. It allows you to
|
14
|
+
define filter sets that can be applied to any ActiveRecord scope,
|
15
|
+
supporting both exact matches and partial matching using SQL LIKE operations.
|
16
|
+
|
17
|
+
Current Release: [0.1.1](https://github.com/darthjee/kiroshi/tree/0.1.1)
|
18
|
+
|
19
|
+
[Next release](https://github.com/darthjee/kiroshi/compare/0.1.1...master)
|
20
|
+
|
21
|
+
## Installation
|
14
22
|
|
15
23
|
- Install it
|
16
24
|
|
@@ -27,3 +35,249 @@ Installation
|
|
27
35
|
```bash
|
28
36
|
bundle install kiroshi
|
29
37
|
```
|
38
|
+
|
39
|
+
## Usage
|
40
|
+
|
41
|
+
### Kiroshi::Filters
|
42
|
+
|
43
|
+
[Filters](https://www.rubydoc.info/gems/kiroshi/Kiroshi/Filters)
|
44
|
+
is a base class for implementing filter sets on ActiveRecord scopes.
|
45
|
+
It uses a class-level DSL to define filters and an instance-level interface to apply them.
|
46
|
+
|
47
|
+
#### Basic Usage
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
# Define a filter class
|
51
|
+
class DocumentFilters < Kiroshi::Filters
|
52
|
+
filter_by :name, match: :like
|
53
|
+
filter_by :status
|
54
|
+
filter_by :category
|
55
|
+
end
|
56
|
+
|
57
|
+
# Apply filters to a scope
|
58
|
+
filters = DocumentFilters.new(name: 'report', status: 'published')
|
59
|
+
filtered_documents = filters.apply(Document.all)
|
60
|
+
# Generates: WHERE name LIKE '%report%' AND status = 'published'
|
61
|
+
```
|
62
|
+
|
63
|
+
#### Filter Types
|
64
|
+
|
65
|
+
Kiroshi supports two types of matching:
|
66
|
+
|
67
|
+
- `:exact` - Exact match (default)
|
68
|
+
- `:like` - Partial match using SQL LIKE
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
class UserFilters < Kiroshi::Filters
|
72
|
+
filter_by :email, match: :like # Partial matching
|
73
|
+
filter_by :role # Exact matching (default)
|
74
|
+
filter_by :active, match: :exact # Explicit exact matching
|
75
|
+
end
|
76
|
+
|
77
|
+
filters = UserFilters.new(email: 'admin', role: 'moderator')
|
78
|
+
filtered_users = filters.apply(User.all)
|
79
|
+
# Generates: WHERE email LIKE '%admin%' AND role = 'moderator'
|
80
|
+
```
|
81
|
+
|
82
|
+
#### Advanced Examples
|
83
|
+
|
84
|
+
##### Multiple Filter Types
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
class ProductFilters < Kiroshi::Filters
|
88
|
+
filter_by :name, match: :like
|
89
|
+
filter_by :category
|
90
|
+
filter_by :price, match: :exact
|
91
|
+
filter_by :brand
|
92
|
+
end
|
93
|
+
|
94
|
+
# Apply only some filters
|
95
|
+
filters = ProductFilters.new(name: 'laptop', category: 'electronics')
|
96
|
+
products = filters.apply(Product.all)
|
97
|
+
# Only name and category filters are applied, price and brand are ignored
|
98
|
+
```
|
99
|
+
|
100
|
+
##### Controller Integration
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
# URL: /documents?filter[name]=report&filter[status]=published&filter[author]=john
|
104
|
+
class DocumentsController < ApplicationController
|
105
|
+
def index
|
106
|
+
@documents = document_filters.apply(Document.all)
|
107
|
+
render json: @documents
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def document_filters
|
113
|
+
DocumentFilters.new(filter_params)
|
114
|
+
end
|
115
|
+
|
116
|
+
def filter_params
|
117
|
+
params[:filter]&.permit(:name, :status, :category, :author)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class DocumentFilters < Kiroshi::Filters
|
122
|
+
filter_by :name, match: :like
|
123
|
+
filter_by :status
|
124
|
+
filter_by :category
|
125
|
+
filter_by :author, match: :like
|
126
|
+
end
|
127
|
+
```
|
128
|
+
|
129
|
+
##### Nested Resource Filtering
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
# URL: /users/123/articles?filter[title]=ruby&filter[published]=true&filter[tag]=tutorial
|
133
|
+
class ArticleFilters < Kiroshi::Filters
|
134
|
+
filter_by :title, match: :like
|
135
|
+
filter_by :published
|
136
|
+
filter_by :tag, match: :like
|
137
|
+
end
|
138
|
+
|
139
|
+
# In your controller
|
140
|
+
def articles
|
141
|
+
base_scope = current_user.articles
|
142
|
+
article_filters.apply(base_scope)
|
143
|
+
end
|
144
|
+
|
145
|
+
def article_filters
|
146
|
+
ArticleFilters.new(params[:filter]&.permit(:title, :published, :tag))
|
147
|
+
end
|
148
|
+
```
|
149
|
+
|
150
|
+
##### Joined Tables and Table Qualification
|
151
|
+
|
152
|
+
When working with joined tables that have columns with the same name, you can specify which table to filter on using the `table` parameter:
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
class DocumentFilters < Kiroshi::Filters
|
156
|
+
filter_by :name, match: :like # Filters by documents.name (default table)
|
157
|
+
filter_by :tag_name, match: :like, table: :tags # Filters by tags.name
|
158
|
+
filter_by :status # Filters by documents.status
|
159
|
+
filter_by :category, table: :documents # Explicitly filter by documents.category
|
160
|
+
end
|
161
|
+
|
162
|
+
# Example with joined scope
|
163
|
+
scope = Document.joins(:tags)
|
164
|
+
filters = DocumentFilters.new(tag_name: 'ruby', status: 'published')
|
165
|
+
filtered_documents = filters.apply(scope)
|
166
|
+
# Generates: WHERE tags.name LIKE '%ruby%' AND documents.status = 'published'
|
167
|
+
```
|
168
|
+
|
169
|
+
###### Table Qualification Examples
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
# Filter documents by tag name and document status
|
173
|
+
class DocumentTagFilters < Kiroshi::Filters
|
174
|
+
filter_by :tag_name, match: :like, table: :tags # Search in tags.name
|
175
|
+
filter_by :status, table: :documents # Search in documents.status
|
176
|
+
filter_by :title, match: :like # Search in documents.title (default table)
|
177
|
+
end
|
178
|
+
|
179
|
+
scope = Document.joins(:tags)
|
180
|
+
filters = DocumentTagFilters.new(tag_name: 'programming', status: 'published', title: 'Ruby')
|
181
|
+
result = filters.apply(scope)
|
182
|
+
# Generates: WHERE tags.name LIKE '%programming%' AND documents.status = 'published' AND documents.title LIKE '%Ruby%'
|
183
|
+
|
184
|
+
# Filter by both document and tag attributes with different field names
|
185
|
+
class AdvancedDocumentFilters < Kiroshi::Filters
|
186
|
+
filter_by :title, match: :like, table: :documents
|
187
|
+
filter_by :tag_name, match: :like, table: :tags
|
188
|
+
filter_by :category, table: :documents
|
189
|
+
filter_by :tag_color, table: :tags
|
190
|
+
end
|
191
|
+
|
192
|
+
scope = Document.joins(:tags)
|
193
|
+
filters = AdvancedDocumentFilters.new(
|
194
|
+
title: 'Ruby',
|
195
|
+
tag_name: 'tutorial',
|
196
|
+
category: 'programming',
|
197
|
+
tag_color: 'blue'
|
198
|
+
)
|
199
|
+
result = filters.apply(scope)
|
200
|
+
# Generates: WHERE documents.title LIKE '%Ruby%' AND tags.name LIKE '%tutorial%' AND documents.category = 'programming' AND tags.color = 'blue'
|
201
|
+
```
|
202
|
+
|
203
|
+
The `table` parameter accepts both symbols and strings, and helps resolve column name ambiguity in complex joined queries.
|
204
|
+
|
205
|
+
### Kiroshi::Filter
|
206
|
+
|
207
|
+
[Filter](https://www.rubydoc.info/gems/kiroshi/Kiroshi/Filter)
|
208
|
+
is the individual filter class that applies filtering logic to ActiveRecord scopes.
|
209
|
+
It's automatically used by `Kiroshi::Filters`, but can also be used standalone.
|
210
|
+
|
211
|
+
#### Standalone Usage
|
212
|
+
|
213
|
+
```ruby
|
214
|
+
# Create individual filters
|
215
|
+
name_filter = Kiroshi::Filter.new(:name, match: :like)
|
216
|
+
status_filter = Kiroshi::Filter.new(:status, match: :exact)
|
217
|
+
|
218
|
+
# Apply filters manually
|
219
|
+
scope = Document.all
|
220
|
+
scope = name_filter.apply(scope, { name: 'report' })
|
221
|
+
scope = status_filter.apply(scope, { status: 'published' })
|
222
|
+
```
|
223
|
+
|
224
|
+
#### Filter Options
|
225
|
+
|
226
|
+
- `match: :exact` - Performs exact matching (default)
|
227
|
+
- `match: :like` - Performs partial matching using SQL LIKE
|
228
|
+
- `table: :table_name` - Specifies which table to filter on (useful for joined queries)
|
229
|
+
|
230
|
+
```ruby
|
231
|
+
# Exact match filter
|
232
|
+
exact_filter = Kiroshi::Filter.new(:status)
|
233
|
+
exact_filter.apply(Document.all, { status: 'published' })
|
234
|
+
# Generates: WHERE status = 'published'
|
235
|
+
|
236
|
+
# LIKE match filter
|
237
|
+
like_filter = Kiroshi::Filter.new(:title, match: :like)
|
238
|
+
like_filter.apply(Document.all, { title: 'Ruby' })
|
239
|
+
# Generates: WHERE title LIKE '%Ruby%'
|
240
|
+
|
241
|
+
# Table-qualified filter for joined queries
|
242
|
+
tag_filter = Kiroshi::Filter.new(:name, match: :like, table: :tags)
|
243
|
+
tag_filter.apply(Document.joins(:tags), { name: 'programming' })
|
244
|
+
# Generates: WHERE tags.name LIKE '%programming%'
|
245
|
+
|
246
|
+
# Document-specific filter in joined query
|
247
|
+
doc_filter = Kiroshi::Filter.new(:title, match: :exact, table: :documents)
|
248
|
+
doc_filter.apply(Document.joins(:tags), { title: 'Ruby Guide' })
|
249
|
+
# Generates: WHERE documents.title = 'Ruby Guide'
|
250
|
+
```
|
251
|
+
|
252
|
+
#### Empty Value Handling
|
253
|
+
|
254
|
+
Filters automatically ignore empty or nil values:
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
filter = Kiroshi::Filter.new(:name)
|
258
|
+
filter.apply(Document.all, { name: nil }) # Returns original scope
|
259
|
+
filter.apply(Document.all, { name: '' }) # Returns original scope
|
260
|
+
filter.apply(Document.all, {}) # Returns original scope
|
261
|
+
filter.apply(Document.all, { name: 'value' }) # Applies filter
|
262
|
+
```
|
263
|
+
|
264
|
+
#### Handling Column Name Ambiguity
|
265
|
+
|
266
|
+
When working with joined tables that have columns with the same name, use the `table` parameter to specify which table's column to filter:
|
267
|
+
|
268
|
+
```ruby
|
269
|
+
# Without table specification - may cause ambiguity
|
270
|
+
scope = Document.joins(:tags) # Both documents and tags have 'name' column
|
271
|
+
|
272
|
+
# Specify which table to filter on
|
273
|
+
name_filter = Kiroshi::Filter.new(:name, match: :like, table: :tags)
|
274
|
+
result = name_filter.apply(scope, { name: 'ruby' })
|
275
|
+
# Generates: WHERE tags.name LIKE '%ruby%'
|
276
|
+
|
277
|
+
# Or filter by document name specifically
|
278
|
+
doc_name_filter = Kiroshi::Filter.new(:name, match: :like, table: :documents)
|
279
|
+
result = doc_name_filter.apply(scope, { name: 'guide' })
|
280
|
+
# Generates: WHERE documents.name LIKE '%guide%'
|
281
|
+
```
|
282
|
+
|
283
|
+
**Priority**: When using `Kiroshi::Filters`, if a filter specifies a `table`, it takes priority over the scope's default table name.
|
data/config/yardstick.yml
CHANGED
data/kiroshi.jpg
ADDED
Binary file
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kiroshi
|
4
|
+
# @author darthjee
|
5
|
+
#
|
6
|
+
# A filter class that applies filtering logic to ActiveRecord scopes
|
7
|
+
#
|
8
|
+
# This class provides a flexible way to apply filters to database queries,
|
9
|
+
# supporting both exact matches and partial matches using SQL LIKE operations.
|
10
|
+
#
|
11
|
+
# @example Creating and applying an exact filter
|
12
|
+
# filter = Kiroshi::Filter.new(:name)
|
13
|
+
# filtered_scope = filter.apply(Document.all, { name: 'John' })
|
14
|
+
#
|
15
|
+
# @example Creating and applying a LIKE filter
|
16
|
+
# filter = Kiroshi::Filter.new(:title, match: :like)
|
17
|
+
# filtered_scope = filter.apply(Article.all, { title: 'Ruby' })
|
18
|
+
#
|
19
|
+
# @since 0.1.0
|
20
|
+
class Filter
|
21
|
+
attr_reader :attribute, :match, :table_name
|
22
|
+
|
23
|
+
# @!method attribute
|
24
|
+
# @api private
|
25
|
+
#
|
26
|
+
# Returns the attribute name to filter by
|
27
|
+
#
|
28
|
+
# @return [Symbol] the attribute name to filter by
|
29
|
+
|
30
|
+
# @!method match
|
31
|
+
# @api private
|
32
|
+
#
|
33
|
+
# Returns the matching type (+:exact+ or +:like+)
|
34
|
+
#
|
35
|
+
# @return [Symbol] the matching type (+:exact+ or +:like+)
|
36
|
+
|
37
|
+
# @!method table_name
|
38
|
+
# @api private
|
39
|
+
#
|
40
|
+
# Returns the table name to qualify the attribute
|
41
|
+
#
|
42
|
+
# @return [String, String, nil] the table name or nil if not specified
|
43
|
+
|
44
|
+
# Creates a new Filter instance
|
45
|
+
#
|
46
|
+
# @param attribute [Symbol] the attribute name to filter by
|
47
|
+
# @param match [Symbol] the matching type, defaults to :exact
|
48
|
+
# @param table [String, Symbol, nil] the table name to qualify the attribute, defaults to nil
|
49
|
+
# @option match [Symbol] :exact performs exact matching (default)
|
50
|
+
# @option match [Symbol] :like performs partial matching using SQL LIKE
|
51
|
+
#
|
52
|
+
# @example Creating an exact match filter
|
53
|
+
# filter = Kiroshi::Filter.new(:status)
|
54
|
+
#
|
55
|
+
# @example Creating a partial match filter
|
56
|
+
# filter = Kiroshi::Filter.new(:name, match: :like)
|
57
|
+
#
|
58
|
+
# @example Creating a filter with table qualification
|
59
|
+
# filter = Kiroshi::Filter.new(:name, table: 'documents')
|
60
|
+
#
|
61
|
+
# @since 0.1.0
|
62
|
+
def initialize(attribute, match: :exact, table: nil)
|
63
|
+
@attribute = attribute
|
64
|
+
@match = match
|
65
|
+
@table_name = table
|
66
|
+
end
|
67
|
+
|
68
|
+
# Applies the filter to the given scope
|
69
|
+
#
|
70
|
+
# This method examines the filters hash for a value corresponding to the
|
71
|
+
# filter's attribute and applies the appropriate WHERE clause to the scope.
|
72
|
+
# If no value is present or the value is blank, the original scope is returned unchanged.
|
73
|
+
#
|
74
|
+
# @param scope [ActiveRecord::Relation] the ActiveRecord scope to filter
|
75
|
+
# @param filters [Hash] a hash containing filter values
|
76
|
+
#
|
77
|
+
# @return [ActiveRecord::Relation] the filtered scope
|
78
|
+
#
|
79
|
+
# @example Applying an exact filter
|
80
|
+
# filter = Kiroshi::Filter.new(:status)
|
81
|
+
# filter.apply(Document.all, { status: 'published' })
|
82
|
+
# # Generates: WHERE status = 'published'
|
83
|
+
#
|
84
|
+
# @example Applying a LIKE filter
|
85
|
+
# filter = Kiroshi::Filter.new(:title, match: :like)
|
86
|
+
# filter.apply(Article.all, { title: 'Ruby' })
|
87
|
+
# # Generates: WHERE title LIKE '%Ruby%'
|
88
|
+
#
|
89
|
+
# @example Applying a filter with table qualification
|
90
|
+
# filter = Kiroshi::Filter.new(:name, table: 'documents')
|
91
|
+
# filter.apply(Document.joins(:tags), { name: 'report' })
|
92
|
+
# # Generates: WHERE documents.name = 'report'
|
93
|
+
#
|
94
|
+
# @example Applying a filter with table qualification for tags
|
95
|
+
# filter = Kiroshi::Filter.new(:name, table: 'tags')
|
96
|
+
# filter.apply(Document.joins(:tags), { name: 'ruby' })
|
97
|
+
# # Generates: WHERE tags.name = 'ruby'
|
98
|
+
#
|
99
|
+
# @example With empty filter value
|
100
|
+
# filter = Kiroshi::Filter.new(:name)
|
101
|
+
# filter.apply(User.all, { name: nil })
|
102
|
+
# # Returns the original scope unchanged
|
103
|
+
#
|
104
|
+
# @since 0.1.0
|
105
|
+
def apply(scope, filters)
|
106
|
+
runner = FilterRunner.new(filter: self, scope: scope, filters: filters)
|
107
|
+
runner.apply
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kiroshi
|
4
|
+
class FilterQuery
|
5
|
+
# @api private
|
6
|
+
# @author darthjee
|
7
|
+
#
|
8
|
+
# Query strategy for exact matching
|
9
|
+
#
|
10
|
+
# This class implements the exact match query strategy, generating
|
11
|
+
# WHERE clauses with exact equality comparisons.
|
12
|
+
#
|
13
|
+
# @example Applying exact match query
|
14
|
+
# query = Kiroshi::FilterQuery::Exact.new(filter_runner)
|
15
|
+
# query.apply
|
16
|
+
# # Generates: WHERE attribute = 'value'
|
17
|
+
#
|
18
|
+
# @since 0.1.1
|
19
|
+
class Exact < FilterQuery
|
20
|
+
# Applies exact match filtering to the scope
|
21
|
+
#
|
22
|
+
# This method generates a WHERE clause with exact equality matching
|
23
|
+
# for the filter's attribute and value.
|
24
|
+
#
|
25
|
+
# @return [ActiveRecord::Relation] the filtered scope with exact match
|
26
|
+
#
|
27
|
+
# @example Applying exact match
|
28
|
+
# query = Exact.new(filter_runner)
|
29
|
+
# query.apply
|
30
|
+
# # Generates: WHERE status = 'published'
|
31
|
+
#
|
32
|
+
# @since 0.1.1
|
33
|
+
def apply
|
34
|
+
scope.where(table_name => { attribute => filter_value })
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kiroshi
|
4
|
+
class FilterQuery
|
5
|
+
# @api private
|
6
|
+
# @author darthjee
|
7
|
+
#
|
8
|
+
# Query strategy for LIKE matching
|
9
|
+
#
|
10
|
+
# This class implements the LIKE match query strategy, generating
|
11
|
+
# WHERE clauses with SQL LIKE operations for partial matching.
|
12
|
+
#
|
13
|
+
# @example Applying LIKE match query
|
14
|
+
# query = Kiroshi::FilterQuery::Like.new(filter_runner)
|
15
|
+
# query.apply
|
16
|
+
# # Generates: WHERE table_name.attribute LIKE '%value%'
|
17
|
+
#
|
18
|
+
# @since 0.1.1
|
19
|
+
class Like < FilterQuery
|
20
|
+
# Applies LIKE match filtering to the scope
|
21
|
+
#
|
22
|
+
# This method generates a WHERE clause with SQL LIKE operation
|
23
|
+
# for partial matching, including table name prefix to avoid
|
24
|
+
# column ambiguity in complex queries.
|
25
|
+
#
|
26
|
+
# @return [ActiveRecord::Relation] the filtered scope with LIKE match
|
27
|
+
#
|
28
|
+
# @example Applying LIKE match
|
29
|
+
# query = Like.new(filter_runner)
|
30
|
+
# query.apply
|
31
|
+
# # Generates: WHERE documents.name LIKE '%ruby%'
|
32
|
+
#
|
33
|
+
# @since 0.1.1
|
34
|
+
def apply
|
35
|
+
scope.where(
|
36
|
+
"#{table_name}.#{attribute} LIKE ?",
|
37
|
+
"%#{filter_value}%"
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kiroshi
|
4
|
+
# @api private
|
5
|
+
# @author darthjee
|
6
|
+
#
|
7
|
+
# Factory class for creating filter query strategies
|
8
|
+
#
|
9
|
+
# This class implements the Strategy pattern for handling different types of
|
10
|
+
# database queries based on the filter match type. It provides a factory method
|
11
|
+
# to create the appropriate query strategy class.
|
12
|
+
#
|
13
|
+
# @example Getting an exact match query strategy
|
14
|
+
# query = Kiroshi::FilterQuery.for(:exact).new(filter_runner)
|
15
|
+
# query.apply
|
16
|
+
#
|
17
|
+
# @example Getting a LIKE match query strategy
|
18
|
+
# query = Kiroshi::FilterQuery.for(:like).new(filter_runner)
|
19
|
+
# query.apply
|
20
|
+
#
|
21
|
+
# @since 0.1.1
|
22
|
+
class FilterQuery
|
23
|
+
autoload :Exact, 'kiroshi/filter_query/exact'
|
24
|
+
autoload :Like, 'kiroshi/filter_query/like'
|
25
|
+
|
26
|
+
class << self
|
27
|
+
# Factory method to create the appropriate query strategy
|
28
|
+
#
|
29
|
+
# This method returns the correct query strategy class based on the
|
30
|
+
# match type provided. It serves as the main entry point for creating
|
31
|
+
# query strategies.
|
32
|
+
#
|
33
|
+
# @param match [Symbol] the type of matching to perform
|
34
|
+
# - :exact for exact matching
|
35
|
+
# - :like for partial matching using SQL LIKE
|
36
|
+
#
|
37
|
+
# @return [Class] the appropriate FilterQuery subclass
|
38
|
+
#
|
39
|
+
# @example Creating an exact match query
|
40
|
+
# query_class = Kiroshi::FilterQuery.for(:exact)
|
41
|
+
# # Returns Kiroshi::FilterQuery::Exact
|
42
|
+
#
|
43
|
+
# @example Creating a LIKE match query
|
44
|
+
# query_class = Kiroshi::FilterQuery.for(:like)
|
45
|
+
# # Returns Kiroshi::FilterQuery::Like
|
46
|
+
#
|
47
|
+
# @raise [ArgumentError] when an unsupported match type is provided
|
48
|
+
#
|
49
|
+
# @since 0.1.1
|
50
|
+
def for(match)
|
51
|
+
case match
|
52
|
+
when :exact
|
53
|
+
Exact
|
54
|
+
when :like
|
55
|
+
Like
|
56
|
+
else
|
57
|
+
raise ArgumentError, "Unsupported match type: #{match}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Creates a new FilterQuery instance
|
63
|
+
#
|
64
|
+
# @param filter_runner [Kiroshi::FilterRunner] the filter runner instance
|
65
|
+
#
|
66
|
+
# @since 0.1.1
|
67
|
+
def initialize(filter_runner)
|
68
|
+
@filter_runner = filter_runner
|
69
|
+
end
|
70
|
+
|
71
|
+
# Base implementation for applying a filter query
|
72
|
+
#
|
73
|
+
# This method should be overridden by subclasses to provide specific
|
74
|
+
# query logic for each match type.
|
75
|
+
#
|
76
|
+
# @return [ActiveRecord::Relation] the filtered scope
|
77
|
+
#
|
78
|
+
# @raise [NotImplementedError] when called on the base class
|
79
|
+
#
|
80
|
+
# @since 0.1.1
|
81
|
+
def apply
|
82
|
+
raise NotImplementedError, 'Subclasses must implement #apply method'
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
attr_reader :filter_runner
|
88
|
+
|
89
|
+
# @!method filter_runner
|
90
|
+
# @api private
|
91
|
+
# @private
|
92
|
+
#
|
93
|
+
# Returns the filter runner instance
|
94
|
+
#
|
95
|
+
# @return [Kiroshi::FilterRunner] the filter runner instance
|
96
|
+
|
97
|
+
delegate :scope, :attribute, :table_name, :filter_value, to: :filter_runner
|
98
|
+
|
99
|
+
# @!method scope
|
100
|
+
# @api private
|
101
|
+
# @private
|
102
|
+
#
|
103
|
+
# Returns the ActiveRecord scope being filtered
|
104
|
+
#
|
105
|
+
# @return [ActiveRecord::Relation] the scope being filtered
|
106
|
+
|
107
|
+
# @!method attribute
|
108
|
+
# @api private
|
109
|
+
# @private
|
110
|
+
#
|
111
|
+
# Returns the attribute name to filter by
|
112
|
+
#
|
113
|
+
# @return [Symbol] the attribute name to filter by
|
114
|
+
|
115
|
+
# @!method table_name
|
116
|
+
# @api private
|
117
|
+
# @private
|
118
|
+
#
|
119
|
+
# Returns the table name from the scope
|
120
|
+
#
|
121
|
+
# @return [String] the table name
|
122
|
+
|
123
|
+
# @!method filter_value
|
124
|
+
# @api private
|
125
|
+
# @private
|
126
|
+
#
|
127
|
+
# Returns the filter value for the current filter's attribute
|
128
|
+
#
|
129
|
+
# @return [Object, nil] the filter value or nil if not present
|
130
|
+
end
|
131
|
+
end
|