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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 46daf9ac53354ecc21016d968c78de4d322a1bb5
4
- data.tar.gz: 62c8cde8889723041b1d622ed2bc1bec5f784ff6
2
+ SHA256:
3
+ metadata.gz: d6c3063e519871ee2848d6202a6388482747e7e2668f04122036e3a5ae8dd1fa
4
+ data.tar.gz: 7cb6850bb59992671ac66d19ef3179bb7c400dae6d39faaa7cbe5bfbdb837643
5
5
  SHA512:
6
- metadata.gz: 010ba896da1a30da488d2726646616ca4e1c7091dd5907ae20150756af248b8173266e4b68f501470c35cbf12d933f0f6f530b8943920b2b0f874bff9a782599
7
- data.tar.gz: 36a3ac1ee305f13318d990f250a40b97ab5f6cb90661c1e546ca4f0b0218e746a05d321d7069944ba100c484e8343125ccb2a8123cc2b15b46e9539e918168e1
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
- [![Build Status](https://travis-ci.org/edelpero/lupa.svg?branch=master)](https://travis-ci.org/edelpero/lupa) [![Coverage Status](https://coveralls.io/repos/edelpero/lupa/badge.svg?branch=master)](https://coveralls.io/r/edelpero/lupa?branch=master) [![Code Climate](https://codeclimate.com/github/edelpero/lupa/badges/gpa.svg)](https://codeclimate.com/github/edelpero/lupa) [![Inline docs](http://inch-ci.org/github/edelpero/lupa.svg?branch=master)](http://inch-ci.org/github/edelpero/lupa)
5
+ [![CI](https://github.com/edelpero/lupa/actions/workflows/ci.yml/badge.svg)](https://github.com/edelpero/lupa/actions/workflows/ci.yml) [![Coverage Status](https://coveralls.io/repos/edelpero/lupa/badge.svg?branch=master)](https://coveralls.io/r/edelpero/lupa?branch=master) [![Code Climate](https://codeclimate.com/github/edelpero/lupa/badges/gpa.svg)](https://codeclimate.com/github/edelpero/lupa) [![Inline docs](http://inch-ci.org/github/edelpero/lupa.svg?branch=master)](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 it's Framework and ORM agnostic. It will work with any ORM or Object that can build a query using **chained method calls**, like ActiveRecord: `
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](https://github.com/edelpero/lupa#search-class)
15
- * [Overview](https://github.com/edelpero/lupa#overview)
16
- * [Definition](https://github.com/edelpero/lupa#definition)
17
- * [Public Methods](https://github.com/edelpero/lupa#public-methods)
18
- * [Default Search Scope](https://github.com/edelpero/lupa#default-search-scope)
19
- * [Default Search Attributes](https://github.com/edelpero/lupa#default-search-attributes)
20
- * [Combining Search Classes](https://github.com/edelpero/lupa#combining-search-classes)
21
- * [Usage with Rails](https://github.com/edelpero/lupa#usage-with-rails)
22
- * [Testing](https://github.com/edelpero/lupa#testing)
23
- * [Testing Default Scope](https://github.com/edelpero/lupa#testing-default-scope)
24
- * [Testing Default Search Attributes](https://github.com/edelpero/lupa#testing-default-search-attributes)
25
- * [Testing Each Scope Method Individually](https://github.com/edelpero/lupa#testing-each-scope-method-individually)
26
- * [Installation](https://github.com/edelpero/lupa#installation)
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
- if search_attributes[:name].present?
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
- if search_attributes[:name].present?
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 experiment some issues.
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] && search_attributes[:created_between][:start_date].try(:to_date)
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] && search_attributes[:created_between][:end_date].try(:to_date)
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
- if search_attributes[:created_between]
279
- CreadtedAtSearch.new(scope).search(created_between: search_attributes[:created_between])
280
- end
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
- - Loop through the search results on your view.
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:
@@ -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