spree_autosuggest 2.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d5b38a670393d80a848069359a6395823e607224
4
+ data.tar.gz: 3771398c33b1725348eabcea5671ea7486f87088
5
+ SHA512:
6
+ metadata.gz: f730d30937901a9374273b12f41fc667fa0c4a581f91a8f7612ef132f7ac63470473cdc7b14a764bd8cbd1f720551c61020629cbaf980ceabb3321f19c05d7e3
7
+ data.tar.gz: 9c0c2265efe52c575dbea8ada953b2c011dca597e7ae064a3235112f15130d50cb8fdc141db0b19b19bf6059c944eb40bb8b9631827cdd542ba2c5be91dbfeaf
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ \#*
2
+ *~
3
+ .#*
4
+ .DS_Store
5
+ .idea
6
+ .project
7
+ tmp
8
+ nbproject
9
+ *.swp
10
+ spec/dummy
11
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'http://rubygems.org'
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2012 Demidov Aleksey
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+ * Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+ * Neither the name Spree nor the names of its contributors may be used to
13
+ endorse or promote products derived from this software without specific
14
+ prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
20
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,12 @@
1
+ Spree Autosuggest
2
+ =================
3
+
4
+ This extension adds suggestions for product search.
5
+
6
+ ### Installation
7
+
8
+ 1. Add to Gemfile: `gem 'spree_autosuggest', :git => 'git://github.com/evrone/spree_autosuggest.git'`
9
+ 2. Run `rails g spree_autosuggest:install`
10
+ 3. Run `rake spree_autosuggest:seed` to add all Taxon & Product names to the autosuggest database
11
+
12
+ **NOTE:** This extension works only with Spree 1.0 and higher.
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require 'rake'
4
+ require 'rake/testtask'
5
+ require 'rake/packagetask'
6
+ require 'rubygems/package_task'
7
+ require 'rspec/core/rake_task'
8
+ require 'bundler/gem_tasks'
9
+
10
+ RSpec::Core::RakeTask.new
11
+
12
+ task :default => [:spec]
13
+
14
+ spec = eval(File.read('spree_autosuggest.gemspec'))
15
+
16
+ Gem::PackageTask.new(spec) do |p|
17
+ p.gem_spec = spec
18
+ end
19
+
20
+ desc "Generates a dummy app for testing"
21
+ task :test_app do
22
+ ENV['LIB_NAME'] = 'spree_autosuggest'
23
+ Rake::Task['common:test_app'].invoke
24
+ end
data/Versionfile ADDED
@@ -0,0 +1,3 @@
1
+ "1.0.x" => { :branch => "master"}
2
+ "1.1.x" => { :branch => "master"}
3
+ "1.2.x" => { :branch => "master"}
@@ -0,0 +1,79 @@
1
+ class @Autosuggest
2
+ constructor: (selector, options = {}) ->
3
+ @search_field = $(selector)
4
+ return if @search_field.length == 0
5
+
6
+ @settings =
7
+ from_db: 15
8
+ to_display: 5
9
+ keyswitch: true
10
+ @settings = $.extend @settings, options
11
+
12
+ @cache = {}
13
+
14
+ $.extend true, $.ui.autocomplete::, @extension_methods()
15
+ @search_field.autocomplete(
16
+ @settings,
17
+ source: (request, response) =>
18
+ @finder(request, response))
19
+ .bind "keydown.autocomplete", (event) =>
20
+ @fire_keybinds(event)
21
+
22
+ # TODO: rewrite
23
+ fire_keybinds: (event) ->
24
+ if event.keyCode == $.ui.keyCode.RIGHT
25
+ elem = $(@search_field.data('autocomplete').menu.active).find('a')
26
+ if elem.length > 0
27
+ elem.trigger('click')
28
+ else
29
+ $('li.ui-menu-item:first a').trigger('mouseenter').trigger('click')
30
+
31
+ finder: (request, response) ->
32
+ term = request.term.toLowerCase()
33
+ cached = @from_cache(term)
34
+ return response cached if cached
35
+ $.getJSON "/suggestions", request, (data) =>
36
+ @cache[term] = data
37
+ response @from_cache(term)
38
+
39
+ from_cache: (term) ->
40
+ result = false
41
+ $.each @cache, (key, data) =>
42
+ if term.indexOf(key) is 0 and data.length < @settings.from_db
43
+ result = @filter_terms(data, term).slice(0, @settings.to_display)
44
+ result
45
+
46
+ filter_terms: (array, term) ->
47
+ matcher = new RegExp($.ui.autocomplete.escapeRegex(term), "i")
48
+ $.grep array, (value) =>
49
+ source = value.label or value.value or value
50
+
51
+ return true if matcher.test(source)
52
+ if @settings.keyswitch
53
+ matcher.test(@keyswitch(source))
54
+
55
+ extension_methods: ->
56
+ _renderItem: (ul, item) ->
57
+ item.label = item.label.replace(new RegExp("(" + $.ui.autocomplete.escapeRegex(@term) + ")", "gi"), "<strong>$1</strong>")
58
+ $("<li></li>").data("item.autocomplete", item).append("<a>" + item.label + "</a>").appendTo ul
59
+
60
+ keyswitch: (str) ->
61
+ ru = "йцукенгшщзхъфывапролджэячсмитьбюёЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮЁ"
62
+ en = "qwertyuiop[]asdfghjkl;'zxcvbnm,.`QWERTYUIOP{}ASDFGHJKL:\"ZXCVBNM<>~"
63
+
64
+ if ru.indexOf(str.match("[^Wd_]")) is -1
65
+ from = en
66
+ to = ru
67
+ else
68
+ from = ru
69
+ to = en
70
+
71
+ switched = ""
72
+ for char in str
73
+ fromIndex = from.indexOf(char)
74
+ if fromIndex < 0
75
+ switched += char
76
+ else
77
+ switched += to[fromIndex]
78
+
79
+ switched
@@ -0,0 +1,13 @@
1
+ //= require store/autosuggest
2
+
3
+ $ ->
4
+ suggestOn = '<%= Spree::Autosuggest::Config.field %>'
5
+
6
+ $("input[name=#{suggestOn}]:not(.ui-autocomplete-input)").on "focus", ->
7
+ new Autosuggest(this,
8
+ from_db: <%= Spree::Autosuggest::Config.rows_from_db %>
9
+ to_display: <%= Spree::Autosuggest::Config.rows_to_display %>
10
+ delay: 100
11
+ minLength: 1
12
+ keyswitch: true )
13
+
@@ -0,0 +1,16 @@
1
+ Spree::BaseController.class_eval do
2
+ after_filter :save_search
3
+
4
+
5
+ def save_search
6
+ keywords = @searcher.try!(:keywords)
7
+
8
+ if @products.present? and keywords.present?
9
+ query = Spree::Suggestion.find_or_initialize_by_keywords(keywords)
10
+
11
+ query.items_found = @products.size
12
+ query.increment(:count)
13
+ query.save
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ class Spree::SuggestionsController < Spree::BaseController
2
+
3
+ def index
4
+ suggestions = Spree::Suggestion.relevant(params['term'])
5
+
6
+ if request.xhr?
7
+ render :json => suggestions.map(&:keywords)
8
+ else
9
+ render_404
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,15 @@
1
+ class Spree::Suggestion < ActiveRecord::Base
2
+ validates :keywords, :presence => true
3
+
4
+ def self.relevant(term)
5
+ config = Spree::Autosuggest::Config
6
+
7
+ select(:keywords).
8
+ where("count >= ?", config.min_count).
9
+ where("items_found != 0").
10
+ where("keywords LIKE ? OR keywords LIKE ?", term + '%', KeySwitcher.switch(term) + '%').
11
+ order("(#{config.count_weight}*count + #{config.items_found_weight}*items_found) DESC").
12
+ limit(config.rows_from_db)
13
+ end
14
+
15
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ Spree::Core::Engine.routes.draw do
2
+ get 'suggestions', :to => 'suggestions#index'
3
+ end
@@ -0,0 +1,12 @@
1
+ class CreateSpreeSuggestions < ActiveRecord::Migration
2
+ def change
3
+ create_table :spree_suggestions do |t|
4
+ t.string :keywords
5
+ t.integer :count
6
+ t.integer :items_found
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :spree_suggestions, [:keywords, :count, :items_found]
11
+ end
12
+ end
@@ -0,0 +1,23 @@
1
+ module SpreeAutosuggest
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+
5
+ def add_javascripts
6
+ append_file "app/assets/javascripts/spree/frontend.js", "//= require store/spree_autosuggest\n"
7
+ end
8
+
9
+ def add_migrations
10
+ run 'bundle exec rake railties:install:migrations FROM=spree_autosuggest'
11
+ end
12
+
13
+ def run_migrations
14
+ res = ask "Would you like to run the migrations now? [Y/n]"
15
+ if res == "" || res.downcase == "y"
16
+ run 'bundle exec rake db:migrate'
17
+ else
18
+ puts "Skiping rake db:migrate, don't forget to run it!"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+
3
+ module KeySwitcher
4
+ extend self
5
+
6
+ def switch(str)
7
+
8
+ ru = "йцукенгшщзхъфывапролджэячсмитьбюёЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮЁ".split('')
9
+ en = "qwertyuiop[]asdfghjkl;'zxcvbnm,.`QWERTYUIOP{}ASDFGHJKL:\"ZXCVBNM<>~".split('')
10
+
11
+ return str unless letters = str.match(/([^\W\d_])/)
12
+
13
+ first_letter = letters[0]
14
+ mapping = first_letter.in?(ru) ? Hash[ru.zip(en)] : Hash[en.zip(ru)]
15
+
16
+ str.split('').inject('') { |result, char| result << (mapping[char] || char) }
17
+
18
+ end
19
+ end
20
+
@@ -0,0 +1,8 @@
1
+ class Spree::SuggestionConfiguration < Spree::Preferences::Configuration
2
+ preference :rows_to_display, :integer, :default => 5
3
+ preference :rows_from_db, :integer, :default => 15
4
+ preference :count_weight, :integer, :default => 2
5
+ preference :items_found_weight, :integer, :default => 1
6
+ preference :min_count, :integer, :default => 5
7
+ preference :field, :string, :default => 'keywords'
8
+ end
@@ -0,0 +1,27 @@
1
+ module Spree::Autosuggest
2
+ end
3
+
4
+ module SpreeAutosuggest
5
+ class Engine < Rails::Engine
6
+ engine_name 'spree_autosuggest'
7
+
8
+ config.autoload_paths += %W(#{config.root}/lib)
9
+
10
+ initializer "spree.suggestion.preferences", :after => "spree.environment" do |app|
11
+ Spree::Autosuggest::Config = Spree::SuggestionConfiguration.new
12
+ end
13
+
14
+ # use rspec for tests
15
+ config.generators do |g|
16
+ g.test_framework :rspec
17
+ end
18
+
19
+ def self.activate
20
+ Dir.glob(File.join(File.dirname(__FILE__), "../../app/**/*_decorator*.rb")) do |c|
21
+ Rails.configuration.cache_classes ? require(c) : load(c)
22
+ end
23
+ end
24
+
25
+ config.to_prepare &method(:activate).to_proc
26
+ end
27
+ end
@@ -0,0 +1,2 @@
1
+ require 'spree_core'
2
+ require 'spree_autosuggest/engine'
@@ -0,0 +1,22 @@
1
+ namespace :spree_autosuggest do
2
+ task :seed => :environment do
3
+ # all taxons with more than two words
4
+ Spree::Taxon.where('parent_id IS NOT NULL').each do |taxon|
5
+ searcher = Spree::Config.searcher_class.new(:taxon => taxon.id)
6
+ query = Spree::Suggestion.find_or_initialize_by_keywords(taxon.name)
7
+ query.items_found = searcher.retrieve_products.size
8
+ query.count = Spree::Autosuggest::Config[:min_count] + 1
9
+ query.save
10
+ end
11
+
12
+ # all product names
13
+ Spree::Product.find_each do |product|
14
+ query = Spree::Suggestion.find_or_initialize_by_keywords(product.name)
15
+ query.items_found = 1
16
+ query.count = Spree::Autosuggest::Config[:min_count] + 1
17
+ query.save
18
+ end
19
+
20
+ # spree pages extension?
21
+ end
22
+ end
data/script/rails ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ ENGINE_PATH = File.expand_path('../..', __FILE__)
5
+ load File.expand_path('../../spec/dummy/script/rails', __FILE__)
@@ -0,0 +1,32 @@
1
+ # Configure Rails Environment
2
+ ENV["RAILS_ENV"] = "test"
3
+
4
+ require File.expand_path("../dummy/config/environment.rb", __FILE__)
5
+
6
+ require 'rspec/rails'
7
+
8
+ # Requires supporting ruby files with custom matchers and macros, etc,
9
+ # in spec/support/ and its subdirectories.
10
+ Dir[File.join(File.dirname(__FILE__), "support/**/*.rb")].each {|f| require f }
11
+
12
+ # Requires factories defined in spree_core
13
+ require 'spree/core/testing_support/factories'
14
+
15
+ RSpec.configure do |config|
16
+ # == Mock Framework
17
+ #
18
+ # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
19
+ #
20
+ # config.mock_with :mocha
21
+ # config.mock_with :flexmock
22
+ # config.mock_with :rr
23
+ config.mock_with :rspec
24
+
25
+ # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
26
+ config.fixture_path = "#{::Rails.root}/spec/fixtures"
27
+
28
+ # If you're not using ActiveRecord, or you'd prefer not to run each of your
29
+ # examples within a transaction, remove the following line or assign false
30
+ # instead of true.
31
+ config.use_transactional_fixtures = true
32
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: UTF-8
2
+ Gem::Specification.new do |s|
3
+ s.platform = Gem::Platform::RUBY
4
+ s.name = 'spree_autosuggest'
5
+ s.version = '2.0.0'
6
+ s.summary = 'Search suggestions for Spree'
7
+ s.description = 'Search suggestions for Spree'
8
+ s.required_ruby_version = '>= 1.8.7'
9
+
10
+ s.author = 'Demidov Aleksey, Alexander Balashov'
11
+
12
+ s.files = `git ls-files`.split("\n")
13
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ s.require_path = 'lib'
15
+ s.requirements << 'none'
16
+
17
+ s.add_dependency 'spree_core', '>= 1.0.0.rc3'
18
+
19
+ s.add_development_dependency 'capybara', '1.0.1'
20
+ s.add_development_dependency 'factory_girl'
21
+ s.add_development_dependency 'ffaker'
22
+ s.add_development_dependency 'rspec-rails', '~> 2.7'
23
+ s.add_development_dependency 'sqlite3'
24
+ end
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spree_autosuggest
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Demidov Aleksey, Alexander Balashov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: spree_core
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.0.rc3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 1.0.0.rc3
27
+ - !ruby/object:Gem::Dependency
28
+ name: capybara
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.1
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 1.0.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: factory_girl
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: ffaker
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec-rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.7'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.7'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sqlite3
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Search suggestions for Spree
98
+ email:
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - ".gitignore"
104
+ - ".rspec"
105
+ - Gemfile
106
+ - LICENSE
107
+ - README.md
108
+ - Rakefile
109
+ - Versionfile
110
+ - app/assets/javascripts/store/autosuggest.js.coffee
111
+ - app/assets/javascripts/store/spree_autosuggest.js.coffee.erb
112
+ - app/controllers/base_controller_decorator.rb
113
+ - app/controllers/spree/suggestions_controller.rb
114
+ - app/models/spree/suggestion.rb
115
+ - config/routes.rb
116
+ - db/migrate/20120216142515_create_spree_suggestions.rb
117
+ - lib/generators/spree_autosuggest/install/install_generator.rb
118
+ - lib/key_switcher.rb
119
+ - lib/spree/suggestion_configuration.rb
120
+ - lib/spree_autosuggest.rb
121
+ - lib/spree_autosuggest/engine.rb
122
+ - lib/tasks/seed.rake
123
+ - script/rails
124
+ - spec/spec_helper.rb
125
+ - spree_autosuggest.gemspec
126
+ homepage:
127
+ licenses: []
128
+ metadata: {}
129
+ post_install_message:
130
+ rdoc_options: []
131
+ require_paths:
132
+ - lib
133
+ required_ruby_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: 1.8.7
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ requirements:
144
+ - none
145
+ rubyforge_project:
146
+ rubygems_version: 2.2.2
147
+ signing_key:
148
+ specification_version: 4
149
+ summary: Search suggestions for Spree
150
+ test_files:
151
+ - spec/spec_helper.rb