Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add classifications.position sorting #44

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
source 'https://rubygems.org'

gem 'spree', github: 'spree/spree', branch: '3-0-stable'
gem 'spree', github: 'spree/spree'
# Provides basic authentication functionality for testing parts of your engine
gem 'spree_auth_devise', github: 'spree/spree_auth_devise', branch: '3-0-stable'
gem 'spree_i18n', github: 'spree-contrib/spree_i18n', branch: '3-0-stable'
gem 'spree_auth_devise', github: 'spree/spree_auth_devise'

gemspec
2 changes: 0 additions & 2 deletions app/assets/stylesheets/spree/frontend/spree_elasticsearch.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
/*
* This is a manifest file that includes stylesheets for spree_elasticsearch
*= require normalize
*= require skeleton
*= require ion.rangeSlider
*= require ion.rangeSlider.skinFlat
*/
Expand Down
33 changes: 21 additions & 12 deletions app/models/spree/product_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,31 @@ module Spree
document_type 'spree_product'

mapping _all: { analyzer: 'nGram_analyzer', search_analyzer: 'whitespace_analyzer' } do
indexes :name, type: 'multi_field' do
indexes :name, type: 'string', analyzer: 'nGram_analyzer', boost: 100
indexes :untouched, type: 'string', include_in_all: false, index: 'not_analyzed'
indexes :name, type: 'text' do
indexes :name, type: 'text', boost: 100, analyzer: 'nGram_analyzer', index: true
indexes :untouched, type: 'keyword', index: false
end

indexes :description, analyzer: 'snowball'
indexes :available_on, type: 'date', format: 'dateOptionalTime', include_in_all: false
indexes :available_on, type: 'date', format: 'dateOptionalTime'
indexes :price, type: 'double'
indexes :sku, type: 'string', index: 'not_analyzed'
indexes :taxon_ids, type: 'string', index: 'not_analyzed'
indexes :properties, type: 'string', index: 'not_analyzed'
indexes :sku, type: 'keyword', index: true
indexes :taxon_ids, type: 'keyword', index: true
indexes :properties, type: 'keyword', index: true
indexes :classifications, type: 'nested' do
indexes :taxon_id, type: 'integer'
indexes :position, type: 'integer'
end
end

def as_indexed_json(options={})
result = as_json({
methods: [:price, :sku],
only: [:available_on, :description, :name],
include: {
classifications: {
only: [:taxon_id, :position]
},
variants: {
only: [:sku],
include: {
Expand Down Expand Up @@ -107,6 +114,8 @@ def to_hash
[ { 'price' => { order: 'asc' } }, { 'name.untouched' => { order: 'asc' } }, '_score' ]
when 'price_desc'
[ { 'price' => { order: 'desc' } }, { 'name.untouched' => { order: 'asc' } }, '_score' ]
when 'classification'
[ { 'classifications.position' => { mode: 'min', order: 'asc', nested_path: 'classifications' } } ]
when 'score'
[ '_score', { 'name.untouched' => { order: 'asc' } }, { price: { order: 'asc' } } ]
else
Expand All @@ -123,23 +132,23 @@ def to_hash
# basic skeleton
result = {
min_score: 0.1,
query: { filtered: {} },
query: { bool: {} },
sort: sorting,
from: from,
aggregations: aggregations
}

# add query and filters to filtered
result[:query][:filtered][:query] = query
# add query and filters to filterd
result[:query][:bool][:must] = query
# taxon and property filters have an effect on the facets
and_filter << { terms: { taxon_ids: taxons } } unless taxons.empty?
# only return products that are available
and_filter << { range: { available_on: { lte: 'now' } } }
result[:query][:filtered][:filter] = { and: and_filter } unless and_filter.empty?
result[:query][:bool][:filter] = and_filter unless and_filter.empty?

# add price filter outside the query because it should have no effect on facets
if price_min && price_max && (price_min < price_max)
result[:filter] = { range: { price: { gte: price_min, lte: price_max } } }
result[:post_filter] = { range: { price: { gte: price_min, lte: price_max } } }
end

result
Expand Down
2 changes: 1 addition & 1 deletion lib/spree_elasticsearch/factories.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FactoryGirl.define do
FactoryBot.define do
# Define your Spree extensions Factories within this file to enable applications, and other extensions to use and override them.
#
# Example adding this to your spec_helper will load these Factories for use:
Expand Down
28 changes: 25 additions & 3 deletions spec/models/products_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@

module Spree
describe Product do
let(:a_product) { create(:product) }
let(:another_product) { create(:product) }
let(:taxon) { create(:taxon) }
let(:a_product) { create(:product, taxons: [taxon]) }
let(:another_product) { create(:product, taxons: [taxon]) }

before do
# for clean testing, delete index, create new one and create/update mapping
Product.delete_all
client = Elasticsearch::Client.new log: true, hosts: ElasticsearchSettings.hosts
client = Elasticsearch::Client.new log: false, hosts: ElasticsearchSettings.hosts

if Elasticsearch::Model.client.indices.exists index: Spree::ElasticsearchSettings.index
client.indices.delete index: ElasticsearchSettings.index
Expand Down Expand Up @@ -108,6 +109,27 @@ module Spree
it { expect(products.results.to_a[1].name).to eq another_product.name }
end

context 'retrieves products sorted by classifications' do
before do
a_product.name = 'Product 1'
another_product.name = 'Product 2'
another_product.classifications.first.insert_at(1) # Reorder positions
a_product.classifications.reload
another_product.classifications.reload
a_product.__elasticsearch__.index_document
another_product.__elasticsearch__.index_document
Product.__elasticsearch__.refresh_index!
end

let(:products) do
Product.__elasticsearch__.search(Spree::Product::ElasticsearchQuery.new(taxons: taxon.id, sorting: 'classification'))
end

it { expect(products.results.total).to eq 2 }
it { expect(products.results.to_a[0].name).to eq another_product.name }
it { expect(products.results.to_a[1].name).to eq a_product.name }
end

context 'filters products based on price' do
before do
a_product.price = 1
Expand Down
4 changes: 2 additions & 2 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
require 'product_helpers'

RSpec.configure do |config|
config.include FactoryGirl::Syntax::Methods
config.include FactoryBot::Syntax::Methods

# == URL Helpers
#
Expand Down Expand Up @@ -72,7 +72,7 @@

# Before each spec check if it is a Javascript test and switch between using database transactions or not where necessary.
config.before :each do
DatabaseCleaner.strategy = example.metadata[:js] ? :truncation : :transaction
DatabaseCleaner.strategy = RSpec.current_example.metadata[:js] ? :truncation : :transaction
DatabaseCleaner.start
end

Expand Down
29 changes: 29 additions & 0 deletions spec/support/elasticsearch.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
require 'elasticsearch/extensions/test/cluster'

RSpec.configure do |config|
unless ENV['CI']
config.before(:suite) { start_elastic_cluster }

config.after(:suite) { stop_elastic_cluster }
end

# delete indices for searchable models to keep clean state between tests
config.after do
end
end

def start_elastic_cluster
ENV['TEST_CLUSTER_PORT'] = '9200'
ENV['TEST_CLUSTER_NODES'] = '1'
ENV['TEST_CLUSTER_NAME'] = 'spree_elasticsearch_test'

ENV['ELASTICSEARCH_URL'] = "http://localhost:#{ENV['TEST_CLUSTER_PORT']}"

return if Elasticsearch::Extensions::Test::Cluster.running?
Elasticsearch::Extensions::Test::Cluster.start(timeout: 60, clear_cluster: true)
end

def stop_elastic_cluster
return unless Elasticsearch::Extensions::Test::Cluster.running?
Elasticsearch::Extensions::Test::Cluster.stop
end
11 changes: 6 additions & 5 deletions spree_elasticsearch.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.name = 'spree_elasticsearch'
s.version = '3.0.0'
s.version = '3.1.0'
s.summary = 'Add searching capabilities via Elasticsearch'
s.description = s.summary
s.required_ruby_version = '>= 1.9.3'
s.required_ruby_version = '>= 2.2.7'

s.author = 'Jan Vereecken'
s.email = '[email protected]'
Expand All @@ -19,16 +19,17 @@ Gem::Specification.new do |s|
s.add_dependency 'elasticsearch-model'
s.add_dependency 'elasticsearch-rails'
s.add_dependency 'settingslogic'
s.add_dependency 'spree_core', '~> 3.0.0'
s.add_dependency 'virtus'
s.add_dependency 'spree_core', '>= 3.1.0', '< 4.0'

s.add_development_dependency 'elasticsearch-extensions'
s.add_development_dependency 'capybara', '~> 2.1'
s.add_development_dependency 'coffee-rails'
s.add_development_dependency 'database_cleaner'
s.add_development_dependency 'byebug'
s.add_development_dependency 'factory_girl', '~> 4.2'
s.add_development_dependency 'factory_bot'
s.add_development_dependency 'ffaker'
s.add_development_dependency 'rspec-rails', '~> 2.13'
s.add_development_dependency 'rspec-rails'
s.add_development_dependency 'sass-rails'
s.add_development_dependency 'selenium-webdriver'
s.add_development_dependency 'simplecov'
Expand Down