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
@@ -1,12 +1,11 @@
|
|
1
1
|
module MundaneSearch
|
2
2
|
module Filters
|
3
|
-
end
|
4
|
-
end
|
5
3
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
require_relative 'filters/attribute_match'
|
4
|
+
# autoload all the stuff in filters under this namespace
|
5
|
+
Dir.glob("#{File.dirname(__FILE__)}/filters/*.rb").each do |filename|
|
6
|
+
class_name = File.basename(filename, '.*').split('_').collect(&:capitalize).join.to_sym
|
7
|
+
autoload class_name, filename
|
8
|
+
end
|
12
9
|
|
10
|
+
end
|
11
|
+
end
|
@@ -1,14 +1,13 @@
|
|
1
1
|
module MundaneSearch::Filters
|
2
2
|
class AttributeMatch < Typical
|
3
|
-
|
4
|
-
|
5
|
-
when ActiveRecord::Relation
|
3
|
+
class ActiveRecord < self
|
4
|
+
def filtered_collection
|
6
5
|
collection.where(param_key => params[param_key.to_s])
|
7
|
-
when :sunspot?
|
8
|
-
# nothing yet
|
9
|
-
else
|
10
|
-
collection.select {|e| e.send(param_key) == params[param_key.to_s] }
|
11
6
|
end
|
12
7
|
end
|
8
|
+
|
9
|
+
def filtered_collection
|
10
|
+
collection.select {|e| e.send(param_key) == params[param_key.to_s] }
|
11
|
+
end
|
13
12
|
end
|
14
13
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module MundaneSearch::Filters
|
2
|
+
class AttributeSubstring < Typical
|
3
|
+
class ActiveRecord < self
|
4
|
+
def filtered_collection
|
5
|
+
# collection.where(param_key => params[param_key.to_s])
|
6
|
+
collection.where(["#{target} LIKE ?", "%#{params[param_key.to_s]}%"])
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def filtered_collection
|
11
|
+
collection.select {|e| e.send(target).index(params[param_key.to_s]) }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module MundaneSearch::Filters
|
2
2
|
class Base
|
3
|
-
include Helpers
|
4
3
|
attr_reader :collection, :params, :options
|
5
4
|
def initialize(collection, params = {}, options= {})
|
6
5
|
@collection, @params, @options = collection, params, options
|
@@ -19,7 +18,11 @@ module MundaneSearch::Filters
|
|
19
18
|
end
|
20
19
|
|
21
20
|
def call
|
22
|
-
|
21
|
+
if apply?
|
22
|
+
[filtered_collection, filtered_params]
|
23
|
+
else
|
24
|
+
[collection, params]
|
25
|
+
end
|
23
26
|
end
|
24
27
|
end
|
25
28
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module MundaneSearch::Filters
|
2
|
+
class Operator < Typical
|
3
|
+
class ActiveRecord < self
|
4
|
+
def filtered_collection
|
5
|
+
collection.where(["#{target} #{operator} ?", params[param_key.to_s]])
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def filtered_collection
|
10
|
+
collection.select {|e| e.send(target).send(operator, params[param_key.to_s]) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def operator
|
14
|
+
options[:operator]
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
|
3
|
+
module MundaneSearch
|
4
|
+
module Filters
|
5
|
+
module Shortcuts
|
6
|
+
def employ(shortcut, options = {}, &block)
|
7
|
+
filter_name, default_options = filter_for(shortcut)
|
8
|
+
filter = "#{filter_name}".safe_constantize
|
9
|
+
raise "No filter found for #{shortcut}" unless filter
|
10
|
+
[options].flatten.each do |opts|
|
11
|
+
use filter, default_options.merge(opts), &block
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
# Use symbols to avoid loading all shortcutted filters.
|
18
|
+
def filter_for(shortcut)
|
19
|
+
{
|
20
|
+
attribute_filter: [:"MundaneSearch::Filters::AttributeMatch", {}],
|
21
|
+
filter_greater_than: operator_filter(:>),
|
22
|
+
filter_less_than: operator_filter(:<),
|
23
|
+
filter_greater_than_or_equal_to: operator_filter(:>=),
|
24
|
+
filter_less_than_or_equal_to: operator_filter(:<=),
|
25
|
+
}[shortcut]
|
26
|
+
end
|
27
|
+
|
28
|
+
def operator_filter(operator)
|
29
|
+
[:"MundaneSearch::Filters::Operator", { operator: operator }]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -1,6 +1,31 @@
|
|
1
1
|
module MundaneSearch::Filters
|
2
2
|
class Typical < Base
|
3
|
-
|
4
|
-
|
3
|
+
def self.param_key_type
|
4
|
+
:string # common default
|
5
|
+
end
|
6
|
+
|
7
|
+
def target
|
8
|
+
options[:target] || param_key
|
9
|
+
end
|
10
|
+
|
11
|
+
def optional?
|
12
|
+
options[:required]
|
13
|
+
end
|
14
|
+
|
15
|
+
def apply?
|
16
|
+
match_value || optional?
|
17
|
+
end
|
18
|
+
|
19
|
+
def param_key
|
20
|
+
options.fetch(:param_key)
|
21
|
+
end
|
22
|
+
|
23
|
+
def match_value
|
24
|
+
params[param_key]
|
25
|
+
end
|
26
|
+
|
27
|
+
def param_key_type
|
28
|
+
options[:type] || :string
|
29
|
+
end
|
5
30
|
end
|
6
|
-
end
|
31
|
+
end
|
@@ -1,19 +1,34 @@
|
|
1
|
+
require 'active_model/naming'
|
2
|
+
|
1
3
|
module MundaneSearch
|
2
4
|
class Result
|
5
|
+
include Buildable
|
3
6
|
include Enumerable
|
4
|
-
|
5
|
-
def
|
6
|
-
@
|
7
|
-
|
7
|
+
|
8
|
+
def self.options
|
9
|
+
@options ||= {}
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :stack
|
13
|
+
def initialize(collection, params)
|
14
|
+
@stack = self.class.builder.result_for(collection,params)
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_model
|
18
|
+
@result_model ||= self.class.result_model_class.new(self)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.result_model_class
|
22
|
+
@result_model_class ||= ResultModel.model_class_for(self)
|
8
23
|
end
|
9
24
|
|
10
|
-
def
|
11
|
-
|
25
|
+
def to_a
|
26
|
+
stack.collection
|
12
27
|
end
|
13
28
|
|
14
29
|
def each(*args, &block)
|
15
30
|
return enum_for(__callee__) unless block_given?
|
16
|
-
|
31
|
+
to_a.each(*args, &block)
|
17
32
|
end
|
18
33
|
end
|
19
|
-
end
|
34
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'active_model/validations'
|
2
|
+
require 'active_model/naming'
|
3
|
+
require 'active_model/translation'
|
4
|
+
|
5
|
+
module MundaneSearch
|
6
|
+
class ResultModel
|
7
|
+
include ColumnsHash
|
8
|
+
include ActiveModel::Validations
|
9
|
+
|
10
|
+
def self.model_class_for(result_class)
|
11
|
+
builder = result_class.builder
|
12
|
+
Class.new(ResultModel) do |model_class|
|
13
|
+
model_class.model_name = result_class.options[:name] || result_class.name
|
14
|
+
builder.filter_canisters.each do |fc|
|
15
|
+
if fc.param_key
|
16
|
+
attribute_column(fc.param_key, fc.param_key_type)
|
17
|
+
define_method(fc.param_key) do
|
18
|
+
send(:result).stack.params[fc.param_key.to_s]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.model_name
|
26
|
+
name = 'GenericSearch' unless self.name
|
27
|
+
namespace = nil
|
28
|
+
ForcedSingularRouteKeyName.new(self, namespace, (@model_name || name))
|
29
|
+
end
|
30
|
+
|
31
|
+
# As string, for when ActionView turns a model name into a url.
|
32
|
+
def self.model_name=(name)
|
33
|
+
@model_name = name
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(result)
|
37
|
+
@result = result
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_key
|
41
|
+
['result-of-to-key']
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_model
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def persisted?
|
49
|
+
false
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
attr_reader :result
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
class ForcedSingularRouteKeyName < ActiveModel::Name
|
59
|
+
def initialize(klass, namespace = nil, name = nil)
|
60
|
+
super(klass, namespace, name)
|
61
|
+
@route_key = (namespace ? ActiveSupport::Inflector.singularize(@param_key) : @singular.dup)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module MundaneSearch
|
2
|
+
class Stack
|
3
|
+
def all_filters
|
4
|
+
return @all_filters if @all_filters
|
5
|
+
@all_filters = all_results.collect(&:filter)
|
6
|
+
end
|
7
|
+
|
8
|
+
def all_results
|
9
|
+
[self] + previous_result.all_results
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(previous_result, filter_canister)
|
13
|
+
@previous_result, @filter_canister = previous_result, filter_canister
|
14
|
+
end
|
15
|
+
|
16
|
+
def collection
|
17
|
+
filter.call.first
|
18
|
+
end
|
19
|
+
|
20
|
+
# necessary?
|
21
|
+
def params
|
22
|
+
filter.call.last
|
23
|
+
end
|
24
|
+
|
25
|
+
def add(filter_canister)
|
26
|
+
Stack.new(self,filter_canister)
|
27
|
+
end
|
28
|
+
|
29
|
+
def filter
|
30
|
+
@filter ||= filter_canister.build(previous_result.collection, previous_result.params)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
attr_reader :previous_result, :filter_canister
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module MundaneSearch
|
2
|
+
module ViewHelpers
|
3
|
+
def search_form_for(record, options = {}, &block)
|
4
|
+
# Later!
|
5
|
+
# if block_given?
|
6
|
+
# form_for(record, search_form_default_options(record).merge(options), &block)
|
7
|
+
# else
|
8
|
+
# # Auto-generate form
|
9
|
+
# end
|
10
|
+
form_for(record.to_model, search_form_default_options.merge(options), &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def simple_search_form_for(record, options = {}, &block)
|
14
|
+
# TODO: warn if simple_form not available?
|
15
|
+
simple_form_for(record.to_model, search_form_default_options.merge(options), &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
def search_form_default_options
|
19
|
+
{
|
20
|
+
method: "GET"
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/mundane-search.gemspec
CHANGED
@@ -17,9 +17,16 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
18
|
gem.require_paths = ["lib"]
|
19
19
|
|
20
|
+
gem.add_dependency "activemodel"
|
21
|
+
|
20
22
|
gem.add_development_dependency "minitest"
|
23
|
+
gem.add_development_dependency "mocha"
|
21
24
|
gem.add_development_dependency "rake"
|
22
25
|
gem.add_development_dependency "activerecord"
|
23
26
|
gem.add_development_dependency "sqlite3"
|
24
27
|
gem.add_development_dependency "database_cleaner"
|
28
|
+
gem.add_development_dependency "actionpack"
|
29
|
+
gem.add_development_dependency "simple_form"
|
30
|
+
gem.add_development_dependency "guard"
|
31
|
+
gem.add_development_dependency "guard-minitest"
|
25
32
|
end
|
data/script/console
CHANGED
data/spec/active_record_setup.rb
CHANGED
@@ -19,52 +19,9 @@ class Book < ActiveRecord::Base
|
|
19
19
|
# t.integer :sold
|
20
20
|
end
|
21
21
|
|
22
|
-
|
22
|
+
require_relative 'demo_data'
|
23
23
|
def populate_books!
|
24
|
-
|
25
|
-
{
|
26
|
-
title: "A Tale of Two Cities",
|
27
|
-
author: "Charles Dickens",
|
28
|
-
publication_date: Date.parse('31-12-1859'),
|
29
|
-
first_purchased_at: Time.utc(1859,"jan",1,20,15,1),
|
30
|
-
sold: 200_000_000
|
31
|
-
},
|
32
|
-
{
|
33
|
-
title: "The Lord of the Rings",
|
34
|
-
author: "J. R. R. Tolkien",
|
35
|
-
publication_date: Date.parse('31-12-1954'),
|
36
|
-
first_purchased_at: Time.utc(1954,"jan",1,20,15,1),
|
37
|
-
sold: 150_000_000
|
38
|
-
},
|
39
|
-
{
|
40
|
-
title: "The Little Prince (Le Petit Prince)",
|
41
|
-
author: "Antoine de Saint-Exupery",
|
42
|
-
publication_date: Date.parse('31-12-1943'),
|
43
|
-
first_purchased_at: Time.utc(1943,"jan",1,20,15,1),
|
44
|
-
sold: 140_000_000
|
45
|
-
},
|
46
|
-
{
|
47
|
-
title: "The Hobbit",
|
48
|
-
author: "J. R. R. Tolkien",
|
49
|
-
publication_date: Date.parse('31-12-1937'),
|
50
|
-
first_purchased_at: Time.utc(1937,"jan",1,20,15,1),
|
51
|
-
sold: 100_000_000
|
52
|
-
},
|
53
|
-
{
|
54
|
-
title: "Hong lou meng (Dream of the Red Chamber/The Story of the Stone)",
|
55
|
-
author: "Cao Xueqin",
|
56
|
-
publication_date: Date.parse('31-12-1754'),
|
57
|
-
first_purchased_at: Time.utc(1754,"jan",1,20,15,1),
|
58
|
-
sold: 100_000_000
|
59
|
-
},
|
60
|
-
{
|
61
|
-
title: "And Then There Were None",
|
62
|
-
author: "Agatha Christie",
|
63
|
-
publication_date: Date.parse('31-12-1939'),
|
64
|
-
first_purchased_at: Time.utc(1939,"jan",1,20,15,1),
|
65
|
-
sold: 100_000_000
|
66
|
-
}
|
67
|
-
].each do |book_hash|
|
24
|
+
demo_data.each do |book_hash|
|
68
25
|
Book.create(book_hash)
|
69
26
|
end
|
70
27
|
end
|