mundane-search 0.0.1 → 0.0.2
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.
- 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
|