spree_sunspot 0.70.0
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/LICENSE +26 -0
- data/README.md +26 -0
- data/app/assets/images/store/checkbox.png +0 -0
- data/app/assets/javascripts/admin/spree_sunspot.js +1 -0
- data/app/assets/javascripts/store/filters.js +45 -0
- data/app/assets/javascripts/store/spree_sunspot.js +2 -0
- data/app/assets/stylesheets/admin/spree_sunspot.css +3 -0
- data/app/assets/stylesheets/store/filters.css.erb +37 -0
- data/app/assets/stylesheets/store/spree_sunspot.css +3 -0
- data/app/models/app_configuration_decorator.rb +3 -0
- data/app/models/product_decorator.rb +20 -0
- data/app/views/base/filter.html.erb +1 -0
- data/app/views/home/filter.html.erb +1 -0
- data/app/views/shared/_filter.html.erb +28 -0
- data/app/views/shared/_search_results.html.erb +11 -0
- data/config/locales/en.yml +7 -0
- data/config/locales/it.yml +7 -0
- data/config/routes.rb +5 -0
- data/lib/generators/spree_sunspot/install/install_generator.rb +30 -0
- data/lib/generators/spree_sunspot/install/templates/spree_sunspot.rb +51 -0
- data/lib/spree_sunspot.rb +2 -0
- data/lib/spree_sunspot/engine.rb +40 -0
- data/lib/spree_sunspot/filter/filter.rb +190 -0
- data/lib/spree_sunspot/filter_support.rb +78 -0
- data/lib/spree_sunspot/filters.rb +28 -0
- data/lib/spree_sunspot/search.rb +100 -0
- data/lib/spree_sunspot/setup.rb +25 -0
- data/lib/tasks/spree_sunspot.rake +8 -0
- metadata +137 -0
data/LICENSE
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Copyright (c) 2012 [name of plugin creator]
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification,
|
5
|
+
are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice,
|
8
|
+
this list of conditions and the following disclaimer.
|
9
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
11
|
+
and/or other materials provided with the distribution.
|
12
|
+
* Neither the name Spree nor the names of its contributors may be used to
|
13
|
+
endorse or promote products derived from this software without specific
|
14
|
+
prior written permission.
|
15
|
+
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
17
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
18
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
19
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
20
|
+
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
21
|
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
22
|
+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
23
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
24
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
25
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
26
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Spree::Sunspot
|
2
|
+
==============
|
3
|
+
|
4
|
+
Use Sunspot as search engine in Spree applications
|
5
|
+
|
6
|
+
|
7
|
+
Basic Installation
|
8
|
+
------------------
|
9
|
+
|
10
|
+
1. Add the following to your Gemfile
|
11
|
+
<pre>
|
12
|
+
gem 'spree_sunspot', '~> 0.70.0'
|
13
|
+
</pre>
|
14
|
+
2. Run `bundle install`
|
15
|
+
3. lunch installation:
|
16
|
+
<pre>
|
17
|
+
rails g spree_sunspot:install
|
18
|
+
</pre>
|
19
|
+
|
20
|
+
|
21
|
+
Example
|
22
|
+
=======
|
23
|
+
|
24
|
+
COMING SOON
|
25
|
+
|
26
|
+
Copyright (c) 2012 [Damiano Giacomello], released under the New BSD License
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
//= require admin/spree_core
|
@@ -0,0 +1,45 @@
|
|
1
|
+
function setup_label() {
|
2
|
+
if ($('label.filter_field input').length) {
|
3
|
+
$('label.filter_field').each(function(){
|
4
|
+
$(this).removeClass('c_on');
|
5
|
+
});
|
6
|
+
$('label.filter_field input:checked').each(function(){
|
7
|
+
$(this).parent('label').addClass('c_on');
|
8
|
+
});
|
9
|
+
};
|
10
|
+
};
|
11
|
+
|
12
|
+
function submit_query() {
|
13
|
+
var value = $.map($.makeArray($('#filter fieldset')), function(val, index) {
|
14
|
+
var name = $(val).attr('data-field');
|
15
|
+
var values = $.map($.makeArray($(val).find('label.filter_field input:checked')), function(param, i) {
|
16
|
+
return $(param).val();
|
17
|
+
});
|
18
|
+
|
19
|
+
if (values.length) {
|
20
|
+
return name + "=" + values.join(';');
|
21
|
+
}
|
22
|
+
}).join('|');
|
23
|
+
|
24
|
+
var source_url = $('#filter input[name="source_url"]').val();
|
25
|
+
var form = "<form action='" + $('form#filter').attr('action') + "' method='get' accept-charset='UTF-8'><input type='hidden' name='s' value='" + value + "'/><input type='hidden' name='source_url' value='" + source_url + "'/></form>";
|
26
|
+
var $form = $(form);
|
27
|
+
$form.appendTo('body').submit();
|
28
|
+
};
|
29
|
+
|
30
|
+
$(function() {
|
31
|
+
$('label.filter_field').click(function(){
|
32
|
+
setup_label();
|
33
|
+
});
|
34
|
+
setup_label();
|
35
|
+
|
36
|
+
$('label.filter_field input').click(function() {
|
37
|
+
submit_query();
|
38
|
+
});
|
39
|
+
|
40
|
+
$('a.filter_clear_all').click(function() {
|
41
|
+
$('#filter input:checked').removeAttr('checked');
|
42
|
+
setup_label();
|
43
|
+
return false;
|
44
|
+
});
|
45
|
+
});
|
@@ -0,0 +1,37 @@
|
|
1
|
+
fieldset.filter {
|
2
|
+
margin-bottom: 10px;
|
3
|
+
}
|
4
|
+
|
5
|
+
fieldset.filter legend {
|
6
|
+
margin-bottom: 3px;
|
7
|
+
}
|
8
|
+
|
9
|
+
p.filter_field {
|
10
|
+
margin-left: 3px;
|
11
|
+
margin-bottom: 3px;
|
12
|
+
}
|
13
|
+
|
14
|
+
label.filter_field {
|
15
|
+
height: 10px;
|
16
|
+
width: 10px;
|
17
|
+
margin-right: 4px;
|
18
|
+
display: inline-block;
|
19
|
+
background: url(<%= image_path 'store/checkbox.png' %>) 0 0;
|
20
|
+
}
|
21
|
+
|
22
|
+
label.filter_field.c_on {
|
23
|
+
background: url(<%= image_path 'store/checkbox.png' %>) 0 10px;
|
24
|
+
}
|
25
|
+
|
26
|
+
label.filter_value {
|
27
|
+
cursor: default;
|
28
|
+
}
|
29
|
+
|
30
|
+
label.filter_field input { position: absolute; left: -9999px; }
|
31
|
+
|
32
|
+
.filter_clear_all { float: right; }
|
33
|
+
|
34
|
+
#filter legend { text-transform: uppercase; font-weight: bold; }
|
35
|
+
|
36
|
+
.filter_submit { display: none; }
|
37
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
Product.class_eval do
|
2
|
+
def get_option_values(option_name)
|
3
|
+
sql = <<-eos
|
4
|
+
SELECT DISTINCT ov.id, ov.presentation
|
5
|
+
FROM option_values AS ov
|
6
|
+
LEFT JOIN option_types AS ot ON (ov.option_type_id = ot.id)
|
7
|
+
LEFT JOIN option_values_variants AS ovv ON (ovv.option_value_id = ov.id)
|
8
|
+
LEFT JOIN variants AS v ON (ovv.variant_id = v.id)
|
9
|
+
LEFT JOIN products AS p ON (v.product_id = p.id)
|
10
|
+
WHERE ((ot.name = '#{option_name}' OR ot.presentation = '#{option_name}')
|
11
|
+
AND p.id = #{self.id});
|
12
|
+
eos
|
13
|
+
OptionValue.find_by_sql(sql).map(&:presentation)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
unless SpreeSunspot::Setup.configuration.nil?
|
18
|
+
Product.class_eval &SpreeSunspot::Setup.configuration
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= render :partial => "shared/search_results", :locals => { :products => @products, :searcher => @searcher } %>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= render :partial => "shared/search_results", :locals => { :products => @products, :searcher => @searcher } %>
|
@@ -0,0 +1,28 @@
|
|
1
|
+
<% url_options = { "action" => "filter" }.merge(params)
|
2
|
+
url_options.merge!(controller.filter_url_settings) if controller.respond_to?(:filter_url_settings)
|
3
|
+
%>
|
4
|
+
<%= form_tag(url_options, :method => :get, :id => "filter") do %>
|
5
|
+
<%= hidden_field_tag 'source_url', params['source_url'] || request.url %>
|
6
|
+
<%= link_to I18n.t(:clear_all), '#', :class => 'filter_clear_all' %>
|
7
|
+
<% filter_params.each do |filter| %>
|
8
|
+
<% next unless filter.display? %>
|
9
|
+
<% if lookup_context.find_all("filters/_#{filter.display_param}").any? %>
|
10
|
+
<%= render :partial => "filters/#{filter.display_param}", :locals => { :filter => filter }%>
|
11
|
+
<% else %>
|
12
|
+
<fieldset class="filter" data-field="<%= filter.display_param %>">
|
13
|
+
<legend class="sub-header"><%= filter.display_name %></legend>
|
14
|
+
<% filter.html_values.each_with_index do |hv, index| %>
|
15
|
+
<p class="filter_field">
|
16
|
+
<% selected = (!(@searcher and @searcher.query).nil? and @searcher.query.has_filter?(filter, hv[:value])) %>
|
17
|
+
<% checkbox_id = "#{filter.display_param}_#{index}" %>
|
18
|
+
<label class="filter_field" for="<%= checkbox_id %>">
|
19
|
+
<%= check_box_tag "s[#{filter.display_param}][]", hv[:value], selected, { :id => checkbox_id } %>
|
20
|
+
</label>
|
21
|
+
<label class="filter_value nowrap"><%= hv[:display] %></label>
|
22
|
+
</p>
|
23
|
+
<% end %>
|
24
|
+
</fieldset>
|
25
|
+
<% end %>
|
26
|
+
<% end %>
|
27
|
+
<%= submit_tag I18n.t(:search), :class => 'filter_submit' %>
|
28
|
+
<% end %>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<% content_for :sidebar do %>
|
2
|
+
<div data-hook="homepage_sidebar_navigation">
|
3
|
+
<%= render :partial => 'shared/taxonomies' %>
|
4
|
+
</div>
|
5
|
+
<% end %>
|
6
|
+
|
7
|
+
<% if @products.blank? %>
|
8
|
+
<%= t(:empty_search_results) %>
|
9
|
+
<% else %>
|
10
|
+
<%= render :partial => "shared/products", :locals => { :products => @products }%>
|
11
|
+
<% end %>
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# Sample localization file for English. Add more files in this directory for other locales.
|
2
|
+
# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
|
3
|
+
|
4
|
+
en:
|
5
|
+
empty_search_results: "No products found"
|
6
|
+
clear_all: "Clear All"
|
7
|
+
search: "Search"
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# Sample localization file for English. Add more files in this directory for other locales.
|
2
|
+
# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
|
3
|
+
|
4
|
+
it:
|
5
|
+
empty_search_results: "Nessun prodotto trovato"
|
6
|
+
clear_all: "Cancella tutto"
|
7
|
+
search: "Cerca"
|
data/config/routes.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
if not defined?(SunspotRails::Generators::InstallGenerator)
|
2
|
+
require 'generators/sunspot_rails/install/install_generator.rb'
|
3
|
+
end
|
4
|
+
|
5
|
+
module SpreeSunspot
|
6
|
+
module Generators
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
8
|
+
source_root File.expand_path("../templates", __FILE__)
|
9
|
+
def self.source_root
|
10
|
+
@source_root ||= File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
|
11
|
+
end
|
12
|
+
|
13
|
+
def invoke_sunspot_rails_generator
|
14
|
+
SunspotRails::Generators::InstallGenerator.start
|
15
|
+
end
|
16
|
+
|
17
|
+
def copy_initializer_file
|
18
|
+
copy_file "spree_sunspot.rb", "config/initializers/spree_sunspot.rb"
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_javascripts
|
22
|
+
append_file "app/assets/javascripts/store/all.js", "//= require store/spree_sunspot\n"
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_stylesheets
|
26
|
+
inject_into_file "app/assets/stylesheets/store/all.css", " *= require store/spree_sunspot\n", :before => /\*\//, :verbose => true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
SpreeSunspot::Setup.configure do
|
2
|
+
searchable :auto_index => true, :auto_remove => true do
|
3
|
+
text :name, :boost => 2.0
|
4
|
+
text :description, :boost => 1.2
|
5
|
+
time :available_on
|
6
|
+
integer :taxon_ids, :references => Taxon, :multiple => true do
|
7
|
+
taxons.collect{|t| t.self_and_ancestors.map(&:id) }.flatten
|
8
|
+
end
|
9
|
+
string :taxon_names, :multiple => true do
|
10
|
+
taxons.collect{|t| t.self_and_ancestors.map(&:name) }.flatten
|
11
|
+
end
|
12
|
+
text :taxon_names do
|
13
|
+
taxons.collect{|t| t.self_and_ancestors.map(&:name) }.flatten
|
14
|
+
end
|
15
|
+
float :price
|
16
|
+
# Additional Examples
|
17
|
+
#
|
18
|
+
# string :category_names, :multiple => true do
|
19
|
+
# category = Taxon.find_by_permalink('categories')
|
20
|
+
# taxons.select{|t| t.ancestors.include?(category)}.collect{|t| t.self_and_ancestors.map(&:name)}.flatten - [category.name]
|
21
|
+
# end
|
22
|
+
# string :brand_name do
|
23
|
+
# brand = Taxon.find_by_permalink('brands')
|
24
|
+
# t = taxons.select{|t| t.ancestors.include?(brand)}.first
|
25
|
+
# t.name unless t.nil?
|
26
|
+
# end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
SpreeSunspot::Setup.filters do |filters|
|
31
|
+
# Maximum value would be ignored if the range maximum is set to
|
32
|
+
# Spree::Sunspot::Setup::IGNORE_MAX
|
33
|
+
filters.add do |f|
|
34
|
+
f.search_param = 'price'
|
35
|
+
f.display_name = 'Price'
|
36
|
+
f.values { [0..10, 10..20, 20..30, 30..SpreeSunspot::Setup::IGNORE_MAX] }
|
37
|
+
end
|
38
|
+
|
39
|
+
# Additional examples
|
40
|
+
# filters.add do |f|
|
41
|
+
# f.search_param = 'category_name'
|
42
|
+
# f.display_name = 'Category'
|
43
|
+
# f.values { Taxon.find_by_permalink('categories').children.map(&:name) }
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# filters.add do |f|
|
47
|
+
# f.search_param = 'brand_name'
|
48
|
+
# f.display_name = 'Brand'
|
49
|
+
# f.values { Taxon.find_by_permalink('brands').children.map(&:name) }
|
50
|
+
# end
|
51
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spree_sunspot/setup'
|
2
|
+
require 'spree_sunspot/filter_support'
|
3
|
+
|
4
|
+
module SpreeSunspot
|
5
|
+
class Engine < Rails::Engine
|
6
|
+
engine_name 'spree_sunspot'
|
7
|
+
|
8
|
+
config.autoload_paths += %W(#{config.root}/lib)
|
9
|
+
|
10
|
+
# use rspec for tests
|
11
|
+
config.generators do |g|
|
12
|
+
g.test_framework :rspec
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.activate
|
16
|
+
Dir.glob(File.join(File.dirname(__FILE__), "../../app/**/*_decorator*.rb")) do |c|
|
17
|
+
Rails.configuration.cache_classes ? require(c) : load(c)
|
18
|
+
end
|
19
|
+
|
20
|
+
ActionView::Base.class_eval do
|
21
|
+
include SpreeSunspot::FilterSupport::Helpers
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
config.to_prepare &method(:activate).to_proc
|
26
|
+
#ActionView::Base.class_eval { include SpreeSunspot::FilterSupport::Helpers }
|
27
|
+
#ActionController::Base.class_eval do
|
28
|
+
# include(SpreeSunspot::FilterSupport)
|
29
|
+
# filter_support
|
30
|
+
#end
|
31
|
+
|
32
|
+
initializer "spree.sunspot.search_config", :after => "spree.environment" do |app|
|
33
|
+
Spree::BaseController.class_eval do
|
34
|
+
include(SpreeSunspot::FilterSupport)
|
35
|
+
filter_support
|
36
|
+
end
|
37
|
+
Spree::Config.searcher_class = SpreeSunspot::Search
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
module SpreeSunspot
|
2
|
+
module Filter
|
3
|
+
class Filter
|
4
|
+
include ActionView::Helpers::NumberHelper
|
5
|
+
attr_accessor :search_param
|
6
|
+
attr_accessor :display_name
|
7
|
+
attr_accessor :values
|
8
|
+
attr_accessor :param_type
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@values = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def values(&blk)
|
15
|
+
@values = yield if block_given?
|
16
|
+
@values
|
17
|
+
end
|
18
|
+
|
19
|
+
def display?
|
20
|
+
!values.empty?
|
21
|
+
end
|
22
|
+
|
23
|
+
def search_param
|
24
|
+
@search_param.to_sym
|
25
|
+
end
|
26
|
+
|
27
|
+
def display_param
|
28
|
+
display_name.gsub(' ', '').underscore
|
29
|
+
end
|
30
|
+
|
31
|
+
def html_values
|
32
|
+
case param_type.to_s
|
33
|
+
when "Range"
|
34
|
+
values.collect do |range|
|
35
|
+
if range.first == 0
|
36
|
+
{ :display => "Under #{number_to_currency(range.last, :precision => 0)}", :value => "#{range.first},#{range.last}" }
|
37
|
+
elsif range.last == SpreeSunspot::Setup::IGNORE_MAX
|
38
|
+
{ :display => "#{number_to_currency(range.first, :precision => 0)}+", :value => "#{range.first},*" }
|
39
|
+
else
|
40
|
+
{ :display => "#{number_to_currency(range.first, :precision => 0)} - #{number_to_currency(range.last, :precision => 0)}",
|
41
|
+
:value => "#{range.first},#{range.last}" }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
else
|
45
|
+
values.collect do |value|
|
46
|
+
{ :display => value, :value => value }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def finalize!
|
52
|
+
raise ArgumentError.new("search_param is nil") if search_param.nil?
|
53
|
+
raise ArgumentError.new("display_name is nil") if display_name.nil?
|
54
|
+
@param_type ||= values[0].class unless values.empty?
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class Condition
|
59
|
+
attr_accessor :value
|
60
|
+
attr_accessor :condition_type
|
61
|
+
attr_accessor :source
|
62
|
+
|
63
|
+
SPLIT_CHAR = ','
|
64
|
+
GREATER_THAN = 1
|
65
|
+
BETWEEN = 2
|
66
|
+
EQUAL = 3
|
67
|
+
|
68
|
+
def multiple?
|
69
|
+
value.kindof?(Array)
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize(source, pcondition)
|
73
|
+
@source = source
|
74
|
+
range = pcondition.split(SPLIT_CHAR)
|
75
|
+
if range.size > 1
|
76
|
+
if range[1] == '*'
|
77
|
+
@value = range[0].to_f
|
78
|
+
@condition_type = GREATER_THAN
|
79
|
+
else
|
80
|
+
@value = Range.new(range[0].to_f, range[1].to_f)
|
81
|
+
@condition_type = BETWEEN
|
82
|
+
end
|
83
|
+
else
|
84
|
+
@value = pcondition
|
85
|
+
@condition_type = EQUAL
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def to_param
|
90
|
+
case condition_type
|
91
|
+
when GREATER_THAN
|
92
|
+
"#{value.to_i.to_s}#{SPLIT_CHAR}*"
|
93
|
+
when BETWEEN
|
94
|
+
"#{value.first.to_i.to_s}#{SPLIT_CHAR}#{value.last.to_i.to_s}"
|
95
|
+
when EQUAL
|
96
|
+
value.to_s
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def build_search_query(query)
|
101
|
+
case condition_type
|
102
|
+
when GREATER_THAN
|
103
|
+
query.with(source.search_param).greater_than(value)
|
104
|
+
when BETWEEN
|
105
|
+
query.with(source.search_param).between(value)
|
106
|
+
when EQUAL
|
107
|
+
query.with(source.search_param, value)
|
108
|
+
end
|
109
|
+
query
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class Param
|
114
|
+
attr_accessor :source
|
115
|
+
attr_accessor :conditions
|
116
|
+
|
117
|
+
SPLIT_CHAR = ';'
|
118
|
+
|
119
|
+
def initialize(source, pcondition)
|
120
|
+
@source = source
|
121
|
+
|
122
|
+
pconditions = pcondition.split(SPLIT_CHAR)
|
123
|
+
this = self
|
124
|
+
@conditions = pconditions.map{|p| SpreeSunspot::Filter::Condition.new(this, p)}
|
125
|
+
end
|
126
|
+
|
127
|
+
def build_search_query(search)
|
128
|
+
search.build do |query|
|
129
|
+
if @conditions.size > 0
|
130
|
+
query.any_of do |query|
|
131
|
+
@conditions.each do |condition|
|
132
|
+
condition.build_search_query(query)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
else
|
136
|
+
conditions[0].build_search_query(query)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
search
|
140
|
+
end
|
141
|
+
|
142
|
+
def to_param
|
143
|
+
value = @conditions.collect{|condition| condition.to_param}.join(SPLIT_CHAR)
|
144
|
+
"#{display_name.downcase}#{SpreeSunspot::Filter::Query::PARAM_SPLIT_CHAR}value"
|
145
|
+
end
|
146
|
+
|
147
|
+
def method_missing(method, *args)
|
148
|
+
if source.respond_to?(method)
|
149
|
+
source.send(method, *args)
|
150
|
+
else
|
151
|
+
super
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def has_filter?(filter, value)
|
156
|
+
@source == filter and @conditions.select{|c| c.to_param == value}.any?
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
class Query
|
161
|
+
attr_accessor :params
|
162
|
+
SPLIT_CHAR = '|'
|
163
|
+
PARAM_SPLIT_CHAR = '='
|
164
|
+
def initialize(query)
|
165
|
+
unless query.nil?
|
166
|
+
qparams = query.split(SPLIT_CHAR)
|
167
|
+
@params = qparams.map do |qp|
|
168
|
+
display_name, values = qp.split(PARAM_SPLIT_CHAR)
|
169
|
+
source = SpreeSunspot::Setup.filters.filter_for(display_name)
|
170
|
+
SpreeSunspot::Filter::Param.new(source, values) unless values.nil?
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def build_search(search)
|
176
|
+
@params.each{|p| p.build_search_query(search) }
|
177
|
+
search
|
178
|
+
end
|
179
|
+
|
180
|
+
def build_url
|
181
|
+
@params.collect{|p| p.to_param}.join(SPLIT_CHAR)
|
182
|
+
end
|
183
|
+
|
184
|
+
def has_filter?(filter, value)
|
185
|
+
@params.select{|p| p.has_filter?(filter, value)}.any?
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'spree_sunspot/search'
|
2
|
+
|
3
|
+
module SpreeSunspot
|
4
|
+
module FilterSupport
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def filter_support(options = {})
|
11
|
+
additional_params = options[:additional_params_method]
|
12
|
+
class_eval <<-EOV
|
13
|
+
include SpreeSunspot::FilterSupport::InstanceMethods
|
14
|
+
include SpreeSunspot::FilterSupport::Helpers
|
15
|
+
helper_method :render_filter
|
16
|
+
EOV
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module InstanceMethods
|
21
|
+
def filter
|
22
|
+
if params[:id]
|
23
|
+
taxon = Taxon.find_by_linkname(params[:id])
|
24
|
+
if taxon
|
25
|
+
params.merge!(:taxon => taxon)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
if search_for_products
|
29
|
+
respond_with(@products)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def search_for_products(scope=nil)
|
34
|
+
if (params[:s].nil? or params[:s].empty?) and !params[:source_url].nil?
|
35
|
+
redirect_to params[:source_url]
|
36
|
+
return false
|
37
|
+
end
|
38
|
+
@searcher = Spree::Config.searcher_class.new(params)
|
39
|
+
if scope.nil?
|
40
|
+
@products = @searcher.retrieve_products
|
41
|
+
else
|
42
|
+
@products = @searcher.retrieve_products(scope)
|
43
|
+
end
|
44
|
+
return true
|
45
|
+
end
|
46
|
+
|
47
|
+
def search_for_similar_products(product, *field_names)
|
48
|
+
@searcher = Spree::Config.searcher_class.new(params)
|
49
|
+
@similar_products = @searcher.similar_products(product, *field_names)
|
50
|
+
end
|
51
|
+
|
52
|
+
def filter_url_options
|
53
|
+
object = instance_variable_get('@'+controller_name.singularize)
|
54
|
+
if object
|
55
|
+
case controller_name
|
56
|
+
when "products"
|
57
|
+
hash_for_product_path(object)
|
58
|
+
when "taxons"
|
59
|
+
hash_for_taxon_short_path(object)
|
60
|
+
end
|
61
|
+
else
|
62
|
+
{}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
module Helpers
|
68
|
+
def render_filter
|
69
|
+
filter_params = SpreeSunspot::Setup.filters.filters
|
70
|
+
render :partial => 'shared/filter', :locals => { :filter_params => filter_params }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
ActionController::Base.class_eval do
|
77
|
+
include SpreeSunspot::FilterSupport
|
78
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module SpreeSunspot
|
2
|
+
class Filters
|
3
|
+
attr_accessor :filters
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@filters = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def add(&blk)
|
10
|
+
filter = SpreeSunspot::Filter::Filter.new
|
11
|
+
yield filter
|
12
|
+
filter.finalize!
|
13
|
+
filters << filter
|
14
|
+
end
|
15
|
+
|
16
|
+
def filter_for(display_name)
|
17
|
+
@filters.select{|f| f.display_name == display_name or f.display_param == display_name }.first
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(method, *args)
|
21
|
+
if @filters.respond_to?(method)
|
22
|
+
@filters.send(method, *args)
|
23
|
+
else
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'spree/search/base'
|
2
|
+
require 'spree_sunspot/filter/filter'
|
3
|
+
|
4
|
+
module SpreeSunspot
|
5
|
+
class Search < Spree::Search::Base
|
6
|
+
def query
|
7
|
+
@filter_query
|
8
|
+
end
|
9
|
+
|
10
|
+
def retrieve_products(*args)
|
11
|
+
base_scope = get_base_scope
|
12
|
+
if args
|
13
|
+
args.each do |additional_scope|
|
14
|
+
case additional_scope
|
15
|
+
when Hash
|
16
|
+
scope_method = additional_scope.keys.first
|
17
|
+
scope_values = additional_scope[scope_method]
|
18
|
+
base_scope = base_scope.send(scope_method.to_sym, *scope_values)
|
19
|
+
else
|
20
|
+
base_scope = base_scope.send(additional_scope.to_sym)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
@products_scope = @product_group.apply_on(base_scope)
|
25
|
+
curr_page = manage_pagination && keywords ? 1 : page
|
26
|
+
|
27
|
+
@products = @products_scope.includes([:images, :master]).page(curr_page).per(per_page)
|
28
|
+
end
|
29
|
+
|
30
|
+
def similar_products(product, *field_names)
|
31
|
+
products_search = Sunspot.more_like_this(product) do
|
32
|
+
fields *field_names
|
33
|
+
boost_by_relevance true
|
34
|
+
paginate :per_page => total_similar_products * 4, :page => 1
|
35
|
+
end
|
36
|
+
|
37
|
+
# get active, in-stock products only.
|
38
|
+
base_scope = get_common_base_scope
|
39
|
+
hits = []
|
40
|
+
if products_search.total > 0
|
41
|
+
hits = products_search.hits.collect{|hit| hit.primary_key.to_i}
|
42
|
+
base_scope = base_scope.where ["#{Product.table_name}.id in (?)", hits]
|
43
|
+
else
|
44
|
+
base_scope = base_scope.where ["#{Product.table_name}.id = -1"]
|
45
|
+
end
|
46
|
+
products_scope = @product_group.apply_on(base_scope)
|
47
|
+
products_results = products_scope.includes([:images, :master]).page(1)
|
48
|
+
|
49
|
+
# return top N most-relevant products (i.e. in the same order returned by more_like_this)
|
50
|
+
@similar_products = products_results.sort_by{ |p| hits.find_index(p.id) }.shift(total_similar_products)
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
def get_base_scope
|
55
|
+
base_scope = get_common_base_scope
|
56
|
+
base_scope = base_scope.in_taxon(taxon) unless taxon.blank?
|
57
|
+
base_scope = get_products_conditions_for(base_scope, keywords) unless keywords.blank?
|
58
|
+
|
59
|
+
# TODO order by master price if is preferred
|
60
|
+
#base_scope = base_scope.ascend_by_master_price
|
61
|
+
base_scope
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_products_conditions_for(base_scope, query)
|
65
|
+
@search = Sunspot.new_search(Product) do |q|
|
66
|
+
q.keywords(query) unless query.blank?
|
67
|
+
# There is no option to say don't paginate.
|
68
|
+
#q.paginate(:page => 1, :per_page => 1000000)
|
69
|
+
end
|
70
|
+
|
71
|
+
if !@properties[:filters].blank?
|
72
|
+
@filter_query = SpreeSunspot::Filter::Query.new(@properties[:filters])
|
73
|
+
@search = @filter_query.build_search(@search)
|
74
|
+
end
|
75
|
+
@search.execute
|
76
|
+
if @search.total > 0
|
77
|
+
hits = @search.hits.collect{|hit| hit.primary_key.to_i}
|
78
|
+
base_scope = base_scope.where ["#{Product.table_name}.id in (?)", hits]
|
79
|
+
else
|
80
|
+
base_scope = base_scope.where ["#{Product.table_name}.id = -1"]
|
81
|
+
end
|
82
|
+
base_scope
|
83
|
+
end
|
84
|
+
|
85
|
+
def prepare(params)
|
86
|
+
super
|
87
|
+
@properties[:filters] = params[:s] || params['s'] || []
|
88
|
+
@properties[:total_similar_products] = params[:total_similar_products].to_i > 0 ? params[:total_similar_products].to_i : Spree::Config[:total_similar_products]
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
def get_common_base_scope
|
93
|
+
base_scope = @cached_product_group ? @cached_product_group.products.active : Product.active
|
94
|
+
base_scope = base_scope.on_hand unless Spree::Config[:show_zero_stock_products]
|
95
|
+
base_scope = base_scope.group_by_products_id if @product_group.product_scopes.size > 1
|
96
|
+
base_scope
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'sunspot_rails'
|
2
|
+
require 'spree_sunspot/filters'
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
module SpreeSunspot
|
6
|
+
class Setup
|
7
|
+
include Singleton
|
8
|
+
IGNORE_MAX = 1000000000
|
9
|
+
@@filters = SpreeSunspot::Filters.new
|
10
|
+
|
11
|
+
@@configuration = nil
|
12
|
+
def self.configure(&blk)
|
13
|
+
@@configuration = blk
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.configuration
|
17
|
+
@@configuration
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.filters(&blk)
|
21
|
+
yield @@filters if block_given?
|
22
|
+
@@filters
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: spree_sunspot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.70.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Damiano Giacomello
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-19 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: spree_core
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: sunspot_rails
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: sunspot_solr
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: progress_bar
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description:
|
79
|
+
email: damiano.giacomello@diginess.it
|
80
|
+
executables: []
|
81
|
+
extensions: []
|
82
|
+
extra_rdoc_files: []
|
83
|
+
files:
|
84
|
+
- README.md
|
85
|
+
- LICENSE
|
86
|
+
- lib/generators/spree_sunspot/install/install_generator.rb
|
87
|
+
- lib/generators/spree_sunspot/install/templates/spree_sunspot.rb
|
88
|
+
- lib/spree_sunspot/engine.rb
|
89
|
+
- lib/spree_sunspot/filter/filter.rb
|
90
|
+
- lib/spree_sunspot/filter_support.rb
|
91
|
+
- lib/spree_sunspot/filters.rb
|
92
|
+
- lib/spree_sunspot/search.rb
|
93
|
+
- lib/spree_sunspot/setup.rb
|
94
|
+
- lib/spree_sunspot.rb
|
95
|
+
- lib/tasks/spree_sunspot.rake
|
96
|
+
- app/assets/images/store/checkbox.png
|
97
|
+
- app/assets/javascripts/admin/spree_sunspot.js
|
98
|
+
- app/assets/javascripts/store/filters.js
|
99
|
+
- app/assets/javascripts/store/spree_sunspot.js
|
100
|
+
- app/assets/stylesheets/admin/spree_sunspot.css
|
101
|
+
- app/assets/stylesheets/store/filters.css.erb
|
102
|
+
- app/assets/stylesheets/store/spree_sunspot.css
|
103
|
+
- app/models/app_configuration_decorator.rb
|
104
|
+
- app/models/product_decorator.rb
|
105
|
+
- app/views/base/filter.html.erb
|
106
|
+
- app/views/home/filter.html.erb
|
107
|
+
- app/views/shared/_filter.html.erb
|
108
|
+
- app/views/shared/_search_results.html.erb
|
109
|
+
- config/locales/en.yml
|
110
|
+
- config/locales/it.yml
|
111
|
+
- config/routes.rb
|
112
|
+
homepage:
|
113
|
+
licenses: []
|
114
|
+
post_install_message:
|
115
|
+
rdoc_options: []
|
116
|
+
require_paths:
|
117
|
+
- lib
|
118
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
119
|
+
none: false
|
120
|
+
requirements:
|
121
|
+
- - ! '>='
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: 1.8.7
|
124
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
|
+
none: false
|
126
|
+
requirements:
|
127
|
+
- - ! '>='
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
requirements:
|
131
|
+
- none
|
132
|
+
rubyforge_project:
|
133
|
+
rubygems_version: 1.8.22
|
134
|
+
signing_key:
|
135
|
+
specification_version: 3
|
136
|
+
summary: Use Sunspot as search engine in Spree applications
|
137
|
+
test_files: []
|