Permalink
Browse files

first public commit

  • Loading branch information...
0 parents commit f1481fd914c08d3aa7633226847a00eed2d7ba5d @hybridindie hybridindie committed Sep 23, 2011
23 LICENSE
@@ -0,0 +1,23 @@
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of the Rails Dog LLC nor the names of its
+ contributors may be used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,27 @@
+SpreeSunspotSearch
+==================
+
+Adds Solr search to Spree using Sunspot.
+
+
+Install
+=======
+
+I make the assumption that you have a functioning Spree store and are just extending the search capabilities with Sunspot/Solr
+
+Add spree_sunspot_search to your Gemfile and run bundler.
+
+gem 'spree_sunspot_search', git: 'git://github.com/jbrien/spree_sunspot_search.git'
+
+add the following to the Gemfile if you are not using another solr install locally for testing and development. The rake tasks for starting and stop this for development are included automatically for your use.
+
+group :test, :development do
+ gem 'sunspot_solr'
+end
+
+bundle install
+
+Install the solr.yml file from Sunspot.
+rails g sunspot_rails:install
+
+Copyright (c) 2011 John Brien Dilts, released under the New BSD License
@@ -0,0 +1,75 @@
+require 'rubygems'
+require 'rake'
+require 'rake/testtask'
+require 'rake/packagetask'
+require 'rake/gempackagetask'
+
+gemfile = File.expand_path('../spec/test_app/Gemfile', __FILE__)
+if File.exists?(gemfile) && (%w(spec cucumber).include?(ARGV.first.to_s) || ARGV.size == 0)
+ require 'bundler'
+ ENV['BUNDLE_GEMFILE'] = gemfile
+ Bundler.setup
+
+ require 'rspec'
+ require 'rspec/core/rake_task'
+ RSpec::Core::RakeTask.new
+
+ require 'cucumber/rake/task'
+ Cucumber::Rake::Task.new do |t|
+ t.cucumber_opts = %w{--format progress}
+ end
+end
+
+desc "Default Task"
+task :default => [:spec, :cucumber ]
+
+spec = eval(File.read('spree_sunspot_search.gemspec'))
+
+Rake::GemPackageTask.new(spec) do |p|
+ p.gem_spec = spec
+end
+
+desc "Release to gemcutter"
+task :release => :package do
+ require 'rake/gemcutter'
+ Rake::Gemcutter::Tasks.new(spec).define
+ Rake::Task['gem:push'].invoke
+end
+
+desc "Default Task"
+task :default => [ :spec ]
+
+desc "Regenerates a rails 3 app for testing"
+task :test_app do
+ require '../spree/lib/generators/spree/test_app_generator'
+ class SpreeSunspotSearchTestAppGenerator < Spree::Generators::TestAppGenerator
+
+ def install_gems
+ inside "test_app" do
+ run 'bundle exec rake spree_core:install'
+ run 'bundle exec rake spree_sunspot_search:install'
+ end
+ end
+
+ def migrate_db
+ run_migrations
+ end
+
+ protected
+ def full_path_for_local_gems
+ <<-gems
+gem 'spree_core', :path => \'#{File.join(File.dirname(__FILE__), "../spree/", "core")}\'
+gem 'spree_sunspot_search', :path => \'#{File.dirname(__FILE__)}\'
+ gems
+ end
+
+ end
+ SpreeSunspotSearchTestAppGenerator.start
+end
+
+namespace :test_app do
+ desc 'Rebuild test and cucumber databases'
+ task :rebuild_dbs do
+ system("cd spec/test_app && bundle exec rake db:drop db:migrate RAILS_ENV=test && rake db:drop db:migrate RAILS_ENV=cucumber")
+ end
+end
@@ -0,0 +1,9 @@
+# This file is used to designate compatibilty with different versions of Spree
+# Please see http://spreecommerce.com/documentation/extensions.html#versionfile for details
+
+# Examples
+#
+# "0.50.x" => { :branch => "master" }
+# "0.40.x" => { :tag => "v1.0.0", :version => "1.0.0" }
+
+
@@ -0,0 +1,72 @@
+Product.class_eval do
+ searchable do
+ # Boost up the name in the results
+ text :name, :boost => 2.0
+ string :product_name, :stored => true do
+ name.downcase.sub(/^(an?|the)\W+/, '')
+ end
+
+ text :description
+ boolean :is_active, :using => :is_active?
+ float :price
+
+ integer :taxon_ids, :multiple => true, :references => Taxon
+ string :taxon_name, :multiple => true do
+ taxons.map(&:name)
+ end
+
+ PRODUCT_OPTION_FACETS.each do |option|
+ string "#{option}_facet", :multiple => true do
+ get_option_values(option.to_s).map(&:presentation)
+ end
+ end
+
+ PRODUCT_PROPERTY_FACETS.each do |prop|
+ string "#{prop}_facet", :multiple => true do
+ get_product_property(prop.to_s)
+ end
+ end
+
+ if respond_to?(:stores)
+ integer :store_ids, :multiple => true, :references => Store
+ end
+
+ end
+
+ def is_active?
+ !deleted_at && available_on &&
+ (available_on <= Time.zone.now) &&
+ (Spree::Config[:allow_backorders] || count_on_hand > 0)
+ end
+
+ private
+
+ def price_range
+ max = 0
+ PRODUCT_PRICE_RANGES.each do |range, name|
+ return name if range.include?(price)
+ max = range.max if range.max > max
+ end
+ I18n.t(:price_and_above, :price => max)
+ end
+
+ def get_product_property(prop)
+ #p = Property.find_by_name(prop)
+ #ProductProperty.find(:product_id => self.id, :property_id => p.id)
+ pp = ProductProperty.first(:joins => :property, :conditions => {:product_id => self.id, :properties => {:name => prop.to_s}})
+ pp.value if pp
+ end
+
+ def get_option_values(option_name)
+ sql = <<-eos
+ SELECT DISTINCT ov.id, ov.presentation
+ FROM option_values AS ov
+ LEFT JOIN option_types AS ot ON (ov.option_type_id = ot.id)
+ LEFT JOIN option_values_variants AS ovv ON (ovv.option_value_id = ov.id)
+ LEFT JOIN variants AS v ON (ovv.variant_id = v.id)
+ LEFT JOIN products AS p ON (v.product_id = p.id)
+ WHERE (ot.name = '#{option_name}' AND p.id = #{self.id});
+ eos
+ OptionValue.find_by_sql(sql)
+ end
+end
@@ -0,0 +1,14 @@
+<%
+ facets_arr = PRODUCT_OPTION_FACETS
+ facets_arr += PRODUCT_PROPERTY_FACETS
+%>
+<% facets_arr.each do |f| %>
+ <% unless @searcher.products.facet("#{f}_facet").rows.empty? %>
+ <h4><%= t "#{f}_facet" %></h4>
+ <ul>
+ <% @searcher.products.facet("#{f}_facet").rows.each do |row| %>
+ <li><%= link_to(row.value, params.merge("#{f}_facet" => row.value)) %> (<%= row.count %>)</li>
+ <% end %>
+ </ul>
+ <% end %>
+<% end %>
@@ -0,0 +1,6 @@
+<% if suggestion = @searcher.suggest %>
+ <p>
+ <%= t(:did_you_mean, :default => "Did you mean") %>
+ <%= link_to h(suggestion), url_for(request.params.merge({:keywords => suggestion})) %>?
+ </p>
+<% end %>
@@ -0,0 +1,26 @@
+<% content_for :sidebar do %>
+ <%= hook :homepage_sidebar_navigation do %>
+ <% if "products" == @current_controller && @taxon %>
+ <%= render "shared/filters" %>
+ <% else %>
+ <%= render "shared/taxonomies" %>
+ <% end %>
+ <% end %>
+<% end %>
+
+
+<% if params[:keywords] %>
+
+ <%= hook :search_results do %>
+ <%= render "shared/products", :products => @products, :taxon => @taxon %>
+ <% end %>
+
+<% else %>
+
+ <%= hook :homepage_products do %>
+ <%= render "shared/products", :products => @products, :taxon => @taxon %>
+ <% end %>
+
+
+<% end %>
+
@@ -0,0 +1,33 @@
+<%
+ paginated_products = @searcher.products if params.key?(:keywords)
+ paginated_products ||= products
+%>
+<% if params.key?(:keywords) %>
+ <h3><%= t(:search_results, :keywords => h(params[:keywords])) %></h3>
+<% end %>
+
+
+<ul class="product-listing">
+ <% products.each_hit_with_result do |result,product| %>
+ <% if Spree::Config[:show_zero_stock_products] || product.has_stock? %>
+ <li id="product_<%= product.id %>">
+ <%= hook :products_list_item, {:product => product} do %>
+ <%= link_to small_image(product), product %>
+ <%= link_to raw(product.name + " <span class='price selling'>#{product_price(product)}</span>"), product, :class => 'info' %>
+ <% end %>
+ </li>
+ <% end %>
+ <% end %>
+</ul>
+<hr class="space" />
+
+<% if paginated_products.respond_to?(:total_pages)
+ params.delete(:search)
+ params.delete(:taxon)
+
+%><%= will_paginate(paginated_products,
+ :previous_label => "&#171; #{t('previous')}",
+ :next_label => "#{t('next')} &#187;") %>
+<% end %>
+
+<hr class="space" />
@@ -0,0 +1,14 @@
+unless defined?(PRODUCT_PRICE_RANGES)
+ PRODUCT_PRICE_RANGES = {0..25 => " Under $25", 25..50 => " $25 to $50",
+ 50..100 => " $50 to $100", 100..200 => "$100 to $200"}
+end
+# Product Options for use with Faceting
+# gets turned to #{value}_option for the facet
+unless defined?(PRODUCT_OPTION_FACETS)
+ PRODUCT_OPTION_FACETS = [:color, :size]
+end
+# Product Properties for use with Faceting
+# gets turned to #{value}_property for the facet
+unless defined?(PRODUCT_PROPERTY_FACETS)
+ PRODUCT_PROPERTY_FACETS = [:brand]
+end
@@ -0,0 +1,54 @@
+module Spree::Search
+ class SpreeSunspot < defined?(Spree::Search::MultiDomain) ? Spree::Search::MultiDomain : Spree::Search::Base
+
+ def retrieve_products
+ products = Sunspot.search([Product]) do
+ # This is a little tricky to understand
+ # - we are sending the block value as a method
+ # - Spree::Search::Base is using method_missing() to return the param values
+ PRODUCT_OPTION_FACETS.each do |option|
+ with("#{option}_facet", send(option)) if send(option)
+
+ facet("#{option}_facet")
+ end
+
+ PRODUCT_PROPERTY_FACETS.each do |prop|
+ with("#{prop}_facet", send(prop)) if send(prop)
+
+ facet("#{prop}_facet")
+ end
+
+ #PRODUCT_PRICE_RANGES.each do |range, name|
+ # with(:price).between(send(range)) if send(range)
+ #end
+
+ with(:taxon_name, taxon_name) if taxon_name
+ with(:is_active, true)
+
+ keywords(query)
+
+ paginate(:page => page, :per_page => per_page)
+
+ end
+ @properties[:products] = products
+ end
+
+ protected
+
+ def prepare(params)
+ super
+ @properties[:taxon_name] = params[:taxon] unless params[:taxon].blank?
+ @properties[:query] = params[:keywords]
+
+ PRODUCT_OPTION_FACETS.each do |option|
+ @properties[option] = params["#{option}_facet"]
+ end
+
+ PRODUCT_PROPERTY_FACETS.each do |prop|
+ @properties[prop] = params["#{prop}_facet"]
+ end
+
+ end
+
+ end
+end
@@ -0,0 +1,22 @@
+require 'spree_core'
+require 'spree_sunspot_search_hooks'
+require 'sunspot_rails'
+
+module SpreeSunspotSearch
+ class Engine < Rails::Engine
+
+ def self.activate
+ if Spree::Config.instance
+ Spree::Config.searcher_class = Spree::Search::SpreeSunspot
+ end
+
+ Dir.glob(File.join(File.dirname(__FILE__), "../app/**/*_decorator*.rb")) do |c|
+ Rails.env.production? ? require(c) : load(c)
+ end
+ end
+
+ config.to_prepare &method(:activate).to_proc
+ config.autoload_paths += %W(#{config.root}/lib)
+
+ end
+end
@@ -0,0 +1,3 @@
+class SpreeSunspotSearchHooks < Spree::ThemeSupport::HookListener
+ insert_before :search_results, 'products/facets'
+end
Oops, something went wrong.

0 comments on commit f1481fd

Please sign in to comment.