has_searcher 0.0.1
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/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Rakefile +2 -0
- data/has_searcher.gemspec +22 -0
- data/lib/app/controllers/crud_controller.rb +56 -0
- data/lib/app/controllers/suggestions_controller.rb +21 -0
- data/lib/app/models/search.rb +68 -0
- data/lib/has_searcher/base.rb +10 -0
- data/lib/has_searcher/formtastic.rb +62 -0
- data/lib/has_searcher/helpers.rb +28 -0
- data/lib/has_searcher/version.rb +3 -0
- data/lib/has_searcher.rb +11 -0
- metadata +78 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
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.rubyforge_project = "has_searcher"
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
end
|
@@ -0,0 +1,56 @@
|
|
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
|
+
|
@@ -0,0 +1,21 @@
|
|
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
|
@@ -0,0 +1,68 @@
|
|
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
|
+
end
|
15
|
+
|
16
|
+
delegate :results, :to => :search
|
17
|
+
|
18
|
+
def result_ids
|
19
|
+
search.hits.map(&:primary_key)
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
def search
|
25
|
+
klass.search do | search |
|
26
|
+
search.keywords normalize_text(keywords) if search_columns.delete("keywords")
|
27
|
+
search_columns.each do | column |
|
28
|
+
if column_for_attribute(column).type == :text
|
29
|
+
search.keywords normalize(column), :fields => column
|
30
|
+
else
|
31
|
+
search.with column, normalize(column)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
search.paginate pagination if pagination.try(:any?)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def save(validate = true)
|
39
|
+
validate ? valid? : true
|
40
|
+
end
|
41
|
+
|
42
|
+
def search_columns
|
43
|
+
@search_columns ||= self.class.column_names.select{ |column| normalize(column).present? }
|
44
|
+
end
|
45
|
+
|
46
|
+
def normalize(column)
|
47
|
+
if respond_to? "normalize_#{column}"
|
48
|
+
send "normalize_#{column}"
|
49
|
+
elsif self.class.serialized_attributes[column] == Array
|
50
|
+
[*self.send("#{column}_before_type_cast")].select(&:present?)
|
51
|
+
elsif column_for_attribute(column).type == :integer
|
52
|
+
self[column].try(:zero?) ? nil : self[column]
|
53
|
+
elsif column_for_attribute(column).type == :text && column == "term"
|
54
|
+
normalize_text(self[column])
|
55
|
+
else
|
56
|
+
self[column]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def normalize_text(text)
|
61
|
+
text.gsub /-/, ' ' if text
|
62
|
+
end
|
63
|
+
|
64
|
+
def klass
|
65
|
+
self.class.model_name.classify.gsub(/Search$/, '').constantize
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'formtastic'
|
2
|
+
|
3
|
+
module Formtastic
|
4
|
+
module SemanticFormHelper
|
5
|
+
def semantic_search_form_for(name, *args, &proc)
|
6
|
+
options = args.extract_options!
|
7
|
+
options.reverse_merge! :html => {:method => :get}
|
8
|
+
url = params[:controller].sub(/\/\w+$/, '').split("/").map(&:underscore)
|
9
|
+
options[:url] ||= url.push name.to_s.pluralize
|
10
|
+
semantic_form_for search_object_for(name), *(args << options), &proc
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class SemanticFormBuilder
|
15
|
+
def search_button(options={})
|
16
|
+
commit_button I18n.t('search'), options.merge(button_html: {name: nil},
|
17
|
+
wrapper_html: {class: 'button'})
|
18
|
+
end
|
19
|
+
|
20
|
+
def default_input_type_with_text_as_string_on_search(method, options={})
|
21
|
+
if object.is_a?(Search) && object.column_for_attribute(method).try(:type) == :text
|
22
|
+
:string
|
23
|
+
else
|
24
|
+
default_input_type_without_text_as_string_on_search(method, options)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
alias_method_chain :default_input_type, :text_as_string_on_search
|
29
|
+
|
30
|
+
def inputs_with_search(*args, &block)
|
31
|
+
if args.empty? && object.is_a?(Search)
|
32
|
+
args = object.class.column_names.collect do | column |
|
33
|
+
if belongs_to? column
|
34
|
+
column = column.gsub /_id$/, ''
|
35
|
+
end
|
36
|
+
column
|
37
|
+
end.map(&:to_sym)
|
38
|
+
args -= [:term]
|
39
|
+
end
|
40
|
+
inputs_without_search(*args, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
alias_method_chain :inputs, :search
|
44
|
+
|
45
|
+
def buttons_with_search(*args, &block)
|
46
|
+
args = :search if args.empty? && object.is_a?(Search)
|
47
|
+
buttons_without_search(*args, &block)
|
48
|
+
end
|
49
|
+
|
50
|
+
alias_method_chain :buttons, :search
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
def belongs_to?(method)
|
55
|
+
method = method.to_s
|
56
|
+
method =~ /_id$/ &&
|
57
|
+
(reflect = object.class.reflect_on_association(method.gsub(/_id$/, '').to_sym)) &&
|
58
|
+
reflect.macro == :belongs_to
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module HasSearcher::Helpers
|
2
|
+
|
3
|
+
protected
|
4
|
+
|
5
|
+
def searcher_for(name)
|
6
|
+
instance_variable_get("@#{searcher_name(name)}") ||
|
7
|
+
instance_variable_set("@#{searcher_name(name)}", create_searcher(searcher_name(name)))
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def searcher_name(name)
|
13
|
+
"#{name}_search"
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_searcher(searcher_name)
|
17
|
+
begin
|
18
|
+
searcher_clazz = searcher_name.classify.constantize
|
19
|
+
searcher_clazz.new(params[searcher_name]) do | searcher |
|
20
|
+
searcher_clazz.column_names.each do | column_name |
|
21
|
+
searcher[column_name] ||= params[column_name]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
rescue NameError
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/lib/has_searcher.rb
ADDED
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: has_searcher
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Dmitry Lihachev
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-04-14 00:00:00 +07:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: |-
|
22
|
+
This gem adds ability to construct search objects for indexed models.
|
23
|
+
It works with sunspot, inherited_resources and formtastic
|
24
|
+
email:
|
25
|
+
- lda@openteam.ru
|
26
|
+
executables: []
|
27
|
+
|
28
|
+
extensions: []
|
29
|
+
|
30
|
+
extra_rdoc_files: []
|
31
|
+
|
32
|
+
files:
|
33
|
+
- .gitignore
|
34
|
+
- Gemfile
|
35
|
+
- Rakefile
|
36
|
+
- has_searcher.gemspec
|
37
|
+
- lib/app/controllers/crud_controller.rb
|
38
|
+
- lib/app/controllers/suggestions_controller.rb
|
39
|
+
- lib/app/models/search.rb
|
40
|
+
- lib/has_searcher.rb
|
41
|
+
- lib/has_searcher/base.rb
|
42
|
+
- lib/has_searcher/formtastic.rb
|
43
|
+
- lib/has_searcher/helpers.rb
|
44
|
+
- lib/has_searcher/version.rb
|
45
|
+
has_rdoc: true
|
46
|
+
homepage: http://github.com/openteam/has_searcher
|
47
|
+
licenses: []
|
48
|
+
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
requirements: []
|
71
|
+
|
72
|
+
rubyforge_project: has_searcher
|
73
|
+
rubygems_version: 1.3.7
|
74
|
+
signing_key:
|
75
|
+
specification_version: 3
|
76
|
+
summary: Adds ability to construct search objects for indexed models
|
77
|
+
test_files: []
|
78
|
+
|