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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +4 -1
  4. data/.travis.yml +6 -0
  5. data/Gemfile +4 -1
  6. data/README.md +44 -30
  7. data/Rakefile +8 -1
  8. data/lib/mundane-search.rb +2 -1
  9. data/lib/mundane-search/filter_canister.rb +4 -11
  10. data/lib/mundane-search/filters/attribute_match.rb +2 -2
  11. data/lib/mundane-search/filters/attribute_substring.rb +2 -3
  12. data/lib/mundane-search/filters/multi_order.rb +78 -0
  13. data/lib/mundane-search/filters/operator.rb +2 -2
  14. data/lib/mundane-search/filters/order.rb +9 -3
  15. data/lib/mundane-search/filters/typical.rb +13 -9
  16. data/lib/mundane-search/param_key_types.rb +43 -0
  17. data/lib/mundane-search/result.rb +0 -2
  18. data/lib/mundane-search/result_model.rb +6 -5
  19. data/lib/mundane-search/version.rb +1 -1
  20. data/lib/mundane-search/view_helpers.rb +16 -2
  21. data/mundane-search.gemspec +2 -1
  22. data/spec/active_record_setup.rb +2 -1
  23. data/spec/buildable_integration_spec.rb +1 -1
  24. data/spec/builder_integration_spec.rb +4 -4
  25. data/spec/filter_canister_spec.rb +9 -8
  26. data/spec/filters/attribute_match_integration_spec.rb +2 -2
  27. data/spec/filters/attribute_match_spec.rb +2 -2
  28. data/spec/filters/attribute_substring_spec.rb +2 -2
  29. data/spec/filters/exact_match_spec.rb +2 -2
  30. data/spec/filters/multi_order_integration_spec.rb +42 -0
  31. data/spec/filters/multi_order_spec.rb +46 -0
  32. data/spec/filters/operator_integration_spec.rb +2 -2
  33. data/spec/filters/operator_spec.rb +2 -2
  34. data/spec/filters/order_integration_spec.rb +14 -2
  35. data/spec/filters/order_spec.rb +25 -4
  36. data/spec/filters/shortcuts_integration_spec.rb +1 -1
  37. data/spec/filters/typical_spec.rb +6 -7
  38. data/spec/filters_spec.rb +1 -1
  39. data/spec/form_integration_spec.rb +1 -1
  40. data/spec/minitest_helper.rb +17 -2
  41. data/spec/param_key_types_spec.rb +29 -0
  42. data/spec/result_integration_spec.rb +1 -1
  43. data/spec/result_model_spec.rb +3 -2
  44. data/spec/search_url_for_integration_spec.rb +15 -0
  45. data/spec/simple_form_integration_spec.rb +3 -5
  46. metadata +57 -83
  47. data/bin/coderay +0 -16
  48. data/bin/erubis +0 -16
  49. data/bin/guard +0 -16
  50. data/bin/pry +0 -16
  51. data/bin/rackup +0 -16
  52. data/bin/rake +0 -16
  53. data/bin/sprockets +0 -16
  54. data/bin/thor +0 -16
  55. data/bin/tilt +0 -16
  56. data/lib/columns_hash.rb +0 -38
  57. data/spec/columns_hash_spec.rb +0 -37
@@ -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
@@ -0,0 +1 @@
1
+ service_name: travis-ci
data/.gitignore CHANGED
@@ -15,4 +15,7 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
- .rvmrc
18
+ .rvmrc
19
+ .ruby-gemset
20
+ .ruby-version
21
+ bin/*
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
4
+ - "2.0.0"
5
+ before_script:
6
+ - rake database:test_setup
data/Gemfile CHANGED
@@ -3,4 +3,7 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in mundane-search.gemspec
4
4
  gemspec
5
5
 
6
- gem 'pry'
6
+ gem 'pry'
7
+
8
+ gem 'simplecov', :require => false, :group => :test
9
+ gem 'coveralls', :require => false, :group => :test
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, param_key: "title"
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
- * param_key: The key in params to examine for a matching value.
85
- * target: The attribute to match against. By default, uses param_key.
86
- * match_value: Usually nil. When nil, the value of params[param_key] is used.
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 param_key_type in a filter.
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, param_key: "publisher", required: true
103
+ use :attribute_match, key: "publisher", required: true
102
104
 
103
105
  # book.title == params["title"]
104
- use :attribute_match, param_key: "title"
106
+ use :attribute_match, key: "title"
105
107
 
106
108
  # book.author == params["writer"]
107
- use :attribute_match, param_key: "writer", target: "author"
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, param_key: "publication_date", operator: :>, match_value: Date.parse("1900-01-01")
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, param_key: "first_purchased_at", type: :time
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, param_key: "title"
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, param_key: title
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 param_key and a symbol of an operator (:>, :<, :>=, :<=)
134
+ Requires a key and a symbol of an operator (:>, :<, :>=, :<=)
133
135
 
134
- use :operator, param_key: "publication_date", 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, param_key: "sort", direction: "asc"
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
- #### ExactMatch
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, param_key: "fruit"
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
- use MundaneSearch::Filters::ExactMatch, param_key: "title"
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, param_key: "title"
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, param_key: "title", required: true
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[:param_key] => "Gumbo" })
249
+ params.merge({ options[:key] => "Gumbo" })
236
250
  end
237
251
  end
238
252
  built = MundaneSearch::Builder.new do
239
- use AlwaysSearchingForGumbo, param_key: "food"
240
- use MundaneSearch::Filters::ExactMatch, param_key: "food"
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, param_key: "food"
250
- use MundaneSearch::Filters::ExactMatch, param_key: "noms"
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, param_key: "foo"
262
- use :exact_match, param_key: "foo"
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:integration test:unit]
48
+ task 'test' => %w[test:all]
42
49
  task 'default' => 'test'
@@ -1,4 +1,4 @@
1
- require "columns_hash"
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 param_key
19
- single_options[:param_key]
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(param_key => params[param_key.to_s])
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(param_key) == params[param_key.to_s] }
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
- # collection.where(param_key => params[param_key.to_s])
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(params[param_key.to_s]) }
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} ?", params[param_key.to_s]])
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, params[param_key.to_s]) }
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? ? collection.reverse : collection
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
- params["direction"] || options[:direction]
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.param_key_type
4
- :string # common default
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] || param_key
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 param_key
20
- options.fetch(:param_key)
24
+ def key
25
+ options.fetch(:key)
21
26
  end
22
27
 
23
28
  def match_value
24
- options[:match_value] || params[param_key]
29
+ options[:match_value] || params[key]
25
30
  end
26
31
 
27
- # This is a duplicate of the class method?
28
- def param_key_type
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