spree_sunspot 0.70.0

Sign up to get free protection for your applications and to get access to all the features.
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: []