lupa 1.0.0 → 1.0.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/.travis.yml +6 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +2 -0
- data/README.md +114 -54
- data/Rakefile +1 -1
- data/lib/lupa.rb +1 -0
- data/lib/lupa/search.rb +10 -1
- data/lib/lupa/version.rb +1 -1
- data/test/default_search_attributes_search_test.rb +22 -0
- data/test/test_helper.rb +3 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 46daf9ac53354ecc21016d968c78de4d322a1bb5
|
4
|
+
data.tar.gz: 62c8cde8889723041b1d622ed2bc1bec5f784ff6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 010ba896da1a30da488d2726646616ca4e1c7091dd5907ae20150756af248b8173266e4b68f501470c35cbf12d933f0f6f530b8943920b2b0f874bff9a782599
|
7
|
+
data.tar.gz: 36a3ac1ee305f13318d990f250a40b97ab5f6cb90661c1e546ca4f0b0218e746a05d321d7069944ba100c484e8343125ccb2a8123cc2b15b46e9539e918168e1
|
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -2,53 +2,65 @@
|
|
2
2
|
|
3
3
|
Lupa means *Magnifier* in spanish.
|
4
4
|
|
5
|
-
|
5
|
+
[](https://travis-ci.org/edelpero/lupa) [](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
|
-
|
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: `
|
10
|
+
Product.where(name: 'Digital').where(category: '23').limit(2)`.
|
10
11
|
|
11
|
-
|
12
|
+
**Table of Contents:**
|
12
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
27
|
|
15
|
-
```haml
|
16
|
-
# app/views/products/_search.html.haml
|
17
28
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
= date_field_tag 'created_between[start_date]'
|
22
|
-
= date_field_tag 'created_between[end_date]'
|
23
|
-
= submit_tag :search
|
24
|
-
```
|
25
|
-
- Create a new instance of your search class and pass a collection to which all search conditions will be applied and specify the search params you want to apply:
|
29
|
+
## Search Class
|
30
|
+
|
31
|
+
### Overview
|
26
32
|
|
27
33
|
```ruby
|
28
|
-
|
34
|
+
products = ProductSearch.new(current_user.products).search(name: 'digital', category: '23')
|
29
35
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
34
|
-
|
35
|
-
protected
|
36
|
-
def search_params
|
37
|
-
params.permit(:name, :category, created_between: [:start_date, :end_date])
|
38
|
-
end
|
36
|
+
# Iterate over the search results
|
37
|
+
products.each do |product|
|
38
|
+
# Your logic goes here
|
39
39
|
end
|
40
40
|
```
|
41
|
-
|
41
|
+
Calling **.each** on the instance will build a search by chaining calls to **name** and **category** methods defined in our **ProductSearch::Scope** class.
|
42
42
|
|
43
|
-
```
|
44
|
-
# app/
|
43
|
+
```ruby
|
44
|
+
# app/searches/product_search.rb
|
45
45
|
|
46
|
-
|
46
|
+
class ProductSearch < Lupa::Search
|
47
|
+
# Scope class holds all your search methods.
|
48
|
+
class Scope
|
47
49
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
50
|
+
# Search method
|
51
|
+
def name
|
52
|
+
if search_attributes[:name].present?
|
53
|
+
scope.where('name iLIKE ?', "%#{search_attributes[:name]}%")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Search method
|
58
|
+
def category
|
59
|
+
scope.where(category_id: search_attributes[:category])
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
52
64
|
```
|
53
65
|
|
54
66
|
### Definition
|
@@ -65,7 +77,7 @@ end
|
|
65
77
|
Inside your **Scope** class you must define your scope methods. You'll also be able to access to the following methods inside your scope class: **scope** and **search_attributes**.
|
66
78
|
|
67
79
|
* **`scope:`** returns the current scope when the scope method is called.
|
68
|
-
* **`search_attributes:`** returns a hash containing the all search attributes specified.
|
80
|
+
* **`search_attributes:`** returns a hash containing the all search attributes specified including the default ones.
|
69
81
|
|
70
82
|
<u>**Note:**</u> All keys of **`search_attributes`** are symbolized.
|
71
83
|
|
@@ -75,32 +87,32 @@ Inside your **Scope** class you must define your scope methods. You'll also be a
|
|
75
87
|
class ProductSearch < Lupa::Search
|
76
88
|
# Scope class holds all your search methods.
|
77
89
|
class Scope
|
78
|
-
|
90
|
+
|
79
91
|
# Search method
|
80
92
|
def name
|
81
93
|
if search_attributes[:name].present?
|
82
94
|
scope.where('name LIKE ?', "%#{search_attributes[:name]}%")
|
83
95
|
end
|
84
96
|
end
|
85
|
-
|
97
|
+
|
86
98
|
# Search method
|
87
99
|
def created_between
|
88
100
|
if created_start_date && created_end_date
|
89
101
|
scope.where(created_at: created_start_date..created_end_date)
|
90
102
|
end
|
91
103
|
end
|
92
|
-
|
104
|
+
|
93
105
|
# Search method
|
94
106
|
def category
|
95
|
-
scope.where(category_id: search_attributes[:category])
|
107
|
+
scope.where(category_id: search_attributes[:category])
|
96
108
|
end
|
97
|
-
|
109
|
+
|
98
110
|
private
|
99
111
|
# Parses search_attributes[:created_between][:start_date]
|
100
112
|
def created_start_date
|
101
113
|
search_attributes[:created_between] && search_attributes[:created_between][:start_date].try(:to_date)
|
102
114
|
end
|
103
|
-
|
115
|
+
|
104
116
|
# Parses search_attributes[:created_between][:end_date]
|
105
117
|
def created_end_date
|
106
118
|
search_attributes[:created_between] && search_attributes[:created_between][:end_date].try(:to_date)
|
@@ -154,7 +166,7 @@ search.unexisting_method
|
|
154
166
|
# => Lupa::ResultMethodNotImplementedError: The resulting scope does not respond to unexisting_method method.
|
155
167
|
```
|
156
168
|
|
157
|
-
|
169
|
+
### Default Search Scope
|
158
170
|
|
159
171
|
You can define a default search scope if you want to use a search class with an specific resource by overriding the initialize method as follows:
|
160
172
|
|
@@ -165,7 +177,7 @@ class ProductSearch < Lupa::Search
|
|
165
177
|
class Scope
|
166
178
|
...
|
167
179
|
end
|
168
|
-
|
180
|
+
|
169
181
|
# Be careful not to change the scope variable name,
|
170
182
|
# otherwise you will experiment some issues.
|
171
183
|
def initialize(scope = Product.all)
|
@@ -183,7 +195,7 @@ search.first
|
|
183
195
|
# => #<Product id: 1, name: 'Eames Chair', category_id: 23, created_at: "2015-04-06 18:54:13", updated_at: "2015-04-06 18:54:13" >
|
184
196
|
```
|
185
197
|
|
186
|
-
|
198
|
+
### Default Search Attributes
|
187
199
|
|
188
200
|
Defining default search attributes will cause the scope method to be invoked always.
|
189
201
|
|
@@ -194,7 +206,7 @@ class ProductSearch < Lupa::Search
|
|
194
206
|
class Scope
|
195
207
|
...
|
196
208
|
end
|
197
|
-
|
209
|
+
|
198
210
|
# This should always return a hash
|
199
211
|
def default_search_attributes
|
200
212
|
{ category: 23 }
|
@@ -210,7 +222,7 @@ search.search_attributes
|
|
210
222
|
# => { name: 'chair', category: 42 }
|
211
223
|
```
|
212
224
|
|
213
|
-
|
225
|
+
### Combining Search Classes
|
214
226
|
|
215
227
|
You can reuse your search class in order to keep them DRY.
|
216
228
|
|
@@ -221,27 +233,27 @@ A common example is searching records created between two dates. So lets create
|
|
221
233
|
|
222
234
|
class CreatedAtSearch < Lupa::Search
|
223
235
|
class Scope
|
224
|
-
|
236
|
+
|
225
237
|
def created_before
|
226
238
|
...
|
227
239
|
end
|
228
|
-
|
240
|
+
|
229
241
|
def created_after
|
230
242
|
...
|
231
243
|
end
|
232
|
-
|
244
|
+
|
233
245
|
def created_between
|
234
246
|
if created_start_date && created_end_date
|
235
247
|
scope.where(created_at: created_start_date..created_end_date)
|
236
248
|
end
|
237
249
|
end
|
238
|
-
|
250
|
+
|
239
251
|
private
|
240
252
|
|
241
253
|
def created_start_date
|
242
254
|
search_attributes[:created_between] && search_attributes[:created_between][:start_date].try(:to_date)
|
243
255
|
end
|
244
|
-
|
256
|
+
|
245
257
|
def created_end_date
|
246
258
|
search_attributes[:created_between] && search_attributes[:created_between][:end_date].try(:to_date)
|
247
259
|
end
|
@@ -256,26 +268,74 @@ Now we can use it in our **ProductSearch** class:
|
|
256
268
|
|
257
269
|
class ProductSearch < Lupa::Search
|
258
270
|
class Scope
|
259
|
-
|
271
|
+
|
260
272
|
def name
|
261
273
|
...
|
262
274
|
end
|
263
|
-
|
275
|
+
|
264
276
|
# We use CreatedAtSearch class to perform the search
|
265
277
|
def created_between
|
266
278
|
if search_attributes[:created_between]
|
267
279
|
CreadtedAtSearch.new(scope).search(created_between: search_attributes[:created_between])
|
268
280
|
end
|
269
281
|
end
|
270
|
-
|
282
|
+
|
271
283
|
def category
|
272
284
|
...
|
273
285
|
end
|
274
|
-
|
286
|
+
|
275
287
|
end
|
276
288
|
end
|
277
289
|
```
|
278
290
|
|
291
|
+
## Usage with Rails
|
292
|
+
|
293
|
+
### Forms
|
294
|
+
|
295
|
+
Define a custom form:
|
296
|
+
|
297
|
+
```haml
|
298
|
+
# app/views/products/_search.html.haml
|
299
|
+
|
300
|
+
= form_tag products_path, method: :get do
|
301
|
+
= text_field_tag 'name'
|
302
|
+
= select_tag 'category', options_from_collection_for_select(@categories, 'id', 'name')
|
303
|
+
= date_field_tag 'created_between[start_date]'
|
304
|
+
= date_field_tag 'created_between[end_date]'
|
305
|
+
= submit_tag :search
|
306
|
+
```
|
307
|
+
|
308
|
+
### Controllers
|
309
|
+
|
310
|
+
Create a new instance of your search class and pass a collection to which all search conditions will be applied and specify the search params you want to apply:
|
311
|
+
|
312
|
+
```ruby
|
313
|
+
# app/controllers/products_controller.rb
|
314
|
+
|
315
|
+
class ProductsController < ApplicationController
|
316
|
+
def index
|
317
|
+
@products = ProductSearch.new(current_user.products).search(search_params)
|
318
|
+
end
|
319
|
+
|
320
|
+
protected
|
321
|
+
def search_params
|
322
|
+
params.permit(:name, :category, created_between: [:start_date, :end_date])
|
323
|
+
end
|
324
|
+
end
|
325
|
+
```
|
326
|
+
- Loop through the search results on your view.
|
327
|
+
|
328
|
+
```haml
|
329
|
+
# app/views/products/index.html.haml
|
330
|
+
|
331
|
+
%h1 Products
|
332
|
+
|
333
|
+
%ul
|
334
|
+
- @products.each do |product|
|
335
|
+
%li
|
336
|
+
= "#{product.name} - #{product.price} - #{product.category}"
|
337
|
+
```
|
338
|
+
|
279
339
|
## Testing
|
280
340
|
|
281
341
|
This is a list of things you should test when creating a search class:
|
data/Rakefile
CHANGED
data/lib/lupa.rb
CHANGED
@@ -2,6 +2,7 @@ require "lupa/version"
|
|
2
2
|
|
3
3
|
module Lupa
|
4
4
|
DefaultScopeError = Class.new(StandardError)
|
5
|
+
DefaultSearchAttributesError = Class.new(StandardError)
|
5
6
|
ScopeMethodNotImplementedError = Class.new(NotImplementedError)
|
6
7
|
ResultMethodNotImplementedError = Class.new(NotImplementedError)
|
7
8
|
SearchAttributesError = Class.new(StandardError)
|
data/lib/lupa/search.rb
CHANGED
@@ -314,9 +314,18 @@ module Lupa
|
|
314
314
|
#
|
315
315
|
# Sets @search_attributes by merging default search attributes with the ones passed to search method.
|
316
316
|
def set_search_attributes(attributes)
|
317
|
+
attributes = merge_search_attributes(attributes)
|
317
318
|
attributes = symbolize_keys(attributes)
|
318
319
|
attributes = remove_blank_attributes(attributes)
|
319
|
-
|
320
|
+
|
321
|
+
@search_attributes = attributes
|
322
|
+
end
|
323
|
+
|
324
|
+
# Internal: Merge search attributes with default search attributes
|
325
|
+
def merge_search_attributes(attributes)
|
326
|
+
return default_search_attributes.merge(attributes) if default_search_attributes.kind_of?(Hash)
|
327
|
+
|
328
|
+
raise Lupa::DefaultSearchAttributesError, "default_search_attributes doesn't return a Hash."
|
320
329
|
end
|
321
330
|
|
322
331
|
# Internal: Symbolizes all keys passed to the search attributes.
|
data/lib/lupa/version.rb
CHANGED
@@ -32,6 +32,22 @@ class ClassWithoutDefaultSearchAttributesSearch < Lupa::Search
|
|
32
32
|
|
33
33
|
end
|
34
34
|
|
35
|
+
class ClassWithInvalidDefaultSearchAttributesSearch < Lupa::Search
|
36
|
+
|
37
|
+
class Scope
|
38
|
+
def reverse; end
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(scope = [1, 2, 3, 4])
|
42
|
+
@scope = scope
|
43
|
+
end
|
44
|
+
|
45
|
+
def default_search_attributes
|
46
|
+
1
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
35
51
|
|
36
52
|
describe Lupa::Search do
|
37
53
|
before do
|
@@ -61,6 +77,12 @@ describe Lupa::Search do
|
|
61
77
|
search.default_search_attributes.must_equal params
|
62
78
|
end
|
63
79
|
end
|
80
|
+
|
81
|
+
context 'when default_search_attributes does not return a Hash' do
|
82
|
+
it 'raises a Lupa::DefaultSearchAttributesError exception' do
|
83
|
+
proc { ClassWithInvalidDefaultSearchAttributesSearch.search({}).results }.must_raise Lupa::DefaultSearchAttributesError
|
84
|
+
end
|
85
|
+
end
|
64
86
|
end
|
65
87
|
|
66
88
|
describe '#results' do
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lupa
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ezequiel Delpero
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-04-
|
11
|
+
date: 2015-04-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -61,6 +61,8 @@ extensions: []
|
|
61
61
|
extra_rdoc_files: []
|
62
62
|
files:
|
63
63
|
- .gitignore
|
64
|
+
- .travis.yml
|
65
|
+
- CHANGELOG.md
|
64
66
|
- Gemfile
|
65
67
|
- LICENSE.txt
|
66
68
|
- README.md
|