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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f215b5db38cfab5958a6d87cfbfffe94a6a4ca9f36b114d6539f064e918aaf3c
4
- data.tar.gz: 11fd8327a643b27eab6feaa92eda2dd962ccbb573501a0dd06f0811ac840dbdc
3
+ metadata.gz: 3bbe023d89c78eca0371b05303b85c44da60ce794a1c39844b2ab719350cc665
4
+ data.tar.gz: aa64d92280278a488c7bb2c5385fc7fd5f28a04a5b7a22aa1cbd1681311bf316
5
5
  SHA512:
6
- metadata.gz: 60fbd8f3a711432be3ecfdd1c565d535657b6424ee46eae72b85d345fed4dd160606eeb7e4c13c83de291b72cce32d6c63bbbc8c31949c1e5c0a3155ddb02821
7
- data.tar.gz: e57185d0789ef027ac87be751cc5054cdf52169beb7a7f9836aaa7e750b9776b4ae86751560c91b20dc20b93775a9f63f58d91948d7d85ec23a5012b1a151fdd
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 21:59:24 UTC using RuboCop version 1.79.2.
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
@@ -6,6 +6,7 @@ gemspec
6
6
 
7
7
  gem 'actionpack', '7.2.2.1'
8
8
  gem 'activerecord', '7.2.2.1'
9
+ gem 'activesupport', '7.2.2.1'
9
10
  gem 'bundler', '~> 2.3'
10
11
  gem 'factory_bot', '6.2.1'
11
12
  gem 'minitest', '5.25.4'
data/README.md CHANGED
@@ -1,16 +1,24 @@
1
- Kiroshi
2
- ====
1
+ # Kiroshi
3
2
  [![Build Status](https://circleci.com/gh/darthjee/kiroshi.svg?style=shield)](https://circleci.com/gh/darthjee/kiroshi)
4
3
  [![Codacy Badge](https://app.codacy.com/project/badge/Grade/35480a5e82e74ff7a0186697b3f61a4b)](https://app.codacy.com/gh/darthjee/kiroshi/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
5
4
 
6
5
  ![kiroshi](https://raw.githubusercontent.com/darthjee/kiroshi/master/kiroshi.jpg)
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
- Installation
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
@@ -1,4 +1,4 @@
1
- threshold: 100
1
+ threshold: 100.0
2
2
  require_exact_threshold: false
3
3
  rules:
4
4
  ApiTag::Presence:
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Kiroshi
4
- VERSION = '0.0.1'
3
+ module Kiroshi
4
+ VERSION = '0.1.0'
5
5
  end
data/lib/kiroshi.rb CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  # @api public
4
4
  # @author darthjee
5
- class Kiroshi
5
+ module Kiroshi
6
6
  autoload :VERSION, 'kiroshi/version'
7
+ autoload :Filters, 'kiroshi/filters'
8
+ autoload :Filter, 'kiroshi/filter'
7
9
  end
@@ -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
@@ -10,6 +10,8 @@ SimpleCov.start 'gem'
10
10
 
11
11
  require 'kiroshi'
12
12
  require 'pry-nav'
13
+ require 'active_support/all'
14
+ require 'factory_bot'
13
15
 
14
16
  require 'active_record'
15
17
  ActiveRecord::Base.establish_connection(
@@ -2,4 +2,9 @@
2
2
 
3
3
  ActiveRecord::Schema.define do
4
4
  self.verbose = false
5
+
6
+ create_table :documents, force: true do |t|
7
+ t.string :name
8
+ t.string :status
9
+ end
5
10
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :document, class: '::Document' do
5
+ sequence(:name) { |n| "Name-#{n}" }
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.configure do |config|
4
+ config.include FactoryBot::Syntax::Methods
5
+ end
6
+
7
+ FactoryBot.find_definitions
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Document < ActiveRecord::Base
4
+ validates :name, presence: true
5
+ end
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.1
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-15 00:00:00.000000000 Z
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: []