solidus_searchkick 0.1.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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +5 -0
- data/.gitignore +14 -0
- data/.travis.yml +18 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +404 -0
- data/LICENSE +26 -0
- data/README.md +78 -0
- data/Rakefile +21 -0
- data/app/assets/javascripts/spree/backend/spree_searchkick.js +2 -0
- data/app/assets/javascripts/spree/frontend/spree_searchkick.js +28 -0
- data/app/assets/javascripts/spree/frontend/typeahead.bundle.js +2451 -0
- data/app/assets/stylesheets/spree/backend/spree_searchkick.css +4 -0
- data/app/assets/stylesheets/spree/frontend/spree_searchkick.css +47 -0
- data/app/controllers/spree/products_controller_decorator.rb +7 -0
- data/app/helpers/spree/products_helper_decorator.rb +9 -0
- data/app/models/spree/order_decorator.rb +8 -0
- data/app/models/spree/product_decorator.rb +57 -0
- data/app/models/spree/property_decorator.rb +7 -0
- data/app/models/spree/taxonomy_decorator.rb +7 -0
- data/app/overrides/spree/admin/properties/_form/add_filterable_to_property_form.html.erb.deface +8 -0
- data/app/overrides/spree/admin/taxonomies/_form/add_filterable_to_taxonomy_form.html.erb.deface +6 -0
- data/app/views/spree/shared/_es_filter.html.erb +15 -0
- data/app/views/spree/shared/_filters.html.erb +12 -0
- data/bin/rails +7 -0
- data/config/locales/en.yml +5 -0
- data/config/routes.rb +6 -0
- data/db/migrate/20150819222417_add_filtrable_to_spree_taxonomies.rb +5 -0
- data/db/migrate/20151009155442_add_filterable_to_spree_property.rb +5 -0
- data/lib/generators/spree_searchkick/install/install_generator.rb +31 -0
- data/lib/solidus_searchkick.rb +2 -0
- data/lib/solidus_searchkick/engine.rb +23 -0
- data/lib/solidus_searchkick/factories.rb +6 -0
- data/lib/solidus_searchkick/version.rb +3 -0
- data/lib/spree/core/searchkick_filters.rb +50 -0
- data/lib/spree/search/searchkick.rb +58 -0
- data/solidus_searchkick.gemspec +38 -0
- data/spec/controllers/spree/products_controller_spec.rb +5 -0
- data/spec/lib/spree/search/searchkick_spec.rb +43 -0
- data/spec/models/spree/order_spec.rb +20 -0
- data/spec/models/spree/product_spec.rb +37 -0
- data/spec/models/spree/property_spec.rb +11 -0
- data/spec/models/spree/taxonomy_spec.rb +11 -0
- data/spec/routing/spree/products_routes_spec.rb +20 -0
- data/spec/spec_helper.rb +92 -0
- metadata +313 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
/*
|
2
|
+
Placeholder manifest file.
|
3
|
+
the installer will append this file to the app vendored assets here: 'vendor/assets/stylesheets/spree/frontend/all.css'
|
4
|
+
*/
|
5
|
+
.tt-query {
|
6
|
+
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
7
|
+
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
8
|
+
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
9
|
+
}
|
10
|
+
|
11
|
+
.tt-hint {
|
12
|
+
color: #999
|
13
|
+
}
|
14
|
+
|
15
|
+
.tt-menu {
|
16
|
+
width: 100%;
|
17
|
+
margin-top: 4px;
|
18
|
+
padding: 4px 0;
|
19
|
+
background-color: #fff;
|
20
|
+
border: 1px solid #ccc;
|
21
|
+
border: 1px solid rgba(0, 0, 0, 0.2);
|
22
|
+
-webkit-border-radius: 4px;
|
23
|
+
-moz-border-radius: 4px;
|
24
|
+
border-radius: 4px;
|
25
|
+
-webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
|
26
|
+
-moz-box-shadow: 0 5px 10px rgba(0,0,0,.2);
|
27
|
+
box-shadow: 0 5px 10px rgba(0,0,0,.2);
|
28
|
+
}
|
29
|
+
|
30
|
+
.tt-suggestion {
|
31
|
+
padding: 3px 20px;
|
32
|
+
line-height: 24px;
|
33
|
+
}
|
34
|
+
|
35
|
+
.tt-suggestion.tt-cursor {
|
36
|
+
color: #fff;
|
37
|
+
background-color: #0097cf;
|
38
|
+
|
39
|
+
}
|
40
|
+
|
41
|
+
.tt-suggestion p {
|
42
|
+
margin: 0;
|
43
|
+
}
|
44
|
+
|
45
|
+
.twitter-typeahead {
|
46
|
+
width: 100%;
|
47
|
+
}
|
@@ -0,0 +1,9 @@
|
|
1
|
+
Spree::ProductsHelper.module_eval do
|
2
|
+
# TODO, will need to handle this
|
3
|
+
def cache_key_for_products
|
4
|
+
count = @products.count
|
5
|
+
hash = Digest::SHA1.hexdigest(params.to_json)
|
6
|
+
max_updated_at = (@products.maximum(:updated_at) || Date.today).to_s(:number)
|
7
|
+
"#{I18n.locale}/#{current_pricing_options.cache_key}/spree/products/all-#{params[:page]}-#{hash}-#{max_updated_at}-#{count}"
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
Spree::Product.class_eval do
|
2
|
+
searchkick autocomplete: [:name]
|
3
|
+
|
4
|
+
def search_data
|
5
|
+
json = {
|
6
|
+
name: name,
|
7
|
+
description: description,
|
8
|
+
active: available?,
|
9
|
+
price: price,
|
10
|
+
currency: cost_currency,
|
11
|
+
sku: sku,
|
12
|
+
conversions: orders.complete.count,
|
13
|
+
taxon_ids: taxon_and_ancestors.map(&:id),
|
14
|
+
taxon_names: taxon_and_ancestors.map(&:name)
|
15
|
+
}
|
16
|
+
|
17
|
+
Spree::Property.all.each do |prop|
|
18
|
+
json.merge!(Hash[prop.name.downcase, property(prop.name)])
|
19
|
+
end
|
20
|
+
|
21
|
+
Spree::Taxonomy.all.each do |taxonomy|
|
22
|
+
json.merge!(Hash["#{ taxonomy.name.downcase }_ids", taxon_by_taxonomy(taxonomy.id).map(&:id)])
|
23
|
+
end
|
24
|
+
|
25
|
+
json
|
26
|
+
end
|
27
|
+
|
28
|
+
def taxon_by_taxonomy(taxonomy_id)
|
29
|
+
taxons.joins(:taxonomy).where(spree_taxonomies: { id: taxonomy_id })
|
30
|
+
end
|
31
|
+
|
32
|
+
def taxon_and_ancestors
|
33
|
+
taxons.map(&:self_and_ancestors).flatten.uniq
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.autocomplete(keywords)
|
37
|
+
if keywords
|
38
|
+
Spree::Product.search(
|
39
|
+
keywords,
|
40
|
+
autocomplete: true,
|
41
|
+
limit: 10, where: search_where
|
42
|
+
).map(&:name).map(&:strip).uniq
|
43
|
+
else
|
44
|
+
Spree::Product.search(
|
45
|
+
'*',
|
46
|
+
where: search_where
|
47
|
+
).map(&:name).map(&:strip)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.search_where
|
52
|
+
{
|
53
|
+
active: true,
|
54
|
+
price: { not: nil }
|
55
|
+
}
|
56
|
+
end
|
57
|
+
end
|
data/app/overrides/spree/admin/properties/_form/add_filterable_to_property_form.html.erb.deface
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
<!-- insert_bottom 'div[data-hook=admin_property_form]' -->
|
2
|
+
<div class="col-md-6">
|
3
|
+
<%= f.field_container :filterable, class: ['form-group'] do %>
|
4
|
+
<%= f.label :filterable, Spree.t(:filterable) %> <span class="required">*</span>
|
5
|
+
<%= error_message_on :property, :filterable, :class => 'error-message' %>
|
6
|
+
<%= check_box :property, :filterable, :class => 'form-control' %>
|
7
|
+
<% end %>
|
8
|
+
</div>
|
data/app/overrides/spree/admin/taxonomies/_form/add_filterable_to_taxonomy_form.html.erb.deface
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
<!-- insert_bottom 'div.form-group' -->
|
2
|
+
<%= f.field_container :filterable, class: ['form-group'] do %>
|
3
|
+
<%= f.label :filterable, Spree.t(:filterable) %> <span class="required">*</span>
|
4
|
+
<%= error_message_on :taxonomy, :filterable, :class => 'error-message' %>
|
5
|
+
<%= check_box :taxonomy, :filterable, :class => 'form-control' %>
|
6
|
+
<% end %>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<div class="navigation" data-hook="navigation">
|
2
|
+
<h4 class="filter-title"><%= Spree.t("esfilters.#{filter[:name]}") %></h4>
|
3
|
+
<ul class="list-group">
|
4
|
+
<% filter[:options].each do |f| %>
|
5
|
+
<li class="list-group-item">
|
6
|
+
<input type="checkbox"
|
7
|
+
id="<%= f[:label] %>"
|
8
|
+
name="search[<%= filter[:name] %>][]"
|
9
|
+
value="<%= f[:value] %>"
|
10
|
+
/>
|
11
|
+
<label class="nowrap" for="<%= f[:label] %>"> <%= f[:label] %> </label>
|
12
|
+
</li>
|
13
|
+
<% end %>
|
14
|
+
</ul>
|
15
|
+
</div>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<%
|
2
|
+
aggregations = @products.aggs
|
3
|
+
filters = Spree::Core::SearchkickFilters.applicable_filters(aggregations)
|
4
|
+
%>
|
5
|
+
<%= form_tag '', :method => :get, :id => 'sidebar_products_search' do %>
|
6
|
+
<%= hidden_field_tag 'per_page', params[:per_page] %>
|
7
|
+
<% filters.each do |filter| %>
|
8
|
+
<% next if filter[:options].empty? %>
|
9
|
+
<%= render partial: "spree/shared/es_filter", locals: { filter: filter } %>
|
10
|
+
<% end %>
|
11
|
+
<%= submit_tag Spree.t(:search), :name => nil, :class => 'btn btn-primary' %>
|
12
|
+
<% end %>
|
data/bin/rails
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
|
2
|
+
|
3
|
+
ENGINE_ROOT = File.expand_path('../..', __FILE__)
|
4
|
+
ENGINE_PATH = File.expand_path('../../lib/solidus_searchkick/engine', __FILE__)
|
5
|
+
|
6
|
+
require 'rails/all'
|
7
|
+
require 'rails/engine/commands'
|
data/config/routes.rb
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
Spree::Core::Engine.routes.draw do
|
2
|
+
# Add your extension routes here
|
3
|
+
get '/best', to: 'products#best_selling'
|
4
|
+
get '/best/t/*id/', to: 'products#best_selling', as: :best_selling_taxon
|
5
|
+
get '/autocomplete/products', to: 'products#autocomplete', as: :autocomplete
|
6
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module SolidusSearchkick
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
|
5
|
+
class_option :auto_run_migrations, :type => :boolean, :default => false
|
6
|
+
|
7
|
+
def add_javascripts
|
8
|
+
append_file 'vendor/assets/javascripts/spree/frontend/all.js', "//= require spree/frontend/solidus_searchkick\n"
|
9
|
+
append_file 'vendor/assets/javascripts/spree/backend/all.js', "//= require spree/backend/solidus_searchkick\n"
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_stylesheets
|
13
|
+
inject_into_file 'vendor/assets/stylesheets/spree/frontend/all.css', " *= require spree/frontend/solidus_searchkick\n", :before => /\*\//, :verbose => true
|
14
|
+
inject_into_file 'vendor/assets/stylesheets/spree/backend/all.css', " *= require spree/backend/solidus_searchkick\n", :before => /\*\//, :verbose => true
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_migrations
|
18
|
+
run 'bundle exec rake railties:install:migrations FROM=solidus_searchkick'
|
19
|
+
end
|
20
|
+
|
21
|
+
def run_migrations
|
22
|
+
run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask 'Would you like to run the migrations now? [Y/n]')
|
23
|
+
if run_migrations
|
24
|
+
run 'bundle exec rake db:migrate'
|
25
|
+
else
|
26
|
+
puts 'Skipping rake db:migrate, don\'t forget to run it!'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module SolidusSearchkick
|
2
|
+
class Engine < Rails::Engine
|
3
|
+
require 'spree/core'
|
4
|
+
isolate_namespace Spree
|
5
|
+
engine_name 'solidus_searchkick'
|
6
|
+
|
7
|
+
config.autoload_paths += %W(#{config.root}/lib)
|
8
|
+
|
9
|
+
# use rspec for tests
|
10
|
+
config.generators do |g|
|
11
|
+
g.test_framework :rspec
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.activate
|
15
|
+
Dir.glob(File.join(File.dirname(__FILE__), '../../app/**/*_decorator*.rb')) do |c|
|
16
|
+
Rails.configuration.cache_classes ? require(c) : load(c)
|
17
|
+
end
|
18
|
+
Spree::Config.searcher_class = Spree::Search::Searchkick
|
19
|
+
end
|
20
|
+
|
21
|
+
config.to_prepare &method(:activate).to_proc
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
FactoryGirl.define do
|
2
|
+
# Define your Spree extensions Factories within this file to enable applications, and other extensions to use and override them.
|
3
|
+
#
|
4
|
+
# Example adding this to your spec_helper will load these Factories for use:
|
5
|
+
# require 'solidus_searchkick/factories'
|
6
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Spree
|
2
|
+
module Core
|
3
|
+
module SearchkickFilters
|
4
|
+
def self.applicable_filters(aggregations)
|
5
|
+
es_filters = []
|
6
|
+
Spree::Taxonomy.filterable.each do |taxonomy|
|
7
|
+
es_filters << self.process_filter(taxonomy.filter_name, :taxon, aggregations[taxonomy.filter_name])
|
8
|
+
end
|
9
|
+
|
10
|
+
Spree::Property.filterable.each do |property|
|
11
|
+
es_filters << self.process_filter(property.filter_name, :property, aggregations[property.filter_name])
|
12
|
+
end
|
13
|
+
|
14
|
+
es_filters.uniq
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.process_filter(name, type, filter)
|
18
|
+
options = []
|
19
|
+
case type
|
20
|
+
when :price
|
21
|
+
min = filter["buckets"].min {|a,b| a["key"] <=> b["key"] }
|
22
|
+
max = filter["buckets"].max {|a,b| a["key"] <=> b["key"] }
|
23
|
+
if min && max
|
24
|
+
options = {min: min["key"].to_i, max: max["key"].to_i, step: 100}
|
25
|
+
else
|
26
|
+
options = {}
|
27
|
+
end
|
28
|
+
when :taxon
|
29
|
+
ids = filter["buckets"].map{|h| h["key"]}
|
30
|
+
taxons = Spree::Taxon.where(id: ids).order(name: :asc)
|
31
|
+
taxons.each {|t| options << {label: t.name, value: t.id }}
|
32
|
+
when :property
|
33
|
+
values = filter["buckets"].map{|h| h["key"]}
|
34
|
+
values.each {|t| options << {label: t, value: t }}
|
35
|
+
end
|
36
|
+
|
37
|
+
{
|
38
|
+
name: name,
|
39
|
+
type: type,
|
40
|
+
options: options
|
41
|
+
}
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.aggregation_term(aggregation)
|
46
|
+
aggregation["buckets"].sort_by { |hsh| hsh["key"] }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Spree::Search
|
2
|
+
class Searchkick < Spree::Core::Search::Base
|
3
|
+
def retrieve_products
|
4
|
+
@products = get_base_elasticsearch
|
5
|
+
end
|
6
|
+
|
7
|
+
protected
|
8
|
+
|
9
|
+
def get_base_elasticsearch
|
10
|
+
curr_page = page || 1
|
11
|
+
Spree::Product.search(keyword_query, where: where_query, aggs: aggregations, smart_aggs: true, order: sort, page: curr_page, per_page: per_page)
|
12
|
+
end
|
13
|
+
|
14
|
+
def where_query
|
15
|
+
where_query = {
|
16
|
+
active: true,
|
17
|
+
currency: pricing_options.currency,
|
18
|
+
price: { not: nil }
|
19
|
+
}
|
20
|
+
where_query.merge!({taxon_ids: taxon.id}) if taxon
|
21
|
+
add_search_filters(where_query)
|
22
|
+
end
|
23
|
+
|
24
|
+
def keyword_query
|
25
|
+
(keywords.nil? || keywords.empty?) ? "*" : keywords
|
26
|
+
end
|
27
|
+
|
28
|
+
def sort
|
29
|
+
order ? order : nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def aggregations
|
33
|
+
fs = []
|
34
|
+
Spree::Taxonomy.filterable.each do |taxonomy|
|
35
|
+
fs << taxonomy.filter_name.to_sym
|
36
|
+
end
|
37
|
+
Spree::Property.filterable.each do |property|
|
38
|
+
fs << property.filter_name.to_sym
|
39
|
+
end
|
40
|
+
fs
|
41
|
+
end
|
42
|
+
|
43
|
+
def add_search_filters(query)
|
44
|
+
return query unless search
|
45
|
+
search.each do |name, scope_attribute|
|
46
|
+
query.merge!(Hash[name, scope_attribute])
|
47
|
+
end
|
48
|
+
|
49
|
+
query
|
50
|
+
end
|
51
|
+
|
52
|
+
def prepare(params)
|
53
|
+
@properties[:order] = params[:order].blank? ? nil : params[:order]
|
54
|
+
params = params.deep_symbolize_keys
|
55
|
+
super
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require File.expand_path("../lib/solidus_searchkick/version", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.platform = Gem::Platform::RUBY
|
6
|
+
s.name = 'solidus_searchkick'
|
7
|
+
s.version = SolidusSearchkick::VERSION
|
8
|
+
s.licenses = ['MIT']
|
9
|
+
s.summary = 'Add searchkick to Solidus'
|
10
|
+
s.description = 'Filters, suggests, autocompletes, sortings, searches'
|
11
|
+
s.required_ruby_version = '>= 2.0.0'
|
12
|
+
|
13
|
+
s.author = ['Jim Smith']
|
14
|
+
s.email = ['jim@jimsmithdesign.com']
|
15
|
+
s.homepage = 'https://github.com/elevatorup/solidus_searchkick'
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.require_path = 'lib'
|
20
|
+
s.requirements << 'none'
|
21
|
+
|
22
|
+
s.add_runtime_dependency 'solidus', '~> 1.4', '>= 1.4.0'
|
23
|
+
s.add_runtime_dependency 'searchkick', '~> 1.2'
|
24
|
+
|
25
|
+
s.add_development_dependency 'capybara', '~> 2.4'
|
26
|
+
s.add_development_dependency 'coffee-rails'
|
27
|
+
s.add_development_dependency 'database_cleaner'
|
28
|
+
s.add_development_dependency 'factory_girl', '~> 4.5'
|
29
|
+
s.add_development_dependency 'ffaker'
|
30
|
+
s.add_development_dependency 'rspec-rails', '~> 3.1'
|
31
|
+
s.add_development_dependency 'sass-rails', '~> 5.0'
|
32
|
+
s.add_development_dependency 'selenium-webdriver'
|
33
|
+
s.add_development_dependency 'simplecov'
|
34
|
+
s.add_development_dependency 'sqlite3'
|
35
|
+
s.add_development_dependency 'pry'
|
36
|
+
s.add_development_dependency 'better_errors'
|
37
|
+
s.add_development_dependency 'binding_of_caller'
|
38
|
+
end
|