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.
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
@@ -1,12 +1,11 @@
1
1
  module MundaneSearch
2
2
  module Filters
3
- end
4
- end
5
3
 
6
- require_relative 'filters/helpers'
7
- require_relative 'filters/base'
8
- require_relative 'filters/typical'
9
- require_relative 'filters/exact_match'
10
- require_relative 'filters/blank_params_are_nil'
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
- def filtered_collection
4
- case collection
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
- [filtered_collection, filtered_params]
21
+ if apply?
22
+ [filtered_collection, filtered_params]
23
+ else
24
+ [collection, params]
25
+ end
23
26
  end
24
27
  end
25
28
  end
@@ -3,7 +3,7 @@ module MundaneSearch::Filters
3
3
  def filtered_params
4
4
  params.inject({}) do |hash,e|
5
5
  val = e.last
6
- hash[e.first] = (val.nil? || val.empty?) ? nil : val
6
+ hash[e.first] = val.empty? ? nil : val
7
7
  hash
8
8
  end
9
9
  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
- has_a_match_value
4
- optional_without_match_value
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
@@ -0,0 +1,13 @@
1
+ module MundaneSearch
2
+ class InitialStack < Stack
3
+ attr_reader :collection, :params
4
+ def initialize(collection,params)
5
+ @collection, @params, @result_class = collection, params
6
+ end
7
+
8
+ def all_results
9
+ []
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ module MundaneSearch
2
+ class Railtie < Rails::Railtie
3
+ initializer "mundane-search.view_helpers" do
4
+ ActionView::Base.send :include, ViewHelpers
5
+ end
6
+ end
7
+ 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
- attr_reader :collection, :params, :previous_result
5
- def initialize(previous_result, filter)
6
- @previous_result = previous_result
7
- @collection, @params = filter.call(previous_result.collection, previous_result.params)
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 add_filter(filter)
11
- Result.new(self,filter)
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
- collection.each(*args, &block)
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
@@ -1,3 +1,3 @@
1
1
  module MundaneSearch
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  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
@@ -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
@@ -6,6 +6,12 @@ require 'rubygems'
6
6
  require 'bundler/setup'
7
7
  require 'mundane-search'
8
8
 
9
+ require './spec/active_record_setup'
10
+
11
+ def minitest!
12
+ require './spec/minitest_helper'
13
+ end
14
+
9
15
  begin
10
16
  require 'pry'
11
17
  Pry.start
@@ -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