kiroshi 0.0.1 → 0.1.0
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 +172 -7
- data/config/yardstick.yml +1 -1
- data/kiroshi.jpg +0 -0
- data/lib/kiroshi/filter.rb +99 -0
- data/lib/kiroshi/filters.rb +177 -0
- data/lib/kiroshi/version.rb +2 -2
- data/lib/kiroshi.rb +3 -1
- data/spec/lib/kiroshi/filter_spec.rb +63 -0
- data/spec/lib/kiroshi/filters_spec.rb +94 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/support/db/schema.rb +5 -0
- data/spec/support/factories/document.rb +7 -0
- data/spec/support/factory_bot.rb +7 -0
- data/spec/support/models/document.rb +5 -0
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3bbe023d89c78eca0371b05303b85c44da60ce794a1c39844b2ab719350cc665
|
4
|
+
data.tar.gz: aa64d92280278a488c7bb2c5385fc7fd5f28a04a5b7a22aa1cbd1681311bf316
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 588fb1ce51bb238a8536b08d123a6cd23b8393896eea4f66df436b8fb641e016f517e411a6936ea6277b76c9d34564d44e82b547517b27ec61762634dbaa1ca3
|
7
|
+
data.tar.gz: 7f90feb28c09bc3858110427fe2266ec16fcc699238689ceea3432ed56a6e839ad1d33d49818e7563998817b3d41fc839f175d5e64bbdb0d838f4375e0d5698a
|
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.0](https://www.rubydoc.info/gems/kiroshi/0.1.0)
|
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.0](https://github.com/darthjee/kiroshi/tree/0.1.0)
|
18
|
+
|
19
|
+
[Next release](https://github.com/darthjee/kiroshi/compare/0.1.0...master)
|
20
|
+
|
21
|
+
## Installation
|
14
22
|
|
15
23
|
- Install it
|
16
24
|
|
@@ -27,3 +35,160 @@ 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
|
+
class DocumentsController < ApplicationController
|
104
|
+
def index
|
105
|
+
@documents = document_filters.apply(Document.all)
|
106
|
+
render json: @documents
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def document_filters
|
112
|
+
DocumentFilters.new(filter_params)
|
113
|
+
end
|
114
|
+
|
115
|
+
def filter_params
|
116
|
+
params.permit(:name, :status, :category, :author)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class DocumentFilters < Kiroshi::Filters
|
121
|
+
filter_by :name, match: :like
|
122
|
+
filter_by :status
|
123
|
+
filter_by :category
|
124
|
+
filter_by :author, match: :like
|
125
|
+
end
|
126
|
+
```
|
127
|
+
|
128
|
+
##### Nested Resource Filtering
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
class ArticleFilters < Kiroshi::Filters
|
132
|
+
filter_by :title, match: :like
|
133
|
+
filter_by :published
|
134
|
+
filter_by :tag, match: :like
|
135
|
+
end
|
136
|
+
|
137
|
+
# In your controller
|
138
|
+
def articles
|
139
|
+
base_scope = current_user.articles
|
140
|
+
article_filters.apply(base_scope)
|
141
|
+
end
|
142
|
+
|
143
|
+
def article_filters
|
144
|
+
ArticleFilters.new(params.permit(:title, :published, :tag))
|
145
|
+
end
|
146
|
+
```
|
147
|
+
|
148
|
+
### Kiroshi::Filter
|
149
|
+
|
150
|
+
[Filter](https://www.rubydoc.info/gems/kiroshi/Kiroshi/Filter)
|
151
|
+
is the individual filter class that applies filtering logic to ActiveRecord scopes.
|
152
|
+
It's automatically used by `Kiroshi::Filters`, but can also be used standalone.
|
153
|
+
|
154
|
+
#### Standalone Usage
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
# Create individual filters
|
158
|
+
name_filter = Kiroshi::Filter.new(:name, match: :like)
|
159
|
+
status_filter = Kiroshi::Filter.new(:status, match: :exact)
|
160
|
+
|
161
|
+
# Apply filters manually
|
162
|
+
scope = Document.all
|
163
|
+
scope = name_filter.apply(scope, { name: 'report' })
|
164
|
+
scope = status_filter.apply(scope, { status: 'published' })
|
165
|
+
```
|
166
|
+
|
167
|
+
#### Filter Options
|
168
|
+
|
169
|
+
- `match: :exact` - Performs exact matching (default)
|
170
|
+
- `match: :like` - Performs partial matching using SQL LIKE
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
# Exact match filter
|
174
|
+
exact_filter = Kiroshi::Filter.new(:status)
|
175
|
+
exact_filter.apply(Document.all, { status: 'published' })
|
176
|
+
# Generates: WHERE status = 'published'
|
177
|
+
|
178
|
+
# LIKE match filter
|
179
|
+
like_filter = Kiroshi::Filter.new(:title, match: :like)
|
180
|
+
like_filter.apply(Document.all, { title: 'Ruby' })
|
181
|
+
# Generates: WHERE title LIKE '%Ruby%'
|
182
|
+
```
|
183
|
+
|
184
|
+
#### Empty Value Handling
|
185
|
+
|
186
|
+
Filters automatically ignore empty or nil values:
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
filter = Kiroshi::Filter.new(:name)
|
190
|
+
filter.apply(Document.all, { name: nil }) # Returns original scope
|
191
|
+
filter.apply(Document.all, { name: '' }) # Returns original scope
|
192
|
+
filter.apply(Document.all, {}) # Returns original scope
|
193
|
+
filter.apply(Document.all, { name: 'value' }) # Applies filter
|
194
|
+
```
|
data/config/yardstick.yml
CHANGED
data/kiroshi.jpg
ADDED
Binary file
|
@@ -0,0 +1,99 @@
|
|
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
|
+
# Creates a new Filter instance
|
22
|
+
#
|
23
|
+
# @param attribute [Symbol] the attribute name to filter by
|
24
|
+
# @param match [Symbol] the matching type, defaults to :exact
|
25
|
+
# @option match [Symbol] :exact performs exact matching (default)
|
26
|
+
# @option match [Symbol] :like performs partial matching using SQL LIKE
|
27
|
+
#
|
28
|
+
# @example Creating an exact match filter
|
29
|
+
# filter = Kiroshi::Filter.new(:status)
|
30
|
+
#
|
31
|
+
# @example Creating a partial match filter
|
32
|
+
# filter = Kiroshi::Filter.new(:name, match: :like)
|
33
|
+
#
|
34
|
+
# @since 0.1.0
|
35
|
+
def initialize(attribute, match: :exact)
|
36
|
+
@attribute = attribute
|
37
|
+
@match = match
|
38
|
+
end
|
39
|
+
|
40
|
+
# Applies the filter to the given scope
|
41
|
+
#
|
42
|
+
# This method examines the filters hash for a value corresponding to the
|
43
|
+
# filter's attribute and applies the appropriate WHERE clause to the scope.
|
44
|
+
# If no value is present or the value is blank, the original scope is returned unchanged.
|
45
|
+
#
|
46
|
+
# @param scope [ActiveRecord::Relation] the ActiveRecord scope to filter
|
47
|
+
# @param filters [Hash] a hash containing filter values
|
48
|
+
#
|
49
|
+
# @return [ActiveRecord::Relation] the filtered scope
|
50
|
+
#
|
51
|
+
# @example Applying an exact filter
|
52
|
+
# filter = Kiroshi::Filter.new(:status)
|
53
|
+
# filter.apply(Document.all, { status: 'published' })
|
54
|
+
# # Generates: WHERE status = 'published'
|
55
|
+
#
|
56
|
+
# @example Applying a LIKE filter
|
57
|
+
# filter = Kiroshi::Filter.new(:title, match: :like)
|
58
|
+
# filter.apply(Article.all, { title: 'Ruby' })
|
59
|
+
# # Generates: WHERE title LIKE '%Ruby%'
|
60
|
+
#
|
61
|
+
# @example With empty filter value
|
62
|
+
# filter = Kiroshi::Filter.new(:name)
|
63
|
+
# filter.apply(User.all, { name: nil })
|
64
|
+
# # Returns the original scope unchanged
|
65
|
+
#
|
66
|
+
# @since 0.1.0
|
67
|
+
def apply(scope, filters)
|
68
|
+
filter_value = filters[attribute]
|
69
|
+
return scope unless filter_value.present?
|
70
|
+
|
71
|
+
case match
|
72
|
+
when :like
|
73
|
+
scope.where("#{attribute} LIKE ?", "%#{filter_value}%")
|
74
|
+
else # :exact (default)
|
75
|
+
scope.where(attribute => filter_value)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
attr_reader :attribute, :match
|
82
|
+
|
83
|
+
# @!method attribute
|
84
|
+
# @api private
|
85
|
+
# @private
|
86
|
+
#
|
87
|
+
# Returns the attribute name to filter by
|
88
|
+
#
|
89
|
+
# @return [Symbol] the attribute name to filter by
|
90
|
+
|
91
|
+
# @!method match
|
92
|
+
# @api private
|
93
|
+
# @private
|
94
|
+
#
|
95
|
+
# Returns the matching type (+:exact+ or +:like+)
|
96
|
+
#
|
97
|
+
# @return [Symbol] the matching type (+:exact+ or +:like+)
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kiroshi
|
4
|
+
# @api public
|
5
|
+
# Base class for implementing filter sets on ActiveRecord scopes
|
6
|
+
#
|
7
|
+
# This class provides a foundation for creating reusable filter collections
|
8
|
+
# that can be applied to ActiveRecord queries. It uses a class-level DSL
|
9
|
+
# to define filters and an instance-level interface to apply them.
|
10
|
+
#
|
11
|
+
# The class is designed to be inherited by specific filter implementations
|
12
|
+
# that define their own set of filters using the {.filter_by} method.
|
13
|
+
#
|
14
|
+
# @api public
|
15
|
+
# @author darthjee
|
16
|
+
#
|
17
|
+
# @example Basic usage with inheritance
|
18
|
+
# class DocumentFilters < Kiroshi::Filters
|
19
|
+
# filter_by :name, match: :like
|
20
|
+
# filter_by :status
|
21
|
+
# filter_by :created_at, match: :exact
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# filters = DocumentFilters.new(name: 'report', status: 'published')
|
25
|
+
# filtered_documents = filters.apply(Document.all)
|
26
|
+
#
|
27
|
+
# @example Multiple filter types
|
28
|
+
# class UserFilters < Kiroshi::Filters
|
29
|
+
# filter_by :email, match: :like
|
30
|
+
# filter_by :role
|
31
|
+
# filter_by :active, match: :exact
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# filters = UserFilters.new(email: 'admin', role: 'moderator')
|
35
|
+
# filtered_users = filters.apply(User.all)
|
36
|
+
#
|
37
|
+
# @since 0.1.0
|
38
|
+
class Filters
|
39
|
+
class << self
|
40
|
+
# Defines a filter for the current filter class
|
41
|
+
#
|
42
|
+
# This method is used at the class level to configure filters that will
|
43
|
+
# be applied when {#apply} is called. Each call creates a new {Filter}
|
44
|
+
# instance with the specified configuration.
|
45
|
+
#
|
46
|
+
# @param attribute [Symbol] the attribute name to filter by
|
47
|
+
# @param options [Hash] additional options passed to {Filter#initialize}
|
48
|
+
# @option options [Symbol] :match (:exact) the matching type
|
49
|
+
# - +:exact+ for exact matching (default)
|
50
|
+
# - +:like+ for partial matching using SQL LIKE
|
51
|
+
#
|
52
|
+
# @return [Filter] the new filter instance
|
53
|
+
#
|
54
|
+
# @example Defining exact match filters
|
55
|
+
# class ProductFilters < Kiroshi::Filters
|
56
|
+
# filter_by :category
|
57
|
+
# filter_by :brand
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# @example Defining partial match filters
|
61
|
+
# class SearchFilters < Kiroshi::Filters
|
62
|
+
# filter_by :title, match: :like
|
63
|
+
# filter_by :description, match: :like
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# @example Mixed filter types
|
67
|
+
# class OrderFilters < Kiroshi::Filters
|
68
|
+
# filter_by :customer_name, match: :like
|
69
|
+
# filter_by :status, match: :exact
|
70
|
+
# filter_by :payment_method
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# @since 0.1.0
|
74
|
+
def filter_by(attribute, **)
|
75
|
+
Filter.new(attribute, **).tap do |filter|
|
76
|
+
filter_configs << filter
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns the list of configured filters for this class
|
81
|
+
#
|
82
|
+
# @return [Array<Filter>] array of {Filter} instances configured
|
83
|
+
# for this filter class
|
84
|
+
#
|
85
|
+
# @example Accessing configured filters
|
86
|
+
# class MyFilters < Kiroshi::Filters
|
87
|
+
# filter_by :name
|
88
|
+
# filter_by :status, match: :like
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
# MyFilters.filter_configs.length # => 2
|
92
|
+
# MyFilters.filter_configs.first.attribute # => :name
|
93
|
+
#
|
94
|
+
# @since 0.1.0
|
95
|
+
def filter_configs
|
96
|
+
@filter_configs ||= []
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Creates a new Filters instance
|
101
|
+
#
|
102
|
+
# @param filters [Hash] a hash containing the filter values to be applied.
|
103
|
+
# Keys should correspond to attributes defined with {.filter_by}.
|
104
|
+
# Values will be used for filtering. Nil or blank values are ignored.
|
105
|
+
#
|
106
|
+
# @example Creating filters with values
|
107
|
+
# filters = DocumentFilters.new(
|
108
|
+
# name: 'annual report',
|
109
|
+
# status: 'published',
|
110
|
+
# category: 'finance'
|
111
|
+
# )
|
112
|
+
#
|
113
|
+
# @example Creating filters with partial values
|
114
|
+
# filters = UserFilters.new(email: 'admin') # Only email filter will be applied
|
115
|
+
#
|
116
|
+
# @example Creating empty filters
|
117
|
+
# filters = ProductFilters.new({}) # No filters will be applied
|
118
|
+
#
|
119
|
+
# @since 0.1.0
|
120
|
+
def initialize(filters = {})
|
121
|
+
@filters = filters || {}
|
122
|
+
end
|
123
|
+
|
124
|
+
# Applies all configured filters to the given scope
|
125
|
+
#
|
126
|
+
# This method iterates through all filters defined via {.filter_by}
|
127
|
+
# and applies each one sequentially to the scope. Filters with no
|
128
|
+
# corresponding value in the filters hash or with blank values are
|
129
|
+
# automatically skipped.
|
130
|
+
#
|
131
|
+
# @param scope [ActiveRecord::Relation] the ActiveRecord scope to filter
|
132
|
+
#
|
133
|
+
# @return [ActiveRecord::Relation] the filtered scope with all
|
134
|
+
# applicable filters applied
|
135
|
+
#
|
136
|
+
# @example Applying filters to a scope
|
137
|
+
# class ArticleFilters < Kiroshi::Filters
|
138
|
+
# filter_by :title, match: :like
|
139
|
+
# filter_by :published, match: :exact
|
140
|
+
# end
|
141
|
+
#
|
142
|
+
# filters = ArticleFilters.new(title: 'Ruby', published: true)
|
143
|
+
# filtered_articles = filters.apply(Article.all)
|
144
|
+
# # Generates: WHERE title LIKE '%Ruby%' AND published = true
|
145
|
+
#
|
146
|
+
# @example With empty filters
|
147
|
+
# filters = ArticleFilters.new({})
|
148
|
+
# filtered_articles = filters.apply(Article.all)
|
149
|
+
# # Returns the original scope unchanged
|
150
|
+
#
|
151
|
+
# @example With partial filters
|
152
|
+
# filters = ArticleFilters.new(title: 'Ruby') # published filter ignored
|
153
|
+
# filtered_articles = filters.apply(Article.all)
|
154
|
+
# # Generates: WHERE title LIKE '%Ruby%'
|
155
|
+
#
|
156
|
+
# @since 0.1.0
|
157
|
+
def apply(scope)
|
158
|
+
self.class.filter_configs.each do |filter|
|
159
|
+
scope = filter.apply(scope, filters)
|
160
|
+
end
|
161
|
+
|
162
|
+
scope
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
attr_reader :filters
|
168
|
+
|
169
|
+
# @!method filters
|
170
|
+
# @api private
|
171
|
+
# @private
|
172
|
+
#
|
173
|
+
# Returns the hash of filter values to be applied
|
174
|
+
#
|
175
|
+
# @return [Hash] the hash of filter values to be applied
|
176
|
+
end
|
177
|
+
end
|
data/lib/kiroshi/version.rb
CHANGED
data/lib/kiroshi.rb
CHANGED
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Kiroshi::Filter, type: :model do
|
6
|
+
describe '#apply' do
|
7
|
+
let(:scope) { Document.all }
|
8
|
+
let(:filter_value) { 'test_value' }
|
9
|
+
let(:filters) { { name: filter_value } }
|
10
|
+
let!(:matching_document) { create(:document, name: filter_value) }
|
11
|
+
let!(:non_matching_document) { create(:document, name: 'other_value') }
|
12
|
+
|
13
|
+
context 'when match is :exact' do
|
14
|
+
subject(:filter) { described_class.new(:name, match: :exact) }
|
15
|
+
|
16
|
+
it 'returns exact matches' do
|
17
|
+
expect(filter.apply(scope, filters)).to include(matching_document)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'does not return non-matching records' do
|
21
|
+
expect(filter.apply(scope, filters)).not_to include(non_matching_document)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'when match is :like' do
|
26
|
+
subject(:filter) { described_class.new(:name, match: :like) }
|
27
|
+
|
28
|
+
let(:filter_value) { 'test' }
|
29
|
+
let!(:matching_document) { create(:document, name: 'test_document') }
|
30
|
+
let!(:non_matching_document) { create(:document, name: 'other_value') }
|
31
|
+
|
32
|
+
it 'returns partial matches' do
|
33
|
+
expect(filter.apply(scope, filters)).to include(matching_document)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'does not return non-matching records' do
|
37
|
+
expect(filter.apply(scope, filters)).not_to include(non_matching_document)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'when match is not specified (default)' do
|
42
|
+
subject(:filter) { described_class.new(:name) }
|
43
|
+
|
44
|
+
it 'defaults to exact match returning only exact matches' do
|
45
|
+
expect(filter.apply(scope, filters)).to include(matching_document)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'defaults to exact match returning not returning when filtering by a non-matching value' do
|
49
|
+
expect(filter.apply(scope, filters)).not_to include(non_matching_document)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'when filter value is not present' do
|
54
|
+
subject(:filter) { described_class.new(:name) }
|
55
|
+
|
56
|
+
let(:filters) { { name: nil } }
|
57
|
+
|
58
|
+
it 'returns the original scope unchanged' do
|
59
|
+
expect(filter.apply(scope, filters)).to eq(scope)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Kiroshi::Filters, type: :model do
|
6
|
+
describe '#apply' do
|
7
|
+
subject(:filter_instance) { filters_class.new(filters) }
|
8
|
+
|
9
|
+
let(:scope) { Document.all }
|
10
|
+
let(:filters) { {} }
|
11
|
+
let!(:document) { create(:document, name: 'test_name', status: 'finished') }
|
12
|
+
let!(:other_document) { create(:document, name: 'other_name', status: 'processing') }
|
13
|
+
|
14
|
+
let(:filters_class) { Class.new(described_class) }
|
15
|
+
|
16
|
+
context 'when no filters are configured' do
|
17
|
+
context 'when no filters are provided' do
|
18
|
+
it 'returns the original scope unchanged' do
|
19
|
+
expect(filter_instance.apply(scope)).to eq(scope)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'when filters are provided' do
|
24
|
+
let(:filters) { { name: 'test_name' } }
|
25
|
+
|
26
|
+
it 'returns the original scope unchanged' do
|
27
|
+
expect(filter_instance.apply(scope)).to eq(scope)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when one exact filter is configured' do
|
33
|
+
let(:filters) { { name: 'test_name' } }
|
34
|
+
|
35
|
+
before do
|
36
|
+
filters_class.filter_by :name
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'returns documents matching the exact filter' do
|
40
|
+
expect(filter_instance.apply(scope)).to include(document)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'does not return documents not matching the exact filter' do
|
44
|
+
expect(filter_instance.apply(scope)).not_to include(other_document)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'when one like filter is configured' do
|
49
|
+
let(:filters) { { name: 'test' } }
|
50
|
+
|
51
|
+
before do
|
52
|
+
filters_class.filter_by :name, match: :like
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'returns documents matching the like filter' do
|
56
|
+
expect(filter_instance.apply(scope)).to include(document)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'does not return documents not matching the like filter' do
|
60
|
+
expect(filter_instance.apply(scope)).not_to include(other_document)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'when multiple filters are configured' do
|
65
|
+
let(:filters) { { name: 'test', status: 'finished' } }
|
66
|
+
|
67
|
+
before do
|
68
|
+
filters_class.filter_by :name, match: :like
|
69
|
+
filters_class.filter_by :status
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'returns documents matching all filters' do
|
73
|
+
expect(filter_instance.apply(scope)).to include(document)
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'does not return documents not matching all filters' do
|
77
|
+
expect(filter_instance.apply(scope)).not_to include(other_document)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'when filters hash is empty' do
|
82
|
+
before do
|
83
|
+
filters_class.filter_by :name
|
84
|
+
filters_class.filter_by :status
|
85
|
+
end
|
86
|
+
|
87
|
+
let(:filters) { {} }
|
88
|
+
|
89
|
+
it 'returns the original scope unchanged' do
|
90
|
+
expect(filter_instance.apply(scope)).to eq(scope)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/spec/spec_helper.rb
CHANGED
data/spec/support/db/schema.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kiroshi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Darthjee
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-08-
|
11
|
+
date: 2025-08-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -62,14 +62,22 @@ files:
|
|
62
62
|
- config/yardstick.yml
|
63
63
|
- docker-compose.yml
|
64
64
|
- kiroshi.gemspec
|
65
|
+
- kiroshi.jpg
|
65
66
|
- lib/kiroshi.rb
|
67
|
+
- lib/kiroshi/filter.rb
|
68
|
+
- lib/kiroshi/filters.rb
|
66
69
|
- lib/kiroshi/version.rb
|
67
70
|
- spec/integration/readme/.keep
|
68
71
|
- spec/integration/yard/.keep
|
72
|
+
- spec/lib/kiroshi/filter_spec.rb
|
73
|
+
- spec/lib/kiroshi/filters_spec.rb
|
69
74
|
- spec/lib/kiroshi_spec.rb
|
70
75
|
- spec/spec_helper.rb
|
71
76
|
- spec/support/db/schema.rb
|
77
|
+
- spec/support/factories/document.rb
|
78
|
+
- spec/support/factory_bot.rb
|
72
79
|
- spec/support/models/.keep
|
80
|
+
- spec/support/models/document.rb
|
73
81
|
- spec/support/shared_examples/.keep
|
74
82
|
homepage: https://github.com/darthjee/kiroshi
|
75
83
|
licenses: []
|