Solidus + Searchkick
Add Elasticsearch to Solidus, powered by searchkick.
Features
- Search products by sku, name, description, taxon, and more out of the box.
- Customize the product search fields to your liking.
- Search Autocomplete by name out of the box (Typeahead).
- Product filtering based on ElasticSearch queries.
Installation
Add searchkick and solidus_searchkick to your Gemfile:
gem 'searchkick'
gem 'solidus_searchkick'
Bundle your dependencies and run the installation generator:
bundle
bundle exec rails g solidus_searchkick:install
Installing solidus_searchkick will copy over a new spree/shared/_filters.html.erb
template. This includes a few minor changes to the default template, mainly changing a few search
params to filter
params to work nicely with solidus_searchkick and elasticsearch.
Install elasticsearch
Searchkick Integration
By default, Searchkick is initialized on the Product model in SolidusSearchkick's product_decorator
with:
searchkick index_name: ..., word_start: [:name]
If you need to modify this, you can do so in your own product_decorator
, by adding something like:
# app/models/spree/product_decorator.rb
Spree::Product.class_eval do
searchkick index_name: ..., word_start: [:name], callbacks: :async unless Spree::Product.try(:searchkick_options)
...
end
In this example, the unless Spree::product.try(:searchkick_options)
conditional is needed, since, by default, the development environment does not cache classes and will reload them.
Adding this condition prevents Rails from throwing an error when reloading the product decorator and trying to add searchkick multiple times.
Index Name
In version 0.3.1, the index_name option was added. It defaults to
"#{Rails.application.class.parent_name.parameterize.underscore}_spree_products_#{Rails.env}"
If you need are upgrading from a version of SolidusSearchkick prior to version 0.3.1, you will need to either:
- reindex all of your products (which will build the index with the new name)
- update your product_decorator to use the index_name currently being used
Search Parameters
By default, only the Spree::Product
class is indexed. The following items are indexed by default:
- name
- description
- available? (indexed as
active
) - price (needed in order to return products that have price != nil)
- currency
- sku
- orders.complete.count (indexed as
conversions
) - taxon_ids
- taxon_names
In order to control what data is indexed, override the Spree::Product#search_data
method. Call Spree::Product.reindex
after changing this method.
Filtering
Initially, you start with a very basic filtering system which includes a price filter in order to show how the filtering works with SolidusSearchkick and ElasticSearch. In order to add additional filters or change the price filter, the following steps will need to be taken:
-
Copy the Spree::Core::SearchkickFilters
file from this gem and place it in lib/spree/core/
-
Add it to the config load path, or require it in an initializer, e.g...
# config/initializers/spree.rb
require 'spree/core/searchkick_filters'
-
Modify SearchkickFilters as needed.
The conds
for SearchkickFilters
are similar to the ProductFilters
in the default version of spree/solidus. Although the first parameters of each is still the label, the second item is the ElasticSearch DSL that will be used for that filter, eg...
conds = [
...
[Spree.t(:under_price, price: format_price(1)), { range: { price: { lt: 1 } } }],
...
]
- Ensure that
Spree::Taxon#applicable_filters
returns the filters you want:
# app/models/spree/taxon_decorator.rb
def applicable_filters
filters = []
...
filters << Spree::Core::SearchkickFilters.price_filter if Spree::Core::SearchkickFilters.respond_to?(:price_filter)
...
filters
end
Advanced Filtering
Checkout out the wiki page here.
Autocomplete
By default, SolidusSearchkick provides autocomplete for the name
field of your products. In order to get this working, all you need to do is add the following lines to the corresponding files:
application.js
//= require spree/frontend/typeahead.bundle
//= require spree/frontend/solidus_searchkick
application.css
*= require spree/frontend/solidus_searchkick
After that, automplete should now be working in the search box.
Note: These requires are not added by the generator in order to give you the option to add Autocomplete instead of forcing it.
Advanced Autocomplete
The default autocomplete provided by solidus_searchkick is pretty basic.
In order to modify how the autocomplete for your site works, you will first need to create a product_decorator
if you do not already have one.
You will then need to override the self.autocomplete
method in order to suit your needs. Take a look at the self.autocomplete
method in the solidus_searchkick product_decorator
to get started.
# app/models/spree/product_decorator.rb
Spree::Product.class_eval do
...
def self.autocomplete(keywords)
...
end
...
end
Fields and Boosting
With SolidusSearchkick, you can include a list of fields that you would like to search on. This list can also include boosted fields the same way that Searchkick can. You can learn more about Searchkick boosting here.
In order to add the fields, pass in an array of the fields:
fields = ['name^99', :description, ...]
searcher = build_searcher(params.merge(fields: fields))
@products = searcher.retrieve_products
Searchkick Options
Since SolidusSearchkick uses Searchkick to interact with ElasticSearch, it also accepts all of the Searchkick options.
You can specify a limit or offset when searching, as well as any other options provided by Searchkick.
In order to use the options, all you need to do is to pass a searchkick_options
hash along with your search.
searcher = build_searcher(params.deep_merge(searchkick_options: { limit: 6, offset: 100 }))
@products = searcher.retrieve_products
OR
search_params = {
search: {
price: {
gt: 100
}
},
searchkick_options: {
order: {
price: :asc
},
limit: 100
}
}
searcher = build_searcher(params.merge(search_params))
@products = searcher.retrieve_products
ElasticSearch DSL
There are times where even the power of Searchkick will not be enough to get you the results you need from ElasitcSearch.
In these cases, you can use the full power of the ElasticSearch DSL by passing in the query
param.
query = {
{
'bool': {
'must': [
{ 'match': { 'name': 'Product 1'} }
],
'filter': [
{ 'range': { 'available_on': { 'gte': '2015-01-01' }}}
]
}
}
}
searcher = build_searcher(query: query)
@products = searcher.retrieve_products
Testing
First bundle your dependencies, then run rake
. rake
will default to building the dummy app if it does not exist, then it will run specs. The dummy app can be regenerated by using rake test_app
.
bundle
bundle exec rake
When testing your applications integration with this extension you may use it's factories.
Simply add this require statement to your spec_helper:
require 'solidus_searchkick/factories'
Copyright (c) 2016 Jim Smith, released under the New BSD License
Special Thanks
SolidusSearchkick was heavily inspired by spree_searchkick, which was used as a starting point to getting Solidus to work nicely with Searchkick.
Contributing
- Fork it ( https://github.com/elevatorup/solidus_searchkick/fork )
- Create your feature branch (
git checkout -b feature/my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin feature/my-new-feature
) - Create a new Pull Request