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 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
@@ -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,2 @@
1
+ //= require store/spree_core
2
+ //= require store/filters
@@ -0,0 +1,3 @@
1
+ /*
2
+ *= require admin/spree_core
3
+ */
@@ -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,3 @@
1
+ /*
2
+ *= require store/filters
3
+ */
@@ -0,0 +1,3 @@
1
+ AppConfiguration.class_eval do
2
+ preference :total_similar_products, :integer, :default => 10
3
+ end
@@ -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,5 @@
1
+ Rails.application.routes.draw do
2
+ match "/q" => "home#filter"
3
+ match "/products/q" => "products#filter"
4
+ match "/:id/q" => "taxons#filter", :constraints => { :id => Regexp.new('(' + Taxon.all.collect { |b| b.linkname rescue b.permalink }.join('|') + ')') }
5
+ end
@@ -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,2 @@
1
+ require 'spree_core'
2
+ require 'spree_sunspot/engine'
@@ -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
@@ -0,0 +1,8 @@
1
+ namespace :spree_sunspot do
2
+ desc "Reindex all products"
3
+ task :reindex => :environment do
4
+ Product.remove_all_from_index!
5
+ Product.reindex
6
+ Sunspot.commit
7
+ end
8
+ 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: []