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.
- data/Guardfile +11 -0
- data/README.md +61 -9
- data/Rakefile +1 -1
- data/bin/coderay +16 -0
- data/bin/erubis +16 -0
- data/bin/guard +16 -0
- data/bin/pry +16 -0
- data/bin/rackup +16 -0
- data/bin/rake +1 -1
- data/bin/sprockets +16 -0
- data/bin/thor +16 -0
- data/bin/tilt +16 -0
- data/lib/columns_hash.rb +38 -0
- data/lib/mundane-search.rb +14 -6
- data/lib/mundane-search/buildable.rb +25 -0
- data/lib/mundane-search/builder.rb +32 -18
- data/lib/mundane-search/filter_canister.rb +27 -3
- data/lib/mundane-search/filters.rb +7 -8
- data/lib/mundane-search/filters/attribute_match.rb +6 -7
- data/lib/mundane-search/filters/attribute_substring.rb +14 -0
- data/lib/mundane-search/filters/base.rb +5 -2
- data/lib/mundane-search/filters/blank_params_are_nil.rb +1 -1
- data/lib/mundane-search/filters/operator.rb +18 -0
- data/lib/mundane-search/filters/shortcuts.rb +33 -0
- data/lib/mundane-search/filters/typical.rb +28 -3
- data/lib/mundane-search/initial_stack.rb +13 -0
- data/lib/mundane-search/railtie.rb +7 -0
- data/lib/mundane-search/result.rb +23 -8
- data/lib/mundane-search/result_model.rb +65 -0
- data/lib/mundane-search/stack.rb +38 -0
- data/lib/mundane-search/version.rb +1 -1
- data/lib/mundane-search/view_helpers.rb +24 -0
- data/mundane-search.gemspec +7 -0
- data/script/console +6 -0
- data/spec/active_record_setup.rb +2 -45
- data/spec/buildable_integration_spec.rb +14 -0
- data/spec/buildable_spec.rb +19 -0
- data/spec/builder_integration_spec.rb +26 -5
- data/spec/builder_spec.rb +13 -18
- data/spec/columns_hash_spec.rb +37 -0
- data/spec/demo_data.rb +50 -0
- data/spec/filter_canister_spec.rb +46 -0
- data/spec/filters/attribute_match_integration_spec.rb +2 -2
- data/spec/filters/attribute_match_spec.rb +27 -0
- data/spec/filters/attribute_substring_spec.rb +27 -0
- data/spec/filters/base_spec.rb +39 -7
- data/spec/filters/blank_params_are_nil_spec.rb +11 -0
- data/spec/filters/operator_integration_spec.rb +20 -0
- data/spec/filters/operator_spec.rb +28 -0
- data/spec/filters/shortcuts_integration_spec.rb +16 -0
- data/spec/filters/shortcuts_spec.rb +15 -0
- data/spec/filters/typical_spec.rb +68 -0
- data/spec/form_integration_spec.rb +29 -0
- data/spec/initial_stack_spec.rb +13 -0
- data/spec/minitest_helper.rb +93 -4
- data/spec/result_integration_spec.rb +24 -0
- data/spec/result_model_spec.rb +50 -0
- data/spec/result_spec.rb +10 -28
- data/spec/search_form_for_integration_spec.rb +19 -0
- data/spec/simple_form_integration_spec.rb +36 -0
- data/spec/simple_search_form_for_integration_spec.rb +25 -0
- data/spec/stack_spec.rb +40 -0
- metadata +167 -6
- data/lib/mundane-search/filters/helpers.rb +0 -44
- 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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
122
|
-
|
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
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
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')
|
data/lib/columns_hash.rb
ADDED
@@ -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
|
data/lib/mundane-search.rb
CHANGED
@@ -1,9 +1,17 @@
|
|
1
|
-
require "
|
2
|
-
|
3
|
-
require
|
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
|
-
|
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
|
-
@
|
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
|
-
|
15
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
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
|
9
|
-
|
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
|