spree_solr_search 0.30.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ solr
2
+ log/*.log
3
+ tmp/pids/*
data/README.md ADDED
@@ -0,0 +1,34 @@
1
+ Solr Search
2
+ ===========
3
+
4
+ ### Installation
5
+
6
+ 1. Add `gem "spree_solr_search"` to your Gemfile
7
+ 1. Run `bundle install`
8
+ 1. Run `rails g spree_solr_search:install`
9
+
10
+ **NOTE:** Master branch works only with Spree 0.30.x.
11
+ If you want use this extension with Spree 0.10.x or 0.11.x, then you should use spree-0-11-stable branch
12
+
13
+ ### Usage
14
+
15
+ To perform the indexing:
16
+
17
+ rake solr:reindex BATCH=500
18
+
19
+
20
+ To start Solr demo-server:
21
+
22
+ rake solr:start SOLR_PATH="/home/roman/www/jetty-solr"
23
+
24
+ To stop Solr demo-server:
25
+
26
+ rake solr:stop
27
+
28
+ To configure production Solr server:
29
+
30
+ edit RAILS_ROOT/config/solr.yml
31
+
32
+ P.S. For development recommended use [jetty-solr](http://github.com/dcrec1/jetty-solr) server.
33
+
34
+
data/Rakefile ADDED
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+ require 'rubygems'
3
+ begin
4
+ require 'jeweler'
5
+ rescue LoadError
6
+ puts "Jeweler not available. Install it with: sudo gem install jeweler"
7
+ exit 1
8
+ end
9
+ #gem 'rdoc', '= 2.2'
10
+ #require 'rdoc'
11
+ require 'rake'
12
+ require 'rake/testtask'
13
+ #require 'rake/rdoctask'
14
+ require 'rake/packagetask'
15
+ require 'rake/gempackagetask'
16
+
17
+ Jeweler::Tasks.new do |s|
18
+ s.name = "spree_solr_search"
19
+ s.summary = "Provides search via Apache Solr for a Spree store."
20
+ s.description = s.summary
21
+ s.email = "roman@railsdog.com"
22
+ s.homepage = "http://github.com/romul/spree-solr-search"
23
+ s.authors = ["Roman Smirnov"]
24
+ # s.add_dependency 'spree_core', ['>= 0.30.0.beta1']
25
+ s.add_dependency 'acts_as_solr_reloaded', ['>= 1.4.0']
26
+ s.has_rdoc = false
27
+ #s.extra_rdoc_files = [ "README.rdoc"]
28
+ #s.rdoc_options = ["--main", "README.rdoc", "--inline-source", "--line-numbers"]
29
+ #s.test_files = Dir['test/**/*.{yml,rb}']
30
+ end
31
+ Jeweler::GemcutterTasks.new
32
+
33
+ desc "Default Task"
34
+ task :default => [ :test ]
35
+
36
+ # Run the unit tests
37
+ Rake::TestTask.new { |t|
38
+ t.libs << "test"
39
+ t.pattern = 'test/**/*_test.rb'
40
+ t.warning = true
41
+ }
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.30.0.beta1
@@ -0,0 +1,9 @@
1
+ Spree::BaseController.class_eval do
2
+ helper :solr
3
+ before_filter :set_searcher
4
+ RAILS_ROOT = Rails.root.to_s
5
+ private
6
+ def set_searcher
7
+ Spree::Config.searcher = Spree::Search::Solr.new
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ module SolrHelper
2
+ def link_to_facet(name, value, count)
3
+ facets_hash = params[:facets]
4
+ facets_hash = {} unless facets_hash.is_a?(Hash)
5
+ facets_hash = facets_hash.merge({name => ["\"#{value}\"", facets_hash[name]].join(',')})
6
+ link_to("#{value} (#{count})", url_for(request.params.merge({:facets => facets_hash})))
7
+ end
8
+ end
@@ -0,0 +1,66 @@
1
+ Product.class_eval do
2
+ acts_as_solr :fields => [:name, :description, :is_active, {:price => :float},
3
+ :taxon_ids, :price_range, :taxon_names,
4
+ :brand_property, :color_option, :size_option],
5
+ :facets=>[:price_range, :taxon_names,
6
+ :brand_property, :color_option, :size_option]
7
+
8
+ def taxon_ids
9
+ taxons.map(&:id)
10
+ end
11
+
12
+ def is_active
13
+ !deleted_at &&
14
+ (available_on <= Time.zone.now) &&
15
+ (Spree::Config[:allow_backorders] || count_on_hand > 0)
16
+ end
17
+
18
+ private
19
+
20
+ def taxon_names
21
+ taxons.map(&:name)
22
+ end
23
+
24
+ def price_range
25
+ price_ranges = YAML.load(Spree::Config[:product_price_ranges])
26
+ case price
27
+ when 0..25
28
+ price_ranges[0]
29
+ when 25..50
30
+ price_ranges[1]
31
+ when 50..100
32
+ price_ranges[2]
33
+ when 100..200
34
+ price_ranges[3]
35
+ else
36
+ price_ranges[4]
37
+ end
38
+ end
39
+
40
+ def brand_property
41
+ pp = ProductProperty.first(:joins => :property,
42
+ :conditions => {:product_id => self.id, :properties => {:name => 'brand'}})
43
+ pp ? pp.value : ''
44
+ end
45
+
46
+ def color_option
47
+ get_option_values('color')
48
+ end
49
+
50
+ def size_option
51
+ get_option_values('size')
52
+ end
53
+
54
+ def get_option_values(option_name)
55
+ sql = <<-eos
56
+ SELECT DISTINCT ov.id, ov.presentation
57
+ FROM option_values AS ov
58
+ LEFT JOIN option_types AS ot ON (ov.option_type_id = ot.id)
59
+ LEFT JOIN option_values_variants AS ovv ON (ovv.option_value_id = ov.id)
60
+ LEFT JOIN variants AS v ON (ovv.variant_id = v.id)
61
+ LEFT JOIN products AS p ON (v.product_id = p.id)
62
+ WHERE (ot.name = '#{option_name}' AND p.id = #{self.id});
63
+ eos
64
+ OptionValue.find_by_sql(sql).map(&:presentation)
65
+ end
66
+ end
@@ -0,0 +1,18 @@
1
+ <%
2
+ facets = Spree::Config.searcher.facets || []
3
+ taxon_names = @taxon ? @taxon.self_and_descendants.map(&:name) : []
4
+ for facet in facets
5
+ options = facet.options
6
+ options = options.sort{|x,y| y.count <=> x.count} unless facet.name == "price_range"
7
+ unless options.empty? %>
8
+ <h4><%= t "#{facet.name}_facet" %></h4>
9
+ <ul><%
10
+ for option in options %>
11
+ <li>
12
+ <%= link_to_facet(facet.name, option.name, option.count) %>
13
+ </li><%
14
+ end %>
15
+ </ul><%
16
+ end
17
+ end
18
+ %>
@@ -0,0 +1,6 @@
1
+ <% if suggestion = Spree::Config.searcher.suggest %>
2
+ <p>
3
+ <%= t(:did_you_mean, :default => "Did you mean") %>
4
+ <%= link_to h(suggestion), url_for(request.params.merge({:keywords => suggestion})) %>?
5
+ </p>
6
+ <% end %>
@@ -0,0 +1,6 @@
1
+ en:
2
+ taxon_names_facet: "Taxon"
3
+ price_range_facet: "Price"
4
+ brand_property_facet: "Brand"
5
+ color_option_facet: "Color"
6
+ size_option_facet: "Size"
@@ -0,0 +1,6 @@
1
+ ru-RU:
2
+ taxon_names_facet: "Категория"
3
+ price_range_facet: "Цена"
4
+ brand_property_facet: "Бренд"
5
+ color_option_facet: "Цвет"
6
+ size_option_facet: "Размер"
@@ -0,0 +1,6 @@
1
+ ru:
2
+ taxon_names_facet: "Категория"
3
+ price_range_facet: "Цена"
4
+ brand_property_facet: "Бренд"
5
+ color_option_facet: "Цвет"
6
+ size_option_facet: "Размер"
@@ -0,0 +1,17 @@
1
+ module SpreeSolrSearch
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ source_root File.expand_path("../../templates", __FILE__)
5
+
6
+ desc "Configures your Rails application for use with spree_solr_search."
7
+
8
+ def copy_config
9
+ template "solr.yml", "config/solr.yml"
10
+ end
11
+
12
+ # def show_readme
13
+ # readme "README" if behavior == :invoke
14
+ # end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ # Config file for the acts_as_solr plugin.
2
+ #
3
+ # If you change the host or port number here, make sure you update
4
+ # them in your Solr config file
5
+
6
+ development:
7
+ url: http://127.0.0.1:8982/solr
8
+
9
+ production:
10
+ url: http://127.0.0.1:8983/solr
11
+ jvm_options: -server -d64 -Xmx1024M -Xms64M
12
+
13
+ test:
14
+ url: http://127.0.0.1:8981/solr
15
+
@@ -0,0 +1,19 @@
1
+ # By Henrik Nyh <http://henrik.nyh.se> 2007-06-15.
2
+ # Free to modify and redistribute with credit.
3
+
4
+ # Adds a find_all_by_solr method to acts_as_solr models to enable
5
+ # will_paginate for acts_as_solr search results.
6
+
7
+ module ActsAsSolr
8
+ module PaginationExtension
9
+
10
+ def find_all_by_solr(query, options)
11
+ Spree::Config.searcher.products.slice(options[:offset], options[:limit])
12
+ end
13
+
14
+ end
15
+ end
16
+
17
+ module ActsAsSolr::ClassMethods
18
+ include ActsAsSolr::PaginationExtension
19
+ end
@@ -0,0 +1,92 @@
1
+ module Spree::Search
2
+ class Solr < Spree::Search::Base
3
+ # method should return hash with conditions {:conditions=> "..."} for Product model
4
+ def get_products_conditions_for(query)
5
+ facets = {
6
+ :fields => [:price_range, :taxon_names, :brand_property, :color_option, :size_option],
7
+ :browse => @properties[:facets_hash].map{|k,v| "#{k}:#{v}"},
8
+ :zeros => false
9
+ }
10
+ search_options = {:facets => facets, :limit => 25000, :lazy => true}
11
+ if order_by_price
12
+ search_options.merge!(:order => (order_by_price == 'descend') ? "price desc" : "price asc")
13
+ end
14
+ full_query = query + " AND is_active:(true)"
15
+ if taxon
16
+ taxons_query = taxon.self_and_descendants.map{|t| "taxon_ids:(#{t.id})"}.join(" OR ")
17
+ full_query += " AND (#{taxons_query})"
18
+ end
19
+
20
+ result = Product.find_by_solr(full_query, search_options)
21
+ @properties[:products] = result.records
22
+ count = result.records.size
23
+
24
+ products = Product.paginate(query, :finder => 'find_all_by_solr',
25
+ :page => page, :per_page => per_page, :total_entries => count)
26
+
27
+ @properties[:products] = products
28
+ @properties[:suggest] = nil
29
+ begin
30
+ if suggest = result.suggest
31
+ suggest.sub!(/\sAND.*/, '')
32
+ @properties[:suggest] = suggest if suggest != query
33
+ end
34
+ rescue
35
+ end
36
+
37
+ @properties[:facets] = parse_facets_hash(result.facets)
38
+ {:conditions=> ["products.id IN (?)", products.map(&:id)]}
39
+ end
40
+
41
+ def prepare(params)
42
+ @properties[:facets_hash] = params[:facets] || {}
43
+ @properties[:taxon] = params[:taxon].blank? ? nil : Taxon.find(params[:taxon])
44
+ @properties[:per_page] = params[:per_page]
45
+ @properties[:page] = params[:page]
46
+ @properties[:manage_pagination] = true
47
+ @properties[:order_by_price] = params[:order_by_price]
48
+ end
49
+
50
+ private
51
+
52
+ def parse_facets_hash(facets_hash = {"facet_fields" => {}})
53
+ facets = []
54
+ price_ranges = YAML::load(Spree::Config[:product_price_ranges])
55
+ facets_hash["facet_fields"].each do |name, options|
56
+ next if options.size <= 1
57
+ facet = Facet.new(name.sub('_facet', ''))
58
+ if name == 'price_range_facet'
59
+ price_ranges.each do |price_range|
60
+ count = options[price_range]
61
+ facet.options << FacetOption.new(price_range, count) if count
62
+ end
63
+ else
64
+ options.each do |value, count|
65
+ facet.options << FacetOption.new(value, count)
66
+ end
67
+ end
68
+ facets << facet
69
+ end
70
+ facets
71
+ end
72
+ end
73
+
74
+
75
+ class Facet
76
+ attr_accessor :options
77
+ attr_accessor :name
78
+ def initialize(name, options = [])
79
+ self.name = name
80
+ self.options = options
81
+ end
82
+ end
83
+
84
+ class FacetOption
85
+ attr_accessor :name
86
+ attr_accessor :count
87
+ def initialize(name, count)
88
+ self.name = name
89
+ self.count = count
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,21 @@
1
+ require 'spree_core'
2
+ require 'spree_solr_search_hooks'
3
+
4
+ module SpreeSolrSearch
5
+ class Engine < Rails::Engine
6
+ def self.activate
7
+ require 'acts_as_solr'
8
+ require 'solr_pagination'
9
+ ENV['RAILS_ENV'] = Rails.env
10
+
11
+ Spree::Config.set(:product_price_ranges =>
12
+ ["Under $25", "$25 to $50", "$50 to $100", "$100 to $200", "$200 and above"])
13
+
14
+ Dir.glob(File.join(File.dirname(__FILE__), "../app/**/*_decorator*.rb")) do |c|
15
+ Rails.env == "production" ? require(c) : load(c)
16
+ end
17
+ end
18
+ config.to_prepare &method(:activate).to_proc
19
+ config.autoload_paths += %W(#{config.root}/lib)
20
+ end
21
+ end
@@ -0,0 +1,6 @@
1
+ class SpreeSolrSearchHooks < Spree::ThemeSupport::HookListener
2
+
3
+ insert_before :search_results, 'products/facets'
4
+ insert_before :search_results, 'products/suggestion'
5
+
6
+ end
@@ -0,0 +1,10 @@
1
+ begin
2
+ ENV['ONLY'] = "Product"
3
+ SOLR_PATH = ENV['SOLR_PATH']
4
+ RAILS_DEFAULT_LOGGER = Logger.new(Rails.root.join("log", Rails.env + ".log"))
5
+ RAILS_ROOT = Rails.root.to_s
6
+ require 'acts_as_solr_reloaded'
7
+ load 'tasks/solr.rake'
8
+ rescue LoadError
9
+ puts "WARNING: acts_as_solr_reloaded gem appears to be unavailable. Please install with bundle install."
10
+ end
@@ -0,0 +1,59 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{spree_solr_search}
8
+ s.version = "0.30.0.beta1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Roman Smirnov"]
12
+ s.date = %q{2010-08-26}
13
+ s.description = %q{Provides search via Apache Solr for a Spree store.}
14
+ s.email = %q{roman@railsdog.com}
15
+ s.extra_rdoc_files = [
16
+ "README.md"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "README.md",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "app/controllers/spree/base_controller_decorator.rb",
24
+ "app/helpers/solr_helper.rb",
25
+ "app/models/product_decorator.rb",
26
+ "app/views/products/_facets.html.erb",
27
+ "app/views/products/_suggestion.html.erb",
28
+ "config/locales/en.yml",
29
+ "config/locales/ru-RU.yml",
30
+ "config/locales/ru.yml",
31
+ "lib/generators/spree_solr_search/install_generator.rb",
32
+ "lib/generators/templates/solr.yml",
33
+ "lib/solr_pagination.rb",
34
+ "lib/spree/search/solr.rb",
35
+ "lib/spree_solr_search.rb",
36
+ "lib/spree_solr_search_hooks.rb",
37
+ "lib/tasks/acts_as_solr.rake",
38
+ "spree_solr_search.gemspec"
39
+ ]
40
+ s.homepage = %q{http://github.com/romul/spree-solr-search}
41
+ s.rdoc_options = ["--charset=UTF-8"]
42
+ s.require_paths = ["lib"]
43
+ s.rubygems_version = %q{1.3.6}
44
+ s.summary = %q{Provides search via Apache Solr for a Spree store.}
45
+
46
+ if s.respond_to? :specification_version then
47
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
48
+ s.specification_version = 3
49
+
50
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
51
+ s.add_runtime_dependency(%q<acts_as_solr_reloaded>, [">= 1.4.0"])
52
+ else
53
+ s.add_dependency(%q<acts_as_solr_reloaded>, [">= 1.4.0"])
54
+ end
55
+ else
56
+ s.add_dependency(%q<acts_as_solr_reloaded>, [">= 1.4.0"])
57
+ end
58
+ end
59
+
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spree_solr_search
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: true
5
+ segments:
6
+ - 0
7
+ - 30
8
+ - 0
9
+ - beta1
10
+ version: 0.30.0.beta1
11
+ platform: ruby
12
+ authors:
13
+ - Roman Smirnov
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-08-26 00:00:00 +04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: acts_as_solr_reloaded
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 1
30
+ - 4
31
+ - 0
32
+ version: 1.4.0
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ description: Provides search via Apache Solr for a Spree store.
36
+ email: roman@railsdog.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.md
43
+ files:
44
+ - .gitignore
45
+ - README.md
46
+ - Rakefile
47
+ - VERSION
48
+ - app/controllers/spree/base_controller_decorator.rb
49
+ - app/helpers/solr_helper.rb
50
+ - app/models/product_decorator.rb
51
+ - app/views/products/_facets.html.erb
52
+ - app/views/products/_suggestion.html.erb
53
+ - config/locales/en.yml
54
+ - config/locales/ru-RU.yml
55
+ - config/locales/ru.yml
56
+ - lib/generators/spree_solr_search/install_generator.rb
57
+ - lib/generators/templates/solr.yml
58
+ - lib/solr_pagination.rb
59
+ - lib/spree/search/solr.rb
60
+ - lib/spree_solr_search.rb
61
+ - lib/spree_solr_search_hooks.rb
62
+ - lib/tasks/acts_as_solr.rake
63
+ - spree_solr_search.gemspec
64
+ has_rdoc: true
65
+ homepage: http://github.com/romul/spree-solr-search
66
+ licenses: []
67
+
68
+ post_install_message:
69
+ rdoc_options:
70
+ - --charset=UTF-8
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ segments:
78
+ - 0
79
+ version: "0"
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">"
83
+ - !ruby/object:Gem::Version
84
+ segments:
85
+ - 1
86
+ - 3
87
+ - 1
88
+ version: 1.3.1
89
+ requirements: []
90
+
91
+ rubyforge_project:
92
+ rubygems_version: 1.3.6
93
+ signing_key:
94
+ specification_version: 3
95
+ summary: Provides search via Apache Solr for a Spree store.
96
+ test_files: []
97
+