mundane-search 0.0.3 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +4 -1
- data/.travis.yml +6 -0
- data/Gemfile +4 -1
- data/README.md +44 -30
- data/Rakefile +8 -1
- data/lib/mundane-search.rb +2 -1
- data/lib/mundane-search/filter_canister.rb +4 -11
- data/lib/mundane-search/filters/attribute_match.rb +2 -2
- data/lib/mundane-search/filters/attribute_substring.rb +2 -3
- data/lib/mundane-search/filters/multi_order.rb +78 -0
- data/lib/mundane-search/filters/operator.rb +2 -2
- data/lib/mundane-search/filters/order.rb +9 -3
- data/lib/mundane-search/filters/typical.rb +13 -9
- data/lib/mundane-search/param_key_types.rb +43 -0
- data/lib/mundane-search/result.rb +0 -2
- data/lib/mundane-search/result_model.rb +6 -5
- data/lib/mundane-search/version.rb +1 -1
- data/lib/mundane-search/view_helpers.rb +16 -2
- data/mundane-search.gemspec +2 -1
- data/spec/active_record_setup.rb +2 -1
- data/spec/buildable_integration_spec.rb +1 -1
- data/spec/builder_integration_spec.rb +4 -4
- data/spec/filter_canister_spec.rb +9 -8
- data/spec/filters/attribute_match_integration_spec.rb +2 -2
- data/spec/filters/attribute_match_spec.rb +2 -2
- data/spec/filters/attribute_substring_spec.rb +2 -2
- data/spec/filters/exact_match_spec.rb +2 -2
- data/spec/filters/multi_order_integration_spec.rb +42 -0
- data/spec/filters/multi_order_spec.rb +46 -0
- data/spec/filters/operator_integration_spec.rb +2 -2
- data/spec/filters/operator_spec.rb +2 -2
- data/spec/filters/order_integration_spec.rb +14 -2
- data/spec/filters/order_spec.rb +25 -4
- data/spec/filters/shortcuts_integration_spec.rb +1 -1
- data/spec/filters/typical_spec.rb +6 -7
- data/spec/filters_spec.rb +1 -1
- data/spec/form_integration_spec.rb +1 -1
- data/spec/minitest_helper.rb +17 -2
- data/spec/param_key_types_spec.rb +29 -0
- data/spec/result_integration_spec.rb +1 -1
- data/spec/result_model_spec.rb +3 -2
- data/spec/search_url_for_integration_spec.rb +15 -0
- data/spec/simple_form_integration_spec.rb +3 -5
- metadata +57 -83
- data/bin/coderay +0 -16
- data/bin/erubis +0 -16
- data/bin/guard +0 -16
- data/bin/pry +0 -16
- data/bin/rackup +0 -16
- data/bin/rake +0 -16
- data/bin/sprockets +0 -16
- data/bin/thor +0 -16
- data/bin/tilt +0 -16
- data/lib/columns_hash.rb +0 -38
- data/spec/columns_hash_spec.rb +0 -37
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 213f6117e16f7a6fec072526aef10e279d24ae2a
|
4
|
+
data.tar.gz: b1ec7d3a1abba8e2448cc1527c5d5cc627a62a72
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f7ce9b6b8f7453100015b010cb5d21a78d1d2b00aa658d5d0e58224b905250a05a85776438e4f9158d1b673c203b4854fc41217e76b69c2042761a6fa4bf02a7
|
7
|
+
data.tar.gz: 41958041eba6222f5a5c318a13028b9eb3c88fca5cb4fd8d76ea7e3e4d7010993fe8d79472451d5a0d83c3681cc2e900ed49d329891ff15f872f4cb031ef7cb4
|
data/.coveralls.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
service_name: travis-ci
|
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# MundaneSearch
|
2
2
|
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/mundane-search.png)](http://badge.fury.io/rb/mundane-search) [![Code Climate](https://codeclimate.com/github/samsm/mundane-search.png)](https://codeclimate.com/github/samsm/mundane-search) [![Coverage Status](https://coveralls.io/repos/samsm/mundane-search/badge.png?branch=master)](https://coveralls.io/r/samsm/mundane-search?branch=master) [![Dependency Status](https://gemnasium.com/samsm/mundane-search.png)](https://gemnasium.com/samsm/mundane-search)
|
4
|
+
|
3
5
|
MundaneSearch aims to compartmentalize multi-step search.
|
4
6
|
|
5
7
|
## Installation
|
@@ -28,7 +30,7 @@ Create a search:
|
|
28
30
|
Add filters to it:
|
29
31
|
|
30
32
|
class BookSearch < MundaneSearch::Result
|
31
|
-
use :attribute_match,
|
33
|
+
use :attribute_match, key: "title"
|
32
34
|
end
|
33
35
|
|
34
36
|
Then use that search in your controllers:
|
@@ -81,11 +83,11 @@ Three ways to notate filters:
|
|
81
83
|
|
82
84
|
First some options that are common to many filters.
|
83
85
|
|
84
|
-
*
|
85
|
-
* target: The attribute to match against. By default, uses
|
86
|
-
* match_value: Usually nil. When nil, the value of params[
|
86
|
+
* key: The key in params to examine for a matching value.
|
87
|
+
* target: The attribute to match against. By default, uses key.
|
88
|
+
* match_value: Usually nil. When nil, the value of params[key] is used.
|
87
89
|
* required: Default false. When true, will run a filter even if (for example) the match_value is nil.
|
88
|
-
* type: Gives form helpers et al a hint as to what type the match_value should be. Overrides class method
|
90
|
+
* type: Gives form helpers et al a hint as to what type the match_value should be. Overrides class method key_type in a filter.
|
89
91
|
Available types:
|
90
92
|
1. :string
|
91
93
|
2. :integer
|
@@ -98,49 +100,61 @@ All those suckers in action:
|
|
98
100
|
class BookSearch < MundaneSearch::Result
|
99
101
|
# book.publisher == params["publisher"] even if the match_value (params["publisher"]) is nil
|
100
102
|
# (in below examples, the filter is skipped if the match_value is nil)
|
101
|
-
use :attribute_match,
|
103
|
+
use :attribute_match, key: "publisher", required: true
|
102
104
|
|
103
105
|
# book.title == params["title"]
|
104
|
-
use :attribute_match,
|
106
|
+
use :attribute_match, key: "title"
|
105
107
|
|
106
108
|
# book.author == params["writer"]
|
107
|
-
use :attribute_match,
|
109
|
+
use :attribute_match, key: "writer", target: "author"
|
108
110
|
|
109
111
|
# book.publication_date > Date.parse("1900-01-01") (disregards params)
|
110
|
-
use :operator,
|
112
|
+
use :operator, key: "publication_date", operator: :>, match_value: Date.parse("1900-01-01")
|
111
113
|
|
112
114
|
# simple_form displays filter as designated type
|
113
|
-
use :attribute_match,
|
115
|
+
use :attribute_match, key: "first_purchased_at", type: :time
|
114
116
|
end
|
115
117
|
|
116
118
|
### AttributeMatch
|
117
119
|
|
118
120
|
Returns objects that exactly match an attribute, ex: book.title == "A Tale of Two Cities"
|
119
121
|
|
120
|
-
use :attribute_match,
|
122
|
+
use :attribute_match, key: "title"
|
121
123
|
|
122
124
|
### AttributeSubstring
|
123
125
|
|
124
126
|
Returns objects that match a portion of an attribute, ex: book.title =~ /Tale of/
|
125
127
|
|
126
|
-
use :attribute_substring,
|
128
|
+
use :attribute_substring, key: title
|
127
129
|
|
128
130
|
### Operator
|
129
131
|
|
130
132
|
Returns objects that match an attribute + operator, ex: book.publication_date > Date.parse("1900-01-01")
|
131
133
|
|
132
|
-
Requires a
|
134
|
+
Requires a key and a symbol of an operator (:>, :<, :>=, :<=)
|
133
135
|
|
134
|
-
use :operator,
|
136
|
+
use :operator, key: "publication_date", operator: :>
|
135
137
|
|
136
138
|
### Order
|
137
139
|
|
138
140
|
Sorts a collection.
|
139
141
|
|
140
|
-
use :order,
|
142
|
+
use :order, key: "sort", direction_key: "bearing"
|
143
|
+
# { "sort" => "publication_date", "bearing" => "descending" }
|
144
|
+
|
145
|
+
### MultiOrder
|
146
|
+
|
147
|
+
Sorts a collection based on several fields.
|
141
148
|
|
149
|
+
# Compact syntax:
|
150
|
+
use :multi_order, key: "sort"
|
151
|
+
# { "sort" => "sold;author:desc" }
|
142
152
|
|
143
|
-
|
153
|
+
# Array syntax (better for forms, maybe?)
|
154
|
+
use :multi_order, key: "sort", direction_key: "bearing"
|
155
|
+
# {"sort" => ["sold","author"], "bearing" => ["asc", "desc"]},
|
156
|
+
|
157
|
+
### ExactMatch
|
144
158
|
|
145
159
|
MundaneSearch can also work with objects that aren't "attribute-y".
|
146
160
|
|
@@ -157,7 +171,7 @@ Changes values of "", [], or {} to nil in params.
|
|
157
171
|
MundaneSearch can be used outside of Rails on whatever sort of object you want:
|
158
172
|
|
159
173
|
built = MundaneSearch::Builder.new do
|
160
|
-
use MundaneSearch::Filters::ExactMatch,
|
174
|
+
use MundaneSearch::Filters::ExactMatch, key: "fruit"
|
161
175
|
end
|
162
176
|
built.call %w(apple orange blueberry), { 'fruit' => 'orange' } # ["orange"]
|
163
177
|
|
@@ -209,22 +223,22 @@ Filters will often be configured to consider input on a specific search. So, you
|
|
209
223
|
This is another filter that would be more useful if instead of being hard-wired to look at params[:name], it could be configured when it is used.
|
210
224
|
A supplied filter: ExactMatch, does just this.
|
211
225
|
|
212
|
-
built = MundaneSearch::Builder.new do
|
213
|
-
|
214
|
-
end
|
215
|
-
built.call %w(Private Sergeant Lieutenant), { "title" => "Sergeant" } # ["Sergeant"]
|
226
|
+
built = MundaneSearch::Builder.new do
|
227
|
+
use MundaneSearch::Filters::ExactMatch, key: "title"
|
228
|
+
end
|
229
|
+
built.call %w(Private Sergeant Lieutenant), { "title" => "Sergeant" } # ["Sergeant"]
|
216
230
|
|
217
231
|
It also ignores empty params:
|
218
232
|
|
219
233
|
built = MundaneSearch::Builder.new do
|
220
|
-
use MundaneSearch::Filters::ExactMatch,
|
234
|
+
use MundaneSearch::Filters::ExactMatch, key: "title"
|
221
235
|
end
|
222
236
|
built.call %w(Private Sergeant Lieutenant), { "title" => nil } # ["Private", "Sergeant", "Lieutenant"]
|
223
237
|
|
224
238
|
Unless you tell it not to (in the following case, the filter will look for an exact match on nil, and not find it):
|
225
239
|
|
226
240
|
built = MundaneSearch::Builder.new do
|
227
|
-
use MundaneSearch::Filters::ExactMatch,
|
241
|
+
use MundaneSearch::Filters::ExactMatch, key: "title", required: true
|
228
242
|
end
|
229
243
|
built.call %w(Private Sergeant Lieutenant), { "title" => nil } # []
|
230
244
|
|
@@ -232,12 +246,12 @@ You can alter params as well, in a similar fashion.
|
|
232
246
|
|
233
247
|
class AlwaysSearchingForGumbo < MundaneSearch::Filters::Base
|
234
248
|
def filtered_params
|
235
|
-
params.merge({ options[:
|
249
|
+
params.merge({ options[:key] => "Gumbo" })
|
236
250
|
end
|
237
251
|
end
|
238
252
|
built = MundaneSearch::Builder.new do
|
239
|
-
use AlwaysSearchingForGumbo,
|
240
|
-
use MundaneSearch::Filters::ExactMatch,
|
253
|
+
use AlwaysSearchingForGumbo, key: "food"
|
254
|
+
use MundaneSearch::Filters::ExactMatch, key: "food"
|
241
255
|
end
|
242
256
|
built.call %w(Pizza Pasta Antipasto Gumbo), { "food" => "Pizza" } # ["Gumbo"]
|
243
257
|
built.call %w(Pizza Pasta Antipasto Gumbo) # ["Gumbo"]
|
@@ -246,8 +260,8 @@ So yeah, it's fun. Here's a more practical example ... if you have clients that
|
|
246
260
|
|
247
261
|
built = MundaneSearch::Builder.new do
|
248
262
|
use MundaneSearch::Filters::BlankParamsAreNil
|
249
|
-
use MundaneSearch::Filters::ExactMatch,
|
250
|
-
use MundaneSearch::Filters::ExactMatch,
|
263
|
+
use MundaneSearch::Filters::ExactMatch, key: "food"
|
264
|
+
use MundaneSearch::Filters::ExactMatch, key: "noms"
|
251
265
|
end
|
252
266
|
built.call %w(Pizza Pasta Antipasto Gumbo), { "food" => "", "noms" => "Gumbo" } # ["Gumbo"]
|
253
267
|
|
@@ -258,8 +272,8 @@ If a filter is defined directly under MundaneSearch::Filters or Object (such as
|
|
258
272
|
The following two "use" designations would use the same filter.
|
259
273
|
|
260
274
|
MundaneSearch::Builder.new do
|
261
|
-
use MundaneSearch::Filters::ExactMatch,
|
262
|
-
use :exact_match,
|
275
|
+
use MundaneSearch::Filters::ExactMatch, key: "foo"
|
276
|
+
use :exact_match, key: "foo"
|
263
277
|
end
|
264
278
|
|
265
279
|
Object is searched first, so a user defined ExactMatch would take precedence over the MundaneSearch::Filters one.
|
data/Rakefile
CHANGED
@@ -20,6 +20,13 @@ namespace 'test' do
|
|
20
20
|
t.test_files = integration_test_files
|
21
21
|
t.verbose = false
|
22
22
|
end
|
23
|
+
|
24
|
+
desc "Run all tests"
|
25
|
+
Rake::TestTask.new('all') do |t|
|
26
|
+
t.libs.push "lib"
|
27
|
+
t.test_files = test_files
|
28
|
+
t.verbose = false
|
29
|
+
end
|
23
30
|
end
|
24
31
|
|
25
32
|
namespace 'database' do
|
@@ -38,5 +45,5 @@ end
|
|
38
45
|
|
39
46
|
#Rake::Task['test'].clear
|
40
47
|
desc "Run all tests"
|
41
|
-
task 'test' => %w[test:
|
48
|
+
task 'test' => %w[test:all]
|
42
49
|
task 'default' => 'test'
|
data/lib/mundane-search.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require "
|
1
|
+
require "attribute_column"
|
2
2
|
|
3
3
|
require 'mundane-search/railtie' if defined?(Rails)
|
4
4
|
|
@@ -14,4 +14,5 @@ module MundaneSearch
|
|
14
14
|
autoload :Buildable, "mundane-search/buildable"
|
15
15
|
autoload :Railtie, "mundane-search/railtie"
|
16
16
|
autoload :ViewHelpers, "mundane-search/view_helpers"
|
17
|
+
autoload :ParamKeyTypes, "mundane-search/param_key_types"
|
17
18
|
end
|
@@ -15,22 +15,15 @@ module MundaneSearch
|
|
15
15
|
varient ? filter.const_get(varient) : filter
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
19
|
-
single_options
|
20
|
-
end
|
21
|
-
|
22
|
-
def param_key_type
|
23
|
-
single_options[:param_key_type] || param_key_type_from_filter
|
18
|
+
def option_keys_with_types
|
19
|
+
ParamKeyTypes.new(single_options, filter).pairs
|
24
20
|
end
|
25
21
|
|
26
22
|
private
|
27
23
|
def single_options
|
28
24
|
options.first || {}
|
29
25
|
end
|
30
|
-
|
31
|
-
def param_key_type_from_filter
|
32
|
-
filter.param_key_type if filter.respond_to?(:param_key_type)
|
33
|
-
end
|
34
|
-
|
35
26
|
end
|
36
27
|
end
|
28
|
+
|
29
|
+
|
@@ -2,12 +2,12 @@ module MundaneSearch::Filters
|
|
2
2
|
class AttributeMatch < Typical
|
3
3
|
class ActiveRecord < self
|
4
4
|
def filtered_collection
|
5
|
-
collection.where(
|
5
|
+
collection.where(target => match_value)
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
9
9
|
def filtered_collection
|
10
|
-
collection.select {|e| e.send(
|
10
|
+
collection.select {|e| e.send(target) == match_value }
|
11
11
|
end
|
12
12
|
end
|
13
13
|
end
|
@@ -2,13 +2,12 @@ module MundaneSearch::Filters
|
|
2
2
|
class AttributeSubstring < Typical
|
3
3
|
class ActiveRecord < self
|
4
4
|
def filtered_collection
|
5
|
-
|
6
|
-
collection.where(["#{target} LIKE ?", "%#{params[param_key.to_s]}%"])
|
5
|
+
collection.where(["#{target} LIKE ?", "%#{match_value}%"])
|
7
6
|
end
|
8
7
|
end
|
9
8
|
|
10
9
|
def filtered_collection
|
11
|
-
collection.select {|e| e.send(target).index(
|
10
|
+
collection.select {|e| e.send(target).index(match_value) }
|
12
11
|
end
|
13
12
|
end
|
14
13
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module MundaneSearch::Filters
|
2
|
+
class MultiOrder < Typical
|
3
|
+
|
4
|
+
class ActiveRecord < self
|
5
|
+
def filtered_collection
|
6
|
+
collection.order(order_string)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
def order_string
|
11
|
+
# puts
|
12
|
+
# puts "*** This requires escaping! ***"
|
13
|
+
|
14
|
+
statement = ordering_pairs.to_a.collect {|p| p.join(" ") }.join(", ")
|
15
|
+
escape_for_order_sql statement
|
16
|
+
end
|
17
|
+
|
18
|
+
def escape_for_order_sql(unescaped)
|
19
|
+
# This is crude, but agressive. Should workd for order statements.
|
20
|
+
# Escapes everything but [a-zA-Z0-9_,.] and whitespace
|
21
|
+
unescaped[/[.,\w\s]+/]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def filtered_collection
|
26
|
+
collection.reverse.sort do |a,b|
|
27
|
+
compare = ordering_pairs.inject([[],[]]) do |sum, pair|
|
28
|
+
attribute, direction = pair
|
29
|
+
attribute = :"#{attribute}"
|
30
|
+
if ascending_terms.include?(direction)
|
31
|
+
sum.first << a.send(attribute)
|
32
|
+
sum.last << b.send(attribute)
|
33
|
+
else
|
34
|
+
sum.first << b.send(attribute)
|
35
|
+
sum.last << a.send(attribute)
|
36
|
+
end
|
37
|
+
sum
|
38
|
+
end
|
39
|
+
compare.first <=> compare.last
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def ordering_pairs
|
46
|
+
if match_value.kind_of?(String)
|
47
|
+
match_value.split(";").inject({}) do |hsh, pair|
|
48
|
+
key, val = pair.split(":")
|
49
|
+
hsh[key] = val || "asc"
|
50
|
+
hsh
|
51
|
+
end
|
52
|
+
else
|
53
|
+
Hash[keys_to_sort_on.zip(corresponding_directions_to_sort)]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def directions_key
|
58
|
+
options[:directions_key] || "directions"
|
59
|
+
end
|
60
|
+
def keys_to_sort_on
|
61
|
+
match_value
|
62
|
+
end
|
63
|
+
|
64
|
+
def corresponding_directions_to_sort
|
65
|
+
params[directions_key]
|
66
|
+
end
|
67
|
+
|
68
|
+
# These are duplicated in MultiOrder
|
69
|
+
def ascending_terms
|
70
|
+
%w(asc ascending <)
|
71
|
+
end
|
72
|
+
|
73
|
+
def descending_terms
|
74
|
+
%w(desc descending >)
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
@@ -2,12 +2,12 @@ module MundaneSearch::Filters
|
|
2
2
|
class Operator < Typical
|
3
3
|
class ActiveRecord < self
|
4
4
|
def filtered_collection
|
5
|
-
collection.where(["#{target} #{operator} ?",
|
5
|
+
collection.where(["#{target} #{operator} ?", match_value])
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
9
9
|
def filtered_collection
|
10
|
-
collection.select {|e| e.send(target).send(operator,
|
10
|
+
collection.select {|e| e.send(target).send(operator, match_value) }
|
11
11
|
end
|
12
12
|
|
13
13
|
def operator
|
@@ -5,14 +5,15 @@ module MundaneSearch::Filters
|
|
5
5
|
collection.order("#{match_value} #{active_record_direction}")
|
6
6
|
end
|
7
7
|
|
8
|
+
private
|
8
9
|
def active_record_direction
|
9
10
|
direction || "ASC"
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
13
14
|
def filtered_collection
|
14
|
-
collection.sort_by(&:"#{match_value}")
|
15
|
-
backwards? ?
|
15
|
+
sorted = collection.sort_by(&:"#{match_value}")
|
16
|
+
backwards? ? sorted.reverse : sorted
|
16
17
|
end
|
17
18
|
|
18
19
|
protected
|
@@ -21,9 +22,14 @@ module MundaneSearch::Filters
|
|
21
22
|
end
|
22
23
|
|
23
24
|
def direction
|
24
|
-
|
25
|
+
options[:direction] || params[direction_key]
|
25
26
|
end
|
26
27
|
|
28
|
+
def direction_key
|
29
|
+
options[:direction_key] || "direction"
|
30
|
+
end
|
31
|
+
|
32
|
+
# These are duplicated in MultiOrder
|
27
33
|
def ascending_terms
|
28
34
|
%w(asc ascending <)
|
29
35
|
end
|
@@ -1,11 +1,16 @@
|
|
1
1
|
module MundaneSearch::Filters
|
2
2
|
class Typical < Base
|
3
|
-
def self.
|
4
|
-
:
|
3
|
+
def self.key_types
|
4
|
+
{ key: key_type }
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.key_type
|
8
|
+
# common default
|
9
|
+
:string
|
5
10
|
end
|
6
11
|
|
7
12
|
def target
|
8
|
-
options[:target] ||
|
13
|
+
options[:target] || key
|
9
14
|
end
|
10
15
|
|
11
16
|
def optional?
|
@@ -16,17 +21,16 @@ module MundaneSearch::Filters
|
|
16
21
|
match_value || optional?
|
17
22
|
end
|
18
23
|
|
19
|
-
def
|
20
|
-
options.fetch(:
|
24
|
+
def key
|
25
|
+
options.fetch(:key)
|
21
26
|
end
|
22
27
|
|
23
28
|
def match_value
|
24
|
-
options[:match_value] || params[
|
29
|
+
options[:match_value] || params[key]
|
25
30
|
end
|
26
31
|
|
27
|
-
|
28
|
-
|
29
|
-
options[:type] || self.class.param_key_type
|
32
|
+
def key_type
|
33
|
+
options[:type] || self.class.key_type
|
30
34
|
end
|
31
35
|
end
|
32
36
|
end
|