lupa 1.0.1 → 1.0.2
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 +5 -5
- data/.github/workflows/ci.yml +56 -0
- data/CHANGELOG.md +22 -1
- data/README.md +83 -48
- data/lib/lupa/scope_methods.rb +80 -3
- data/lib/lupa/search.rb +594 -153
- data/lib/lupa/version.rb +1 -1
- data/lib/lupa.rb +128 -4
- data/lupa.gemspec +3 -3
- data/test/test_helper.rb +6 -2
- metadata +18 -21
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d6c3063e519871ee2848d6202a6388482747e7e2668f04122036e3a5ae8dd1fa
|
|
4
|
+
data.tar.gz: 7cb6850bb59992671ac66d19ef3179bb7c400dae6d39faaa7cbe5bfbdb837643
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4b41e376873605d430296381df17225bf5d168b885857be7a5b395d206b4a7ab4f388473928898003539df96bbb64e6d4fc02c130914c5245b6adf0d3c2034ca
|
|
7
|
+
data.tar.gz: 3003501628ef7c228bf4762bdbaf9f81cb00b2b6e436f75940adfa710e1a2ffd30224b6a3155a828c1fa942a1ca1589ae7e9fbf4056c83dede6b0622381a423e
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ master ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ master ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
ruby-version:
|
|
16
|
+
- '2.2'
|
|
17
|
+
- '2.3'
|
|
18
|
+
- '2.4'
|
|
19
|
+
- '2.5'
|
|
20
|
+
- '2.6'
|
|
21
|
+
- '2.7'
|
|
22
|
+
- '3.0'
|
|
23
|
+
- '3.1'
|
|
24
|
+
- '3.2'
|
|
25
|
+
- '3.3'
|
|
26
|
+
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v4
|
|
29
|
+
|
|
30
|
+
- name: Set up Ruby ${{ matrix.ruby-version }}
|
|
31
|
+
uses: ruby/setup-ruby@v1
|
|
32
|
+
with:
|
|
33
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
34
|
+
bundler-cache: true
|
|
35
|
+
|
|
36
|
+
- name: Run tests
|
|
37
|
+
run: bundle exec rake test
|
|
38
|
+
|
|
39
|
+
- name: Upload coverage to Coveralls
|
|
40
|
+
if: ${{ matrix.ruby-version >= '2.5' }}
|
|
41
|
+
uses: coverallsapp/github-action@v2
|
|
42
|
+
with:
|
|
43
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
44
|
+
flag-name: ruby-${{ matrix.ruby-version }}
|
|
45
|
+
parallel: true
|
|
46
|
+
|
|
47
|
+
coveralls-finish:
|
|
48
|
+
needs: test
|
|
49
|
+
runs-on: ubuntu-latest
|
|
50
|
+
steps:
|
|
51
|
+
- name: Coveralls Finished
|
|
52
|
+
uses: coverallsapp/github-action@v2
|
|
53
|
+
with:
|
|
54
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
55
|
+
parallel-finished: true
|
|
56
|
+
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,25 @@
|
|
|
1
|
+
## 1.0.2
|
|
2
|
+
|
|
3
|
+
* documentation
|
|
4
|
+
* Add comprehensive RDoc documentation for all classes and methods
|
|
5
|
+
* Include detailed examples and usage patterns in RDoc
|
|
6
|
+
* Document all error classes with practical examples
|
|
7
|
+
* Fix multiple typos in README.md
|
|
8
|
+
* Update Table of Contents links in README.md
|
|
9
|
+
* Add benchmarks section comparing Lupa with HasScope and Searchlight
|
|
10
|
+
* Improve code examples and usage patterns in README.md
|
|
11
|
+
|
|
12
|
+
* ci
|
|
13
|
+
* Migrate from Travis CI to GitHub Actions
|
|
14
|
+
* Add support for Ruby 2.2 through 3.3
|
|
15
|
+
* Configure Coveralls for Ruby 2.5+ (conditional loading for compatibility)
|
|
16
|
+
|
|
17
|
+
* dependencies
|
|
18
|
+
* Update minitest development dependency from ~> 5.5.1 to ~> 5.5
|
|
19
|
+
* Update bundler development dependency from ~> 1.6 to >= 1.6
|
|
20
|
+
* Add rake development dependency >= 10.0
|
|
21
|
+
|
|
1
22
|
## 1.0.1
|
|
2
23
|
|
|
3
24
|
* enhancements
|
|
4
|
-
* A **Lupa::DefaultSearchAttributesError** exception will be raised if `default_search_attributes` does not return a hash.
|
|
25
|
+
* A **Lupa::DefaultSearchAttributesError** exception will be raised if `default_search_attributes` does not return a hash.
|
data/README.md
CHANGED
|
@@ -2,28 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
Lupa means *Magnifier* in spanish.
|
|
4
4
|
|
|
5
|
-
[](https://github.com/edelpero/lupa/actions/workflows/ci.yml) [](https://coveralls.io/r/edelpero/lupa?branch=master) [](https://codeclimate.com/github/edelpero/lupa) [](http://inch-ci.org/github/edelpero/lupa)
|
|
6
6
|
|
|
7
7
|
Lupa lets you create simple, robust and scaleable search filters with ease using regular Ruby classes and object oriented design patterns.
|
|
8
8
|
|
|
9
|
-
Lupa
|
|
9
|
+
Lupa is Framework and ORM agnostic. It will work with any ORM or Object that can build a query using **chained method calls**, like ActiveRecord: `
|
|
10
10
|
Product.where(name: 'Digital').where(category: '23').limit(2)`.
|
|
11
11
|
|
|
12
12
|
**Table of Contents:**
|
|
13
13
|
|
|
14
|
-
* [Search Class](
|
|
15
|
-
* [Overview](
|
|
16
|
-
* [Definition](
|
|
17
|
-
* [Public Methods](
|
|
18
|
-
* [Default Search Scope](
|
|
19
|
-
* [Default Search Attributes](
|
|
20
|
-
* [Combining Search Classes](
|
|
21
|
-
* [Usage with Rails](
|
|
22
|
-
* [Testing](
|
|
23
|
-
* [Testing Default Scope](
|
|
24
|
-
* [Testing Default Search Attributes](
|
|
25
|
-
* [Testing Each Scope Method Individually](
|
|
26
|
-
* [
|
|
14
|
+
* [Search Class](#search-class)
|
|
15
|
+
* [Overview](#overview)
|
|
16
|
+
* [Definition](#definition)
|
|
17
|
+
* [Public Methods](#public-methods)
|
|
18
|
+
* [Default Search Scope](#default-search-scope)
|
|
19
|
+
* [Default Search Attributes](#default-search-attributes)
|
|
20
|
+
* [Combining Search Classes](#combining-search-classes)
|
|
21
|
+
* [Usage with Rails](#usage-with-rails)
|
|
22
|
+
* [Testing](#testing)
|
|
23
|
+
* [Testing Default Scope](#testing-default-scope)
|
|
24
|
+
* [Testing Default Search Attributes](#testing-default-search-attributes)
|
|
25
|
+
* [Testing Each Scope Method Individually](#testing-each-scope-method-individually)
|
|
26
|
+
* [Benchmarks](#benchmarks)
|
|
27
|
+
* [Lupa vs HasScope](#lupa-vs-hasscope)
|
|
28
|
+
* [Lupa vs Searchlight](#lupa-vs-searchlight)
|
|
29
|
+
* [Installation](#installation)
|
|
27
30
|
|
|
28
31
|
|
|
29
32
|
## Search Class
|
|
@@ -49,9 +52,7 @@ class ProductSearch < Lupa::Search
|
|
|
49
52
|
|
|
50
53
|
# Search method
|
|
51
54
|
def name
|
|
52
|
-
|
|
53
|
-
scope.where('name iLIKE ?', "%#{search_attributes[:name]}%")
|
|
54
|
-
end
|
|
55
|
+
scope.where('name iLIKE ?', "%#{search_attributes[:name]}%")
|
|
55
56
|
end
|
|
56
57
|
|
|
57
58
|
# Search method
|
|
@@ -90,16 +91,7 @@ class ProductSearch < Lupa::Search
|
|
|
90
91
|
|
|
91
92
|
# Search method
|
|
92
93
|
def name
|
|
93
|
-
|
|
94
|
-
scope.where('name LIKE ?', "%#{search_attributes[:name]}%")
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# Search method
|
|
99
|
-
def created_between
|
|
100
|
-
if created_start_date && created_end_date
|
|
101
|
-
scope.where(created_at: created_start_date..created_end_date)
|
|
102
|
-
end
|
|
94
|
+
scope.where('name LIKE ?', "%#{search_attributes[:name]}%")
|
|
103
95
|
end
|
|
104
96
|
|
|
105
97
|
# Search method
|
|
@@ -107,16 +99,6 @@ class ProductSearch < Lupa::Search
|
|
|
107
99
|
scope.where(category_id: search_attributes[:category])
|
|
108
100
|
end
|
|
109
101
|
|
|
110
|
-
private
|
|
111
|
-
# Parses search_attributes[:created_between][:start_date]
|
|
112
|
-
def created_start_date
|
|
113
|
-
search_attributes[:created_between] && search_attributes[:created_between][:start_date].try(:to_date)
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
# Parses search_attributes[:created_between][:end_date]
|
|
117
|
-
def created_end_date
|
|
118
|
-
search_attributes[:created_between] && search_attributes[:created_between][:end_date].try(:to_date)
|
|
119
|
-
end
|
|
120
102
|
end
|
|
121
103
|
end
|
|
122
104
|
```
|
|
@@ -179,7 +161,7 @@ class ProductSearch < Lupa::Search
|
|
|
179
161
|
end
|
|
180
162
|
|
|
181
163
|
# Be careful not to change the scope variable name,
|
|
182
|
-
# otherwise you will
|
|
164
|
+
# otherwise you will experience issues.
|
|
183
165
|
def initialize(scope = Product.all)
|
|
184
166
|
@scope = scope
|
|
185
167
|
end
|
|
@@ -209,17 +191,25 @@ class ProductSearch < Lupa::Search
|
|
|
209
191
|
|
|
210
192
|
# This should always return a hash
|
|
211
193
|
def default_search_attributes
|
|
212
|
-
{ category: 23 }
|
|
194
|
+
{ category: '23' }
|
|
213
195
|
end
|
|
214
196
|
end
|
|
215
197
|
```
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
search = ProductSearch.new(current_user.products).search(name: 'chair')
|
|
201
|
+
|
|
202
|
+
search.search_attributes
|
|
203
|
+
# => { name: 'chair', category: '23' }
|
|
204
|
+
```
|
|
205
|
+
|
|
216
206
|
**<u>Note:</u>** You can override default search attributes by passing it to the search params.
|
|
217
207
|
|
|
218
208
|
``` ruby
|
|
219
209
|
search = ProductSearch.new(current_user.products).search(name: 'chair', category: '42')
|
|
220
210
|
|
|
221
211
|
search.search_attributes
|
|
222
|
-
# => { name: 'chair', category: 42 }
|
|
212
|
+
# => { name: 'chair', category: '42' }
|
|
223
213
|
```
|
|
224
214
|
|
|
225
215
|
### Combining Search Classes
|
|
@@ -250,12 +240,16 @@ class CreatedAtSearch < Lupa::Search
|
|
|
250
240
|
|
|
251
241
|
private
|
|
252
242
|
|
|
243
|
+
# Parses search_attributes[:created_between][:start_date]
|
|
253
244
|
def created_start_date
|
|
254
|
-
search_attributes[:created_between] &&
|
|
245
|
+
search_attributes[:created_between] &&
|
|
246
|
+
search_attributes[:created_between][:start_date].try(:to_date)
|
|
255
247
|
end
|
|
256
248
|
|
|
249
|
+
# Parses search_attributes[:created_between][:end_date]
|
|
257
250
|
def created_end_date
|
|
258
|
-
search_attributes[:created_between] &&
|
|
251
|
+
search_attributes[:created_between] &&
|
|
252
|
+
search_attributes[:created_between][:end_date].try(:to_date)
|
|
259
253
|
end
|
|
260
254
|
end
|
|
261
255
|
end
|
|
@@ -273,11 +267,13 @@ class ProductSearch < Lupa::Search
|
|
|
273
267
|
...
|
|
274
268
|
end
|
|
275
269
|
|
|
276
|
-
# We use CreatedAtSearch class to perform the search
|
|
270
|
+
# We use CreatedAtSearch class to perform the search.
|
|
271
|
+
# Be sure to always call `results` method on your composed
|
|
272
|
+
# search class.
|
|
277
273
|
def created_between
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
274
|
+
CreatedAtSearch.new(scope).
|
|
275
|
+
search(created_between: search_attributes[:created_between]).
|
|
276
|
+
results
|
|
281
277
|
end
|
|
282
278
|
|
|
283
279
|
def category
|
|
@@ -287,6 +283,7 @@ class ProductSearch < Lupa::Search
|
|
|
287
283
|
end
|
|
288
284
|
end
|
|
289
285
|
```
|
|
286
|
+
**Note:** If you are combining search classes. Be sure to always call **results** method on the search classes composing your main search class.
|
|
290
287
|
|
|
291
288
|
## Usage with Rails
|
|
292
289
|
|
|
@@ -323,7 +320,9 @@ class ProductsController < ApplicationController
|
|
|
323
320
|
end
|
|
324
321
|
end
|
|
325
322
|
```
|
|
326
|
-
|
|
323
|
+
### Views
|
|
324
|
+
|
|
325
|
+
Loop through the search results on your view.
|
|
327
326
|
|
|
328
327
|
```haml
|
|
329
328
|
# app/views/products/index.html.haml
|
|
@@ -460,6 +459,42 @@ describe ProductSearch do
|
|
|
460
459
|
end
|
|
461
460
|
```
|
|
462
461
|
|
|
462
|
+
## Benchmarks
|
|
463
|
+
|
|
464
|
+
I used [benchmark-ips](https://github.com/evanphx/benchmark-ips).
|
|
465
|
+
|
|
466
|
+
### Lupa vs. [HasScope](https://github.com/plataformatec/has_scope)
|
|
467
|
+
|
|
468
|
+
```
|
|
469
|
+
Calculating -------------------------------------
|
|
470
|
+
lupa 265.000 i/100ms
|
|
471
|
+
has_scope 254.000 i/100ms
|
|
472
|
+
-------------------------------------------------
|
|
473
|
+
lupa 3.526k (±24.7%) i/s - 67.045k
|
|
474
|
+
has_scope 3.252k (±24.8%) i/s - 61.976k
|
|
475
|
+
|
|
476
|
+
Comparison:
|
|
477
|
+
lupa: 3525.8 i/s
|
|
478
|
+
has_scope: 3252.0 i/s - 1.08x slower
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### Lupa vs. [Searchlight](https://github.com/nathanl/searchlight)
|
|
482
|
+
|
|
483
|
+
```
|
|
484
|
+
Calculating -------------------------------------
|
|
485
|
+
lupa 480.000 i/100ms
|
|
486
|
+
searchlight 232.000 i/100ms
|
|
487
|
+
-------------------------------------------------
|
|
488
|
+
lupa 7.273k (±25.1%) i/s - 689.280k
|
|
489
|
+
searchlight 2.665k (±14.1%) i/s - 260.072k
|
|
490
|
+
|
|
491
|
+
Comparison:
|
|
492
|
+
lupa: 7273.5 i/s
|
|
493
|
+
searchlight: 2665.4 i/s - 2.73x slower
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
*If you know about another gem that was not included on the benchmark, feel free to run the benchmarks and send a Pull Request.*
|
|
497
|
+
|
|
463
498
|
## Installation
|
|
464
499
|
|
|
465
500
|
Add this line to your application's Gemfile:
|
data/lib/lupa/scope_methods.rb
CHANGED
|
@@ -1,13 +1,90 @@
|
|
|
1
1
|
module Lupa
|
|
2
|
+
# Internal module that provides common functionality to Scope classes.
|
|
3
|
+
# This module is automatically included in the Scope class defined within
|
|
4
|
+
# your search class.
|
|
5
|
+
#
|
|
6
|
+
# It provides access to two key attributes:
|
|
7
|
+
# - `scope`: The current scope being searched
|
|
8
|
+
# - `search_attributes`: The hash of search parameters
|
|
9
|
+
#
|
|
10
|
+
# @example Accessing scope and search_attributes in a Scope class
|
|
11
|
+
# class ProductSearch < Lupa::Search
|
|
12
|
+
# class Scope
|
|
13
|
+
# # scope and search_attributes are available here
|
|
14
|
+
# def name
|
|
15
|
+
# # scope is the current ActiveRecord relation (or any chainable object)
|
|
16
|
+
# scope.where('name LIKE ?', "%#{search_attributes[:name]}%")
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# def price_range
|
|
20
|
+
# # search_attributes contains all search parameters
|
|
21
|
+
# if search_attributes[:price_range]
|
|
22
|
+
# scope.where(price: search_attributes[:price_range])
|
|
23
|
+
# else
|
|
24
|
+
# scope
|
|
25
|
+
# end
|
|
26
|
+
# end
|
|
27
|
+
# end
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# @note This module is included automatically by Lupa and should not be
|
|
31
|
+
# included manually in your code.
|
|
32
|
+
#
|
|
33
|
+
# @api private
|
|
34
|
+
# @since 0.1.0
|
|
2
35
|
module ScopeMethods
|
|
3
|
-
|
|
36
|
+
# @!attribute [rw] scope
|
|
37
|
+
# The current scope object that search methods will operate on.
|
|
38
|
+
# This is typically an ActiveRecord::Relation or similar chainable object.
|
|
39
|
+
# The scope is updated after each search method is called.
|
|
40
|
+
#
|
|
41
|
+
# @return [Object] the current scope object
|
|
42
|
+
#
|
|
43
|
+
# @example Accessing the scope in a search method
|
|
44
|
+
# def category
|
|
45
|
+
# # scope is the current state of the query chain
|
|
46
|
+
# scope.where(category_id: search_attributes[:category])
|
|
47
|
+
# end
|
|
4
48
|
attr_accessor :scope
|
|
5
|
-
attr_reader :search_attributes
|
|
6
49
|
|
|
50
|
+
# @!attribute [r] search_attributes
|
|
51
|
+
# A hash containing all search attributes, including default search attributes.
|
|
52
|
+
# All keys are symbolized automatically by Lupa.
|
|
53
|
+
#
|
|
54
|
+
# @return [Hash] the search attributes hash with symbolized keys
|
|
55
|
+
#
|
|
56
|
+
# @example Accessing search attributes in a scope method
|
|
57
|
+
# def search_by_name
|
|
58
|
+
# name = search_attributes[:name]
|
|
59
|
+
# scope.where('name LIKE ?', "%#{name}%") if name.present?
|
|
60
|
+
# end
|
|
61
|
+
#
|
|
62
|
+
# @example With nested hash attributes
|
|
63
|
+
# def created_between
|
|
64
|
+
# start_date = search_attributes[:created_between][:start_date]
|
|
65
|
+
# end_date = search_attributes[:created_between][:end_date]
|
|
66
|
+
# scope.where(created_at: start_date..end_date)
|
|
67
|
+
# end
|
|
68
|
+
attr_reader :search_attributes
|
|
69
|
+
|
|
70
|
+
# Initializes a new Scope instance with the given scope and search attributes.
|
|
71
|
+
# This method is called automatically by Lupa::Search and should not be called directly.
|
|
72
|
+
#
|
|
73
|
+
# @param scope [Object] the initial scope object to search on (e.g., ActiveRecord::Relation)
|
|
74
|
+
# @param search_attributes [Hash] the hash of search parameters with symbolized keys
|
|
75
|
+
#
|
|
76
|
+
# @return [ScopeMethods] the initialized scope instance
|
|
77
|
+
#
|
|
78
|
+
# @example Internal usage (automatically called by Lupa)
|
|
79
|
+
# # This happens internally when you call:
|
|
80
|
+
# ProductSearch.new(Product.all).search(name: 'chair')
|
|
81
|
+
# # Lupa automatically calls:
|
|
82
|
+
# ProductSearch::Scope.new(Product.all, { name: 'chair' })
|
|
83
|
+
#
|
|
84
|
+
# @api private
|
|
7
85
|
def initialize(scope, search_attributes)
|
|
8
86
|
@scope = scope
|
|
9
87
|
@search_attributes = search_attributes
|
|
10
88
|
end
|
|
11
|
-
|
|
12
89
|
end
|
|
13
90
|
end
|