has_searcher 0.0.17 → 0.0.90
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/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +27 -1
- data/app/assets/stylesheets/has_searcher/application.css +13 -0
- data/app/controllers/has_searcher/application_controller.rb +4 -0
- data/app/helpers/has_searcher/application_helper.rb +4 -0
- data/{lib/app/models → app/models/searcher}/boostificator.rb +9 -9
- data/app/models/searcher/configuration.rb +76 -0
- data/app/models/searcher/model.rb +28 -0
- data/app/models/searcher.rb +114 -0
- data/app/views/layouts/has_searcher/application.html.erb +14 -0
- data/config/routes.rb +2 -0
- data/lib/has_searcher/engine.rb +5 -0
- data/lib/has_searcher/version.rb +1 -1
- data/lib/has_searcher.rb +10 -15
- data/lib/tasks/has_searcher_tasks.rake +4 -0
- metadata +81 -22
- data/.gitignore +0 -5
- data/Gemfile +0 -4
- data/has_searcher.gemspec +0 -23
- data/lib/app/controllers/crud_controller.rb +0 -56
- data/lib/app/controllers/suggestions_controller.rb +0 -21
- data/lib/app/models/search.rb +0 -106
- data/lib/has_searcher/base.rb +0 -11
- data/lib/has_searcher/formtastic.rb +0 -72
- data/lib/has_searcher/helpers.rb +0 -33
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2012 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
data/Rakefile
CHANGED
@@ -1,2 +1,28 @@
|
|
1
|
-
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'HasSearcher'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
24
|
+
load 'rails/tasks/engine.rake'
|
25
|
+
|
26
|
+
|
2
27
|
Bundler::GemHelper.install_tasks
|
28
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the top of the
|
9
|
+
* compiled file, but it's generally better to create a new file per style scope.
|
10
|
+
*
|
11
|
+
*= require_self
|
12
|
+
*= require_tree .
|
13
|
+
*/
|
@@ -1,23 +1,24 @@
|
|
1
|
-
class Boostificator
|
1
|
+
class Searcher::Boostificator
|
2
2
|
MILLIS_IN_YEAR = 1000 * 60 * 60 * 24 * 365
|
3
3
|
PRECISION = 6
|
4
4
|
|
5
|
-
attr_accessor :
|
5
|
+
attr_accessor :boost_field
|
6
6
|
|
7
7
|
attr_accessor :m, :x, :a, :b
|
8
8
|
|
9
9
|
attr_accessor :extra_boost
|
10
10
|
|
11
|
-
def initialize(options={})
|
11
|
+
def initialize(boost_field, options={})
|
12
|
+
self.boost_field = boost_field
|
12
13
|
options.reverse_merge!(:a => 1.0, :b => 1.0, :m => 1.0 / MILLIS_IN_YEAR, :extra_boost => 1.0)
|
13
|
-
options.each do |
|
14
|
-
self.send("#{
|
14
|
+
options.each do |attribute, value|
|
15
|
+
self.send("#{attribute}=", value)
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
18
19
|
|
19
|
-
def adjust_solr_params
|
20
|
-
|
20
|
+
def adjust_solr_params(sunspot)
|
21
|
+
sunspot.adjust_solr_params do |params|
|
21
22
|
params[:boost] = "product(#{extra_boost},#{recip_s})"
|
22
23
|
params[:defType] = 'edismax'
|
23
24
|
end
|
@@ -25,7 +26,7 @@ class Boostificator
|
|
25
26
|
|
26
27
|
private
|
27
28
|
def recip_s
|
28
|
-
@recip_s ||= "map(recip(abs(ms(#{now_s},#{
|
29
|
+
@recip_s ||= "map(recip(abs(ms(#{now_s},#{boost_field})),#{m},#{a},#{b}),#{recip_min},#{recip_max},1)"
|
29
30
|
end
|
30
31
|
|
31
32
|
def now
|
@@ -51,5 +52,4 @@ class Boostificator
|
|
51
52
|
def recip_max
|
52
53
|
@recip_max ||= sprintf("%f", (recip * 10**PRECISION).ceil/10.0**PRECISION)
|
53
54
|
end
|
54
|
-
|
55
55
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
class Searcher::Configuration
|
2
|
+
attr_accessor :searcher, :scopes
|
3
|
+
|
4
|
+
attr_accessor :facets
|
5
|
+
|
6
|
+
attr_writer :models
|
7
|
+
|
8
|
+
delegate :search_object, :to => :searcher
|
9
|
+
|
10
|
+
def initialize(searcher, &block)
|
11
|
+
self.searcher = searcher
|
12
|
+
self.scopes = {}
|
13
|
+
self.facets = {}
|
14
|
+
self.models = []
|
15
|
+
scope :runtime
|
16
|
+
scope :default
|
17
|
+
instance_eval &block if block
|
18
|
+
end
|
19
|
+
|
20
|
+
def keywords(field, options={})
|
21
|
+
search_object.create_field(field, options)
|
22
|
+
scope do |sunspot|
|
23
|
+
sunspot.fulltext search_object.send(field) if search_object.send(field).presence
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def models(*names_or_clases)
|
28
|
+
self.models = [*names_or_clases]
|
29
|
+
end
|
30
|
+
|
31
|
+
def search_models
|
32
|
+
@search_models ||= @models.map do |name_or_class|
|
33
|
+
name_or_class.is_a?(Class) ? name_or_class : name_or_class.to_s.classify.constantize
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def property(field, options={}, &block)
|
38
|
+
modificators = [*options[:modificator]] + [*options[:modificators]]
|
39
|
+
|
40
|
+
modificators.each do |modificator|
|
41
|
+
|
42
|
+
request_field = [field, modificator].compact.join('_')
|
43
|
+
|
44
|
+
search_object.create_field(request_field, options)
|
45
|
+
|
46
|
+
if block
|
47
|
+
scope &block
|
48
|
+
else
|
49
|
+
scope do |sunspot|
|
50
|
+
sunspot.with(field).send(modificator, search_object.send(request_field)) if search_object.send(request_field).presence
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
if modificators.empty?
|
56
|
+
search_object.create_field(field, options)
|
57
|
+
if block
|
58
|
+
scope &block
|
59
|
+
else
|
60
|
+
scope do |sunspot|
|
61
|
+
sunspot.with(field, search_object.send(field)) if search_object.send(field).presence
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def facet(name, &block)
|
68
|
+
facets[name] = block
|
69
|
+
end
|
70
|
+
|
71
|
+
def scope(name=:default, &block)
|
72
|
+
scopes[name] ||= []
|
73
|
+
scopes[name] << block if block
|
74
|
+
searcher
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Searcher::Model
|
2
|
+
attr_accessor :methods, :searcher
|
3
|
+
|
4
|
+
def initialize(searcher)
|
5
|
+
self.searcher = searcher
|
6
|
+
self.methods = Module.new
|
7
|
+
self.extend self.methods
|
8
|
+
end
|
9
|
+
|
10
|
+
def attributes=(params=nil)
|
11
|
+
params ||= {}
|
12
|
+
params.each do |field, value|
|
13
|
+
self.send "#{field}=", value if respond_to? "#{field}="
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def create_field(name, options)
|
18
|
+
methods.class_eval do
|
19
|
+
define_method name do
|
20
|
+
instance_variable_get("@#{name}") ||
|
21
|
+
(options[:default].call if options[:default] && options[:default].respond_to?(:call))
|
22
|
+
end
|
23
|
+
define_method "#{name}=" do |value|
|
24
|
+
instance_variable_set("@#{name}", value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
class Searcher
|
2
|
+
|
3
|
+
attr_accessor :configuration, :sunspot, :search_object
|
4
|
+
attr_accessor :scope_chain
|
5
|
+
|
6
|
+
delegate :current_page, :num_pages, :limit_value, :to => :results
|
7
|
+
|
8
|
+
def initialize(&block)
|
9
|
+
self.scope_chain = [:default, :runtime]
|
10
|
+
self.search_object = Searcher::Model.new(self)
|
11
|
+
self.configuration = Configuration.new(self, &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def sunspot
|
15
|
+
@sunspot ||= Sunspot.new_search configuration.search_models
|
16
|
+
end
|
17
|
+
|
18
|
+
def each(&block)
|
19
|
+
results.each(&block)
|
20
|
+
end
|
21
|
+
|
22
|
+
delegate :results, :total, :facet, :to => :executed_sunspot
|
23
|
+
alias_method :all, :results
|
24
|
+
|
25
|
+
def result_ids
|
26
|
+
executed_sunspot.hits.map(&:primary_key)
|
27
|
+
end
|
28
|
+
|
29
|
+
def order_by(name, type=:asc)
|
30
|
+
configuration.scope :runtime do |sunspot|
|
31
|
+
sunspot.order_by(name)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
alias_method :order, :order_by
|
35
|
+
|
36
|
+
def limit(number)
|
37
|
+
paginate(:per_page => number)
|
38
|
+
end
|
39
|
+
alias_method :per_page, :limit
|
40
|
+
alias_method :per, :limit
|
41
|
+
|
42
|
+
def page(number)
|
43
|
+
paginate(:page => number)
|
44
|
+
end
|
45
|
+
|
46
|
+
def paginate(params)
|
47
|
+
configuration.scope :runtime do |sunspot|
|
48
|
+
sunspot.paginate(params)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def scoped
|
53
|
+
default.runtime
|
54
|
+
end
|
55
|
+
|
56
|
+
def boost_by(field, options={})
|
57
|
+
boostificator = Boostificator.new(field, options)
|
58
|
+
configuration.scope :runtime do |sunspot|
|
59
|
+
boostificator.adjust_solr_params(sunspot)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def execute
|
64
|
+
unless @executed
|
65
|
+
build_query
|
66
|
+
set_facets
|
67
|
+
sunspot.execute
|
68
|
+
@executed = true
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def executed_sunspot
|
73
|
+
execute
|
74
|
+
sunspot
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def build_query
|
80
|
+
scope_chain.uniq.each do |scope_name|
|
81
|
+
configuration.scopes[scope_name].each do |block|
|
82
|
+
sunspot.build do |sunspot|
|
83
|
+
case block.arity
|
84
|
+
when 0
|
85
|
+
sunspot.instance_eval(&block)
|
86
|
+
when 1
|
87
|
+
block.call(sunspot)
|
88
|
+
else
|
89
|
+
raise ArgumentError.new "arity > 1 not supported"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def set_facets
|
97
|
+
configuration.facets.each_pair do |facet_name, block|
|
98
|
+
sunspot.build do |search|
|
99
|
+
search.instance_eval do |search|
|
100
|
+
search.facet facet_name, &block
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def method_missing(name, *args, &block)
|
107
|
+
if configuration.scopes.include?(name)
|
108
|
+
scope_chain << name
|
109
|
+
return self
|
110
|
+
end
|
111
|
+
return results.send(name, *args, &block) if results.respond_to?(name)
|
112
|
+
super
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>HasSearcher</title>
|
5
|
+
<%= stylesheet_link_tag "has_searcher/application", :media => "all" %>
|
6
|
+
<%= javascript_include_tag "has_searcher/application" %>
|
7
|
+
<%= csrf_meta_tags %>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<%= yield %>
|
12
|
+
|
13
|
+
</body>
|
14
|
+
</html>
|
data/config/routes.rb
ADDED
data/lib/has_searcher/version.rb
CHANGED
data/lib/has_searcher.rb
CHANGED
@@ -1,19 +1,14 @@
|
|
1
|
-
|
2
|
-
autoload :Base, 'has_searcher/base'
|
3
|
-
autoload :Helpers, 'has_searcher/helpers'
|
4
|
-
end
|
1
|
+
require "has_searcher/engine"
|
5
2
|
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
module HasSearcher
|
4
|
+
@@searchers = {}
|
5
|
+
def self.create_searcher(name, &block)
|
6
|
+
@@searchers[name] = block
|
9
7
|
end
|
10
|
-
end
|
11
|
-
|
12
|
-
require 'has_searcher/formtastic' rescue nil
|
13
8
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
9
|
+
def self.searcher(name, params={})
|
10
|
+
(Searcher.new &@@searchers[name]).tap do |searcher|
|
11
|
+
searcher.search_object.attributes = params
|
12
|
+
end
|
13
|
+
end
|
19
14
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: has_searcher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.90
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,40 +9,99 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-06
|
12
|
+
date: 2012-07-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
16
|
-
requirement: &
|
16
|
+
requirement: &6913540 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.2.3
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *6913540
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: sqlite3
|
27
|
+
requirement: &6913100 !ruby/object:Gem::Requirement
|
17
28
|
none: false
|
18
29
|
requirements:
|
19
30
|
- - ! '>='
|
20
31
|
- !ruby/object:Gem::Version
|
21
32
|
version: '0'
|
22
|
-
type: :
|
33
|
+
type: :development
|
23
34
|
prerelease: false
|
24
|
-
version_requirements: *
|
25
|
-
|
26
|
-
|
35
|
+
version_requirements: *6913100
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rspec-rails
|
38
|
+
requirement: &7006040 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *7006040
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: sunspot_matchers
|
49
|
+
requirement: &7005240 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *7005240
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: sunspot_rails
|
60
|
+
requirement: &7004020 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 2.0.0.pre
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *7004020
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sunspot_solr
|
71
|
+
requirement: &7003340 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 2.0.0.pre
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *7003340
|
80
|
+
description: ! "This gem adds ability to construct search objects for indexed models,
|
81
|
+
build search forms and execute searches.\n It works with sunspot,
|
82
|
+
inherited_resources and simple_form/formtastic"
|
27
83
|
email:
|
28
84
|
- lda@openteam.ru
|
29
85
|
executables: []
|
30
86
|
extensions: []
|
31
87
|
extra_rdoc_files: []
|
32
88
|
files:
|
33
|
-
- .
|
34
|
-
-
|
35
|
-
-
|
36
|
-
-
|
37
|
-
-
|
38
|
-
-
|
39
|
-
-
|
40
|
-
-
|
41
|
-
-
|
42
|
-
- lib/
|
43
|
-
- lib/has_searcher/
|
44
|
-
- lib/has_searcher/helpers.rb
|
89
|
+
- app/views/layouts/has_searcher/application.html.erb
|
90
|
+
- app/assets/stylesheets/has_searcher/application.css
|
91
|
+
- app/controllers/has_searcher/application_controller.rb
|
92
|
+
- app/models/searcher/configuration.rb
|
93
|
+
- app/models/searcher/model.rb
|
94
|
+
- app/models/searcher/boostificator.rb
|
95
|
+
- app/models/searcher.rb
|
96
|
+
- app/helpers/has_searcher/application_helper.rb
|
97
|
+
- config/routes.rb
|
98
|
+
- lib/tasks/has_searcher_tasks.rake
|
99
|
+
- lib/has_searcher/engine.rb
|
45
100
|
- lib/has_searcher/version.rb
|
101
|
+
- lib/has_searcher.rb
|
102
|
+
- MIT-LICENSE
|
103
|
+
- Rakefile
|
104
|
+
- README.rdoc
|
46
105
|
homepage: http://github.com/openteam/has_searcher
|
47
106
|
licenses: []
|
48
107
|
post_install_message:
|
@@ -57,7 +116,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
57
116
|
version: '0'
|
58
117
|
segments:
|
59
118
|
- 0
|
60
|
-
hash: -
|
119
|
+
hash: -759190286511194393
|
61
120
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
121
|
none: false
|
63
122
|
requirements:
|
@@ -66,9 +125,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
66
125
|
version: '0'
|
67
126
|
segments:
|
68
127
|
- 0
|
69
|
-
hash: -
|
128
|
+
hash: -759190286511194393
|
70
129
|
requirements: []
|
71
|
-
rubyforge_project:
|
130
|
+
rubyforge_project:
|
72
131
|
rubygems_version: 1.8.15
|
73
132
|
signing_key:
|
74
133
|
specification_version: 3
|
data/Gemfile
DELETED
data/has_searcher.gemspec
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
$:.push File.expand_path("../lib", __FILE__)
|
3
|
-
require "has_searcher/version"
|
4
|
-
|
5
|
-
Gem::Specification.new do |s|
|
6
|
-
s.name = "has_searcher"
|
7
|
-
s.version = HasSearcher::VERSION
|
8
|
-
s.platform = Gem::Platform::RUBY
|
9
|
-
s.authors = ["Dmitry Lihachev"]
|
10
|
-
s.email = ["lda@openteam.ru"]
|
11
|
-
s.homepage = "http://github.com/openteam/has_searcher"
|
12
|
-
s.summary = %q{Adds ability to construct search objects for indexed models}
|
13
|
-
s.description = %q{This gem adds ability to construct search objects for indexed models.
|
14
|
-
It works with sunspot, inherited_resources and formtastic}
|
15
|
-
|
16
|
-
s.add_dependency "rails"
|
17
|
-
s.rubyforge_project = "has_searcher"
|
18
|
-
|
19
|
-
s.files = `git ls-files`.split("\n")
|
20
|
-
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
21
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
22
|
-
s.require_paths = ["lib"]
|
23
|
-
end
|
@@ -1,56 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
class SearchController < ApplicationController
|
4
|
-
inherit_resources
|
5
|
-
|
6
|
-
helper_method :search_object_for
|
7
|
-
|
8
|
-
protected
|
9
|
-
|
10
|
-
def collection
|
11
|
-
get_collection_ivar || set_collection_ivar(search_and_paginate_collection)
|
12
|
-
end
|
13
|
-
|
14
|
-
def search_and_paginate_collection
|
15
|
-
if params[:utf8]
|
16
|
-
search_object = search_object_for(resource_instance_name)
|
17
|
-
search_object.pagination = paginate_options
|
18
|
-
search_object.results
|
19
|
-
else
|
20
|
-
end_of_association_chain.paginate(paginate_options)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def paginate_options(options={})
|
25
|
-
{
|
26
|
-
:page => params[:page],
|
27
|
-
:per_page => per_page
|
28
|
-
}.merge(options)
|
29
|
-
end
|
30
|
-
|
31
|
-
def per_page
|
32
|
-
20
|
33
|
-
end
|
34
|
-
|
35
|
-
def render_new_button?
|
36
|
-
respond_to? :new
|
37
|
-
end
|
38
|
-
|
39
|
-
def searcher_name(name)
|
40
|
-
"#{name}_search"
|
41
|
-
end
|
42
|
-
|
43
|
-
def create_searcher(searcher_name)
|
44
|
-
begin
|
45
|
-
searcher_clazz = searcher_name.classify.constantize
|
46
|
-
searcher_clazz.new(params[searcher_name]) do | searcher |
|
47
|
-
searcher_clazz.column_names.each do | column_name |
|
48
|
-
searcher[column_name] ||= params[column_name]
|
49
|
-
end
|
50
|
-
end
|
51
|
-
rescue NameError
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
end
|
56
|
-
|
@@ -1,21 +0,0 @@
|
|
1
|
-
class SuggestionsController < CrudController
|
2
|
-
|
3
|
-
def index
|
4
|
-
render :text => suggestions.to_json
|
5
|
-
end
|
6
|
-
|
7
|
-
|
8
|
-
protected
|
9
|
-
|
10
|
-
def suggestions
|
11
|
-
collection.map do | suggest |
|
12
|
-
{ :id => suggest.id, :value => suggest.term }
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def resource_instance_name
|
17
|
-
@resource_instance_name ||= params.delete(:model)
|
18
|
-
end
|
19
|
-
|
20
|
-
|
21
|
-
end
|
data/lib/app/models/search.rb
DELETED
@@ -1,106 +0,0 @@
|
|
1
|
-
class Search < ActiveRecord::Base
|
2
|
-
|
3
|
-
attr_accessor :pagination
|
4
|
-
|
5
|
-
class << self
|
6
|
-
def columns
|
7
|
-
@columns ||= [];
|
8
|
-
end
|
9
|
-
|
10
|
-
def column(name, sql_type = nil, default = nil, null = true)
|
11
|
-
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default,
|
12
|
-
sql_type.to_s, null)
|
13
|
-
end
|
14
|
-
|
15
|
-
def column_defaults
|
16
|
-
columns.map(&:name).inject({}) do | hash, name | hash[name] = nil; hash end
|
17
|
-
end
|
18
|
-
|
19
|
-
def columns_hash
|
20
|
-
columns.inject({}) do | hash, column | hash[column.name] = column; hash end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def pagination
|
25
|
-
@pagination ||= {}
|
26
|
-
@pagination.merge! per_page: per_page if respond_to? :per_page
|
27
|
-
@pagination
|
28
|
-
end
|
29
|
-
|
30
|
-
delegate :results, :to => :search
|
31
|
-
delegate :total, :to => :search
|
32
|
-
|
33
|
-
def result_ids
|
34
|
-
search.hits.map(&:primary_key)
|
35
|
-
end
|
36
|
-
|
37
|
-
protected
|
38
|
-
|
39
|
-
def search
|
40
|
-
Sunspot.search klass do | search |
|
41
|
-
search.keywords keywords if search_columns.delete("keywords")
|
42
|
-
search_columns.each do | column |
|
43
|
-
value = normalize(column)
|
44
|
-
if column_for_attribute(column).type == :text && self.class.serialized_attributes[column] != Array
|
45
|
-
if fuzzy?(value)
|
46
|
-
search.adjust_solr_params do |params|
|
47
|
-
params[:q] = value.split(/ /).map{ |value| "#{column}_text:#{value}"}.join(' ')
|
48
|
-
end
|
49
|
-
else
|
50
|
-
search.keywords value, :fields => column
|
51
|
-
end
|
52
|
-
else
|
53
|
-
case column
|
54
|
-
when /_lt$/
|
55
|
-
search.with(column[0..-4]).less_than(value)
|
56
|
-
when /_gt$/
|
57
|
-
search.with(column[0..-4]).greater_than(value)
|
58
|
-
else
|
59
|
-
search.with column, value
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
additional_search(search)
|
64
|
-
search.order_by *order_by.split(' ') if respond_to?(:order_by) && order_by.present?
|
65
|
-
search.paginate pagination if pagination.try(:any?)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def additional_search(search)
|
70
|
-
end
|
71
|
-
|
72
|
-
def save(validate = true)
|
73
|
-
validate ? valid? : true
|
74
|
-
end
|
75
|
-
|
76
|
-
def search_columns
|
77
|
-
@search_columns ||= (self.class.column_names - %w[per_page order_by]).select{ |column| normalize(column).present? }
|
78
|
-
end
|
79
|
-
|
80
|
-
def normalize(column)
|
81
|
-
if respond_to?("normalize_#{column}")
|
82
|
-
send "normalize_#{column}"
|
83
|
-
elsif self.class.serialized_attributes[column] == Array
|
84
|
-
[*self.send("#{column}_before_type_cast")].select(&:present?)
|
85
|
-
elsif column_for_attribute(column).type == :integer
|
86
|
-
self[column].try(:zero?) ? nil : self[column]
|
87
|
-
elsif column_for_attribute(column).type == :text && column =~ /term$/
|
88
|
-
normalize_term_column(self[column])
|
89
|
-
else
|
90
|
-
self[column]
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
def normalize_term_column(text)
|
95
|
-
text.gsub(/[^[:alnum:]~]+/, ' ').strip if text
|
96
|
-
end
|
97
|
-
|
98
|
-
def fuzzy?(text)
|
99
|
-
text =~ /~/
|
100
|
-
end
|
101
|
-
|
102
|
-
def klass
|
103
|
-
self.class.model_name.classify.gsub(/Search$/, '').constantize
|
104
|
-
end
|
105
|
-
|
106
|
-
end
|
data/lib/has_searcher/base.rb
DELETED
@@ -1,72 +0,0 @@
|
|
1
|
-
begin
|
2
|
-
require 'formtastic'
|
3
|
-
|
4
|
-
module Formtastic
|
5
|
-
module Helpers
|
6
|
-
module FormHelper
|
7
|
-
def semantic_search_form_for(name, *args, &proc)
|
8
|
-
options = args.extract_options!
|
9
|
-
options.reverse_merge! :html => {:method => :get}
|
10
|
-
url = params[:controller].sub(/\/\w+$/, '').split("/").map(&:underscore)
|
11
|
-
options[:url] ||= url.push name.to_s.pluralize
|
12
|
-
semantic_form_for searcher_for(name), *(args << options), &proc
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
module SemanticFormHelper
|
18
|
-
def semantic_search_form_for(*args)
|
19
|
-
Formtastic::Helpers::FormHelper.semantic_search_form_for(args)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
class SemanticFormBuilder
|
24
|
-
def search_button(options={})
|
25
|
-
commit_button I18n.t('search'), options.merge(button_html: {name: nil},
|
26
|
-
wrapper_html: {class: 'button'})
|
27
|
-
end
|
28
|
-
|
29
|
-
def default_input_type_with_text_as_string_on_search(method, options={})
|
30
|
-
if object.is_a?(Search) && object.class.respond_to?(:has_enum?) && !object.class.has_enum?(method) && object.column_for_attribute(method).try(:type) == :text
|
31
|
-
:string
|
32
|
-
else
|
33
|
-
default_input_type_without_text_as_string_on_search(method, options)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
alias_method_chain :default_input_type, :text_as_string_on_search
|
38
|
-
|
39
|
-
def inputs_with_search(*args, &block)
|
40
|
-
if args.empty? && object.is_a?(Search)
|
41
|
-
args = object.class.column_names.collect do | column |
|
42
|
-
if belongs_to? column
|
43
|
-
column = column.gsub /_id$/, ''
|
44
|
-
end
|
45
|
-
column
|
46
|
-
end.map(&:to_sym)
|
47
|
-
args -= [:term, :order_by, :per_page]
|
48
|
-
end
|
49
|
-
inputs_without_search(*args, &block)
|
50
|
-
end
|
51
|
-
|
52
|
-
alias_method_chain :inputs, :search
|
53
|
-
|
54
|
-
def buttons_with_search(*args, &block)
|
55
|
-
args = :search if args.empty? && object.is_a?(Search)
|
56
|
-
buttons_without_search(*args, &block)
|
57
|
-
end
|
58
|
-
|
59
|
-
alias_method_chain :buttons, :search
|
60
|
-
|
61
|
-
protected
|
62
|
-
|
63
|
-
def belongs_to?(method)
|
64
|
-
method = method.to_s
|
65
|
-
method =~ /_id$/ &&
|
66
|
-
(reflect = object.class.reflect_on_association(method.gsub(/_id$/, '').to_sym)) &&
|
67
|
-
reflect.macro == :belongs_to
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
rescue LoadError
|
72
|
-
end
|
data/lib/has_searcher/helpers.rb
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
module HasSearcher::Helpers
|
2
|
-
|
3
|
-
protected
|
4
|
-
|
5
|
-
def searcher
|
6
|
-
@searcher ||= searcher_for(resource_instance_name)
|
7
|
-
end
|
8
|
-
|
9
|
-
def searcher_for(name)
|
10
|
-
instance_variable_get("@#{searcher_name(name)}") ||
|
11
|
-
instance_variable_set("@#{searcher_name(name)}", create_searcher(searcher_name(name)))
|
12
|
-
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def searcher_name(name)
|
17
|
-
"#{name}_search"
|
18
|
-
end
|
19
|
-
|
20
|
-
def create_searcher(searcher_name)
|
21
|
-
begin
|
22
|
-
searcher_clazz = searcher_name.classify.constantize
|
23
|
-
rescue NameError
|
24
|
-
return
|
25
|
-
end
|
26
|
-
searcher_clazz.new(params[searcher_name]) do | searcher |
|
27
|
-
searcher_clazz.column_names.each do | column_name |
|
28
|
-
searcher.send("#{column_name}=", params[column_name]) if searcher.send(column_name).blank?
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
end
|