mundane-search 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/Guardfile +11 -0
  2. data/README.md +61 -9
  3. data/Rakefile +1 -1
  4. data/bin/coderay +16 -0
  5. data/bin/erubis +16 -0
  6. data/bin/guard +16 -0
  7. data/bin/pry +16 -0
  8. data/bin/rackup +16 -0
  9. data/bin/rake +1 -1
  10. data/bin/sprockets +16 -0
  11. data/bin/thor +16 -0
  12. data/bin/tilt +16 -0
  13. data/lib/columns_hash.rb +38 -0
  14. data/lib/mundane-search.rb +14 -6
  15. data/lib/mundane-search/buildable.rb +25 -0
  16. data/lib/mundane-search/builder.rb +32 -18
  17. data/lib/mundane-search/filter_canister.rb +27 -3
  18. data/lib/mundane-search/filters.rb +7 -8
  19. data/lib/mundane-search/filters/attribute_match.rb +6 -7
  20. data/lib/mundane-search/filters/attribute_substring.rb +14 -0
  21. data/lib/mundane-search/filters/base.rb +5 -2
  22. data/lib/mundane-search/filters/blank_params_are_nil.rb +1 -1
  23. data/lib/mundane-search/filters/operator.rb +18 -0
  24. data/lib/mundane-search/filters/shortcuts.rb +33 -0
  25. data/lib/mundane-search/filters/typical.rb +28 -3
  26. data/lib/mundane-search/initial_stack.rb +13 -0
  27. data/lib/mundane-search/railtie.rb +7 -0
  28. data/lib/mundane-search/result.rb +23 -8
  29. data/lib/mundane-search/result_model.rb +65 -0
  30. data/lib/mundane-search/stack.rb +38 -0
  31. data/lib/mundane-search/version.rb +1 -1
  32. data/lib/mundane-search/view_helpers.rb +24 -0
  33. data/mundane-search.gemspec +7 -0
  34. data/script/console +6 -0
  35. data/spec/active_record_setup.rb +2 -45
  36. data/spec/buildable_integration_spec.rb +14 -0
  37. data/spec/buildable_spec.rb +19 -0
  38. data/spec/builder_integration_spec.rb +26 -5
  39. data/spec/builder_spec.rb +13 -18
  40. data/spec/columns_hash_spec.rb +37 -0
  41. data/spec/demo_data.rb +50 -0
  42. data/spec/filter_canister_spec.rb +46 -0
  43. data/spec/filters/attribute_match_integration_spec.rb +2 -2
  44. data/spec/filters/attribute_match_spec.rb +27 -0
  45. data/spec/filters/attribute_substring_spec.rb +27 -0
  46. data/spec/filters/base_spec.rb +39 -7
  47. data/spec/filters/blank_params_are_nil_spec.rb +11 -0
  48. data/spec/filters/operator_integration_spec.rb +20 -0
  49. data/spec/filters/operator_spec.rb +28 -0
  50. data/spec/filters/shortcuts_integration_spec.rb +16 -0
  51. data/spec/filters/shortcuts_spec.rb +15 -0
  52. data/spec/filters/typical_spec.rb +68 -0
  53. data/spec/form_integration_spec.rb +29 -0
  54. data/spec/initial_stack_spec.rb +13 -0
  55. data/spec/minitest_helper.rb +93 -4
  56. data/spec/result_integration_spec.rb +24 -0
  57. data/spec/result_model_spec.rb +50 -0
  58. data/spec/result_spec.rb +10 -28
  59. data/spec/search_form_for_integration_spec.rb +19 -0
  60. data/spec/simple_form_integration_spec.rb +36 -0
  61. data/spec/simple_search_form_for_integration_spec.rb +25 -0
  62. data/spec/stack_spec.rb +40 -0
  63. metadata +167 -6
  64. data/lib/mundane-search/filters/helpers.rb +0 -44
  65. data/lib/mundane-search/initial_result.rb +0 -7
data/Guardfile ADDED
@@ -0,0 +1,11 @@
1
+ # More info at https://github.com/guard/guard#readme
2
+ # guard 'minitest', :seed => "23242" do
3
+ guard 'minitest' do
4
+ watch(%r|^spec/(.*)_spec\.rb|)
5
+ watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb".sub("spec/mundane-search/", "spec/") }
6
+ watch(%r{^lib/(.*/)?([^/]+)\.rb$}) do |m|
7
+ "spec/#{m[1]}#{m[2]}_integration_spec.rb".sub("spec/mundane-search/", "spec/")
8
+ end
9
+ watch(%r|^spec/spec_helper\.rb|) { "spec" }
10
+ watch("spec/minitest_helper.rb") { "spec" }
11
+ end
data/README.md CHANGED
@@ -12,18 +12,53 @@ You know the deal:
12
12
 
13
13
  ## Usage
14
14
 
15
- Build a search using middleware, then run that search on a specific collection and params.
15
+ Still in the process of figuring this out! But much of it works like I want, so hopefully no brutal changes.
16
16
 
17
- Collections are typically array-like structures (arrays, ActiveRecord or Sunsport scoped collections, etc.)
17
+ ### Typical use
18
+
19
+ Build a search, then run that search on a specific collection and params.
20
+
21
+ Collections are typically array-like structures (arrays, ActiveRecord or Sunspot scoped collections, etc.)
18
22
 
19
23
  Params are hash-like structures, such as those interpreted from forms and query strings. They are optional.
20
24
 
25
+ Create a search:
26
+
27
+ class BookSearch < MundaneSearch::Result
28
+ end
29
+
30
+ Add filters to it:
31
+
32
+ class BookSearch < MundaneSearch::Result
33
+ use MundaneSearch::Filters::AttributeMatch, param_key: "title"
34
+ end
35
+
36
+ Then use that search in your controllers:
37
+
38
+ # params = { "title" => "A Tale of Two Cities" }
39
+ @result = BookSearch.results_for(Book.scoped, params)
40
+
41
+ The returned result is enumerable:
42
+
43
+ @result.each {|book| ... }
44
+ @result.first
45
+
46
+ And has some Rails form compatibility:
47
+
48
+ <%= search_form_for(@result) do |f| %>
49
+ <%= f.input :title %>
50
+ <% end %>
51
+
52
+ ## Sans sugar
53
+
54
+ MundaneSearch can be used outside of Rails on whatever sort of object you want:
55
+
21
56
  built = MundaneSearch::Builder.new do
22
57
  use MundaneSearch::Filters::ExactMatch, param_key: "fruit"
23
58
  end
24
59
  built.call %w(apple orange blueberry), { 'fruit' => 'orange' } # ["orange"]
25
60
 
26
- If you check out project, ./script/console will get you a session with everything loaded up.
61
+ If you git checkout the project, ./script/console will get you a session with everything loaded up.
27
62
 
28
63
  ## Middleware
29
64
 
@@ -113,19 +148,36 @@ So yeah, it's fun. Here's a more practical example ... if you have clients that
113
148
  end
114
149
  built.call %w(Pizza Pasta Antipasto Gumbo), { "food" => "", "noms" => "Gumbo" } # ["Gumbo"]
115
150
 
116
- ## ActiveRecord
151
+ ### A shortcut for referencing filters
152
+
153
+ If a filter is defined directly under MundaneSearch::Filters or Object (such as when you just define a class without a namespace), you can reference it with a underscored version of that filter.
154
+
155
+ The following two "use" designations would use the same filter.
156
+
157
+ MundaneSearch::Builder.new do
158
+ use MundaneSearch::Filters::ExactMatch, param_key: "foo"
159
+ use :exact_match, param_key: "foo"
160
+ end
161
+
162
+ Object is searched first, so a user defined ExactMatch would take precedence over the MundaneSearch::Filters one.
163
+
164
+ ## Supporting multiple collection types
117
165
 
118
166
  MundaneSearch can work with any collection object that can be passed around and modified. Filters can be designed to work with several types of collection.
119
167
 
168
+ When a filter is about to be built, MundaneSearch looks at the base class of the collection being searched and checks to see if there is a subclass with the same name.
169
+
170
+ In the following example, the ActiveRecord subclass will be used instead of the OnlyManagers class when the collection is an instance of ActiveRecord::Relation.
171
+
120
172
  class OnlyManagers < MundaneSearch::Filters::Base
121
- def filtered_collection
122
- case collection
123
- when ActiveRecord::Relation
173
+ class ActiveRecord < self
174
+ def filtered_collection
124
175
  collection.where(postion: "manager")
125
- else
126
- collection.select {|e| e.position == "manager" }
127
176
  end
128
177
  end
178
+ def filtered_collection
179
+ collection.select {|e| e.position == "manager" }
180
+ end
129
181
  end
130
182
 
131
183
 
data/Rakefile CHANGED
@@ -38,5 +38,5 @@ end
38
38
 
39
39
  #Rake::Task['test'].clear
40
40
  desc "Run all tests"
41
- task 'test' => %w[test:unit test:integration]
41
+ task 'test' => %w[test:integration test:unit]
42
42
  task 'default' => 'test'
data/bin/coderay ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'coderay' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('mundane-search', 'coderay')
data/bin/erubis ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'erubis' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('erubis', 'erubis')
data/bin/guard ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'guard' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('guard', 'guard')
data/bin/pry ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'pry' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('mundane-search', 'pry')
data/bin/rackup ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'rackup' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('rack', 'rackup')
data/bin/rake CHANGED
@@ -13,4 +13,4 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
13
13
  require 'rubygems'
14
14
  require 'bundler/setup'
15
15
 
16
- load Gem.bin_path('rake', 'rake')
16
+ load Gem.bin_path('mundane-search', 'rake')
data/bin/sprockets ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'sprockets' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('sprockets', 'sprockets')
data/bin/thor ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'thor' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('thor', 'thor')
data/bin/tilt ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'tilt' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('tilt', 'tilt')
@@ -0,0 +1,38 @@
1
+ require 'ostruct'
2
+ require 'active_support/concern'
3
+
4
+ module ColumnsHash
5
+ extend ActiveSupport::Concern
6
+ class Attribute < OpenStruct
7
+ def number?
8
+ number
9
+ end
10
+ end
11
+
12
+ def self.generate(options)
13
+ type = options[:type]
14
+ attribute = Attribute.new(options)
15
+ case type
16
+ when :string
17
+ attribute.limit = 255
18
+ when :integer, :float
19
+ attribute.number = true
20
+ end
21
+ attribute
22
+ end
23
+
24
+ module ClassMethods
25
+ def columns_hash
26
+ @columns_hash ||= {}
27
+ end
28
+
29
+ def attribute_column(name, attribute_type)
30
+ columns_hash[name] = ColumnsHash.generate({name: name, type: attribute_type})
31
+ end
32
+ end
33
+
34
+ def column_for_attribute(attribute)
35
+ self.class.columns_hash[attribute]
36
+ end
37
+
38
+ end
@@ -1,9 +1,17 @@
1
- require "mundane-search/version"
2
- require "mundane-search/builder"
3
- require "mundane-search/filter_canister"
4
- require "mundane-search/result"
5
- require "mundane-search/initial_result"
6
- require "mundane-search/filters"
1
+ require "columns_hash"
2
+
3
+ require 'mundane-search/railtie' if defined?(Rails)
7
4
 
8
5
  module MundaneSearch
6
+ autoload :Version, "mundane-search/version"
7
+ autoload :Builder, "mundane-search/builder"
8
+ autoload :FilterCanister, "mundane-search/filter_canister"
9
+ autoload :Stack, "mundane-search/stack"
10
+ autoload :Result, "mundane-search/result"
11
+ autoload :ResultModel, "mundane-search/result_model"
12
+ autoload :InitialStack, "mundane-search/initial_stack"
13
+ autoload :Filters, "mundane-search/filters"
14
+ autoload :Buildable, "mundane-search/buildable"
15
+ autoload :Railtie, "mundane-search/railtie"
16
+ autoload :ViewHelpers, "mundane-search/view_helpers"
9
17
  end
@@ -0,0 +1,25 @@
1
+ require 'active_support/concern'
2
+
3
+ module MundaneSearch
4
+ module Buildable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class << self
9
+ def builder
10
+ @builder ||= Builder.new
11
+ end
12
+ end
13
+ end
14
+
15
+
16
+ module ClassMethods
17
+ # Simple delegations to the builder
18
+ [:use, :result_for, :call, :employ].each do |method|
19
+ define_method method do |*args, &block|
20
+ builder.send(method, *args, &block)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,25 +1,17 @@
1
- require_relative './filters'
1
+ require 'active_support/inflector'
2
2
 
3
3
  module MundaneSearch
4
4
  class Builder
5
- include MundaneSearch::Filters
5
+ include MundaneSearch::Filters::Shortcuts
6
6
 
7
7
  def initialize(&block)
8
- @use = []
9
-
8
+ @filter_canisters = []
10
9
  instance_eval(&block) if block_given?
11
10
  end
12
11
 
13
12
  def use(filter, *args, &block)
14
- @use << filter_canister.new(filter, *args, &block)
15
- end
16
-
17
- def filter_canister
18
- FilterCanister
19
- end
20
-
21
- def filters
22
- @use
13
+ filter = convert_filter(filter)
14
+ @filter_canisters << build_filter_canister.call(filter, *args, &block)
23
15
  end
24
16
 
25
17
  def call(collection, params = {})
@@ -28,14 +20,36 @@ module MundaneSearch
28
20
  end
29
21
 
30
22
  def result_for(collection, params = {})
31
- initial_result = InitialResult.new(collection, params)
32
- filters.inject(initial_result) do |result, filter|
33
- result.add_filter(filter)
23
+ params ||= {} # tollerate nil
24
+ initial_stack = InitialStack.new(collection, params)
25
+ filter_canisters.inject(initial_stack) do |stack, canister|
26
+ stack.add(canister)
34
27
  end
35
28
  end
36
- end
37
- end
38
29
 
30
+ attr_reader :filter_canisters
39
31
 
32
+ private
40
33
 
34
+ def build_filter_canister
35
+ FilterCanister.public_method(:new)
36
+ end
41
37
 
38
+ def convert_filter(filter)
39
+ case filter
40
+ when Class
41
+ filter
42
+ when String, Symbol
43
+ camelized = "#{filter}".camelize
44
+ [Object, MundaneSearch::Filters].each do |filter_base|
45
+ if filter_base.const_defined?(camelized)
46
+ break(filter_base.const_get(camelized))
47
+ end
48
+ end
49
+ else
50
+ # Warning: May not be a valid filter
51
+ filter
52
+ end
53
+ end
54
+ end
55
+ end
@@ -5,8 +5,32 @@ module MundaneSearch
5
5
  @filter, @options = filter, args
6
6
  end
7
7
 
8
- def call(collection, params)
9
- filter.new(collection, params, *options).call
8
+ def build(collection, params)
9
+ filter_variant(collection).new(collection, params, *options)
10
10
  end
11
+
12
+ def filter_variant(collection)
13
+ base = collection.class.to_s.split('::').first.to_sym
14
+ varient = filter.constants.detect {|c| c == base }
15
+ varient ? filter.const_get(varient) : filter
16
+ end
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
24
+ end
25
+
26
+ private
27
+ def single_options
28
+ options.first || {}
29
+ end
30
+
31
+ def param_key_type_from_filter
32
+ filter.param_key_type if filter.respond_to?(:param_key_type)
33
+ end
34
+
11
35
  end
12
- end
36
+ end