mundane-search 0.0.3 → 0.0.5

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.
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