mindtrick 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 35514c456a9f75e1cde8529243ede1c9c04281db
4
+ data.tar.gz: 2f8d34721606d5abdc48804ca87137e2a40fd835
5
+ SHA512:
6
+ metadata.gz: 5ba82350e58c9bfa47dd2131bc225c19d566fbaeef87664f2b4b41cf3d9ccee46075b5b7827a4a62c0e000a09aae241c423a2b64f5e2e360ade54a44da0bed0d
7
+ data.tar.gz: 50289e71b97fb2f349481f5daf0ab78ab3db5ee3e2d2d1b1fa191dc3359f5a43dcfcea067a3f11b2458a477cf3e74e7185f0200809ac3cc829049b33c343f41a
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Josh Lewis
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # Mindtrick
2
+
3
+ Mindtrick is a Ruby library for search suggestion completion.
4
+
5
+ Mindtrick uses Redis's sorted sets to store and track popularity of completed
6
+ terms relative to incomplete search terms. As more users search, the more
7
+ helpful the suggestions become.
8
+
9
+ ## Usage
10
+
11
+ Require Mindtrick and setup a new instance:
12
+ ```ruby
13
+ require 'mindtrick'
14
+ mt = Mindtrick.new
15
+ ```
16
+
17
+ Each time a user submits a search, add the query:
18
+ ```ruby
19
+ mt.add('these are not the droids you are looking for')
20
+ ```
21
+
22
+ When the user inputs a few letters, get a list of suggestions:
23
+ ```ruby
24
+ mt.suggest('T')
25
+ # => ['these are not the droids you are looking for']
26
+ ```
27
+
28
+ Terms are scored, so that the most frequently searched terms are suggested:
29
+ ```ruby
30
+ ['foo', 'foobar', 'foobar', 'foosball', 'foosball', 'foosball'].each do |term|
31
+ mt.add(term)
32
+ end
33
+
34
+ mt.suggest('foo', 2)
35
+ # => ['foosball', 'foobar']
36
+ ```
37
+
38
+ ## Options
39
+
40
+ ### redis
41
+
42
+ You can tell Mindtrick what redis instance you want to connect to. By default,
43
+ Mindtrick assumes Redis is on localhost with default credentials.
44
+
45
+ ```ruby
46
+ Mindtrick.new(redis: Redis.new(url: "redis://..."))
47
+ ```
48
+
49
+ ### prefix
50
+
51
+ By default, all sets created by Mindtrick are prefixed with 'mndtrk'.
52
+ You can change this if you don't like it, or if you have multiple suggestion
53
+ contexts:
54
+
55
+ ```ruby
56
+ Mindtrick.new(prefix: 'searchbox1')
57
+ ```
58
+
59
+ ### max_terms
60
+
61
+ To prevent balooning of your Redis instance, we prune unpopular terms so that
62
+ there are only 250 terms per prefix. This number can be tweaked to your liking.
63
+
64
+ ```ruby
65
+ Mindtrick.new(max_terms: 100)
66
+ ```
67
+
68
+ ### max_length
69
+
70
+ Again, to prevent balooning of your Redis instance, Mindtrick doesn't keep
71
+ suggestions over 15 characters in length. You can tweak this as needed:
72
+
73
+ ```ruby
74
+ Mindtrick.new(max_length: 20)
75
+ ```
76
+
77
+ ## Installation
78
+
79
+ Add this line to your application's Gemfile:
80
+
81
+ ```ruby
82
+ gem 'mindtrick'
83
+ ```
84
+
85
+ And then execute:
86
+
87
+ ```bash
88
+ $ bundle
89
+ ```
90
+
91
+ Or install it yourself as:
92
+
93
+ ```bash
94
+ $ gem install mindtrick
95
+ ```
96
+
97
+ ## Contributing
98
+
99
+ 1. Fork it ( https://github.com/[my-github-username]/mindtrick/fork )
100
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
101
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
102
+ 4. Push to the branch (`git push origin my-new-feature`)
103
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rake/testtask'
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ t.pattern = 'test/**/*_test.rb'
7
+ end
8
+
9
+ task :default => :test
10
+
@@ -0,0 +1,40 @@
1
+ module Mindtrick
2
+ class Set
3
+
4
+ attr_reader :redis, :prefix, :max_terms, :max_length
5
+ def initialize(opts = {})
6
+ @redis = opts[:redis] || Redis.new
7
+ @prefix = opts[:prefix] || 'mndtrk'
8
+ @max_terms = opts[:max_terms] || 250
9
+ @max_length = opts[:max_length] || 15
10
+ end
11
+
12
+ def add(term)
13
+ term = Text.new(term)
14
+ term.each_fragment do |f|
15
+ if f.length <= max_length
16
+ k = f.prefixed(prefix)
17
+ redis.zincrby(k, 1, term)
18
+ enforce_term_limit(k)
19
+ end
20
+ end
21
+ term
22
+ end
23
+
24
+ def suggest(partial, count = 10)
25
+ key = Text.new(partial).prefixed(prefix)
26
+ redis.zrevrange key, 0, count
27
+ end
28
+
29
+ private
30
+
31
+ def enforce_term_limit(key)
32
+ if (over = redis.zcount(key, 0, '+inf') - max_terms) > 0
33
+ partials = redis.zrange(key, 0, over * 3).sample(over)
34
+ redis.zrem(key, partials)
35
+ end
36
+ end
37
+
38
+
39
+ end
40
+ end
@@ -0,0 +1,19 @@
1
+ module Mindtrick
2
+ class Text < String
3
+ def each_fragment
4
+ return to_enum(:each_fragment) unless block_given?
5
+ (0..term.length).each do |i|
6
+ yield term[0...i]
7
+ end
8
+ end
9
+
10
+ def term
11
+ strip.gsub(/\s+/,' ')
12
+ end
13
+
14
+ def prefixed(prefix)
15
+ "#{ prefix }:#{ downcase }"
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ module Mindtrick
2
+ VERSION = "0.0.2"
3
+ end
data/lib/mindtrick.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'redis'
2
+
3
+ require 'mindtrick/version'
4
+ require 'mindtrick/text'
5
+ require 'mindtrick/set'
6
+
7
+ module Mindtrick
8
+ end
data/mindtrick.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mindtrick/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "mindtrick"
8
+ spec.version = Mindtrick::VERSION
9
+ spec.authors = ["Josh Lewis"]
10
+ spec.email = ["josh.w.lewis@gmail.com"]
11
+ spec.summary = %q{Search completion suggestions using Redis's sorted sets.}
12
+ spec.description = %q{Search completion suggestions using Redis's sorted sets.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^test/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'redis', '~> 3.0'
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1.6'
24
+ spec.add_development_dependency 'rake', '~> 10.0'
25
+ spec.add_development_dependency 'minitest', '~> 5.0'
26
+ end
data/test/set_test.rb ADDED
@@ -0,0 +1,83 @@
1
+ require 'test_helper'
2
+
3
+ class SetTest < Minitest::Test
4
+
5
+ attr_accessor :max_length, :max_terms
6
+ def set
7
+ Mindtrick::Set.new(redis: redis, prefix: prefix, max_length: max_length,
8
+ max_terms: max_terms)
9
+ end
10
+
11
+ def prefix
12
+ @prefix ||= 'mndtrk-tst'
13
+ end
14
+
15
+ def redis
16
+ @redis ||= Redis.new
17
+ end
18
+
19
+ def build_samples
20
+ %w{fo foo foobar foobar foosball foosball foosball }.each do |term|
21
+ set.add(term)
22
+ end
23
+ end
24
+
25
+
26
+ def test_add
27
+ set.add('qux')
28
+ assert_includes set.suggest('q'), 'qux'
29
+ end
30
+
31
+ def test_suggest
32
+ build_samples
33
+ results = set.suggest "foo"
34
+ assert_equal results.first, 'foosball'
35
+ assert_equal results.last, 'foo'
36
+ refute_includes results, 'fo'
37
+ end
38
+
39
+ def test_max_length_default
40
+ self.max_length = nil
41
+ assert stores_length(15)
42
+ refute stores_length(16)
43
+ end
44
+
45
+ def test_max_length_option
46
+ self.max_length = 4
47
+ assert stores_length(4)
48
+ refute stores_length(5)
49
+ end
50
+
51
+ def stores_length(l)
52
+ phrase = random_string(l * 2)
53
+ set.add(phrase)
54
+ 0 < redis.zcard("#{ prefix }:#{ phrase[0...l] }")
55
+ end
56
+
57
+ def test_max_terms_default
58
+ self.max_terms = nil
59
+ assert_max_terms(250)
60
+ end
61
+
62
+ def test_max_terms_option
63
+ self.max_terms = 40
64
+ assert_max_terms(40)
65
+ end
66
+
67
+ def assert_max_terms(max)
68
+ (max + 25).times do
69
+ set.add("foo#{ random_string(8) }")
70
+ end
71
+ assert_equal max, redis.zcount("#{ prefix }:foo", '-inf', '+inf')
72
+ end
73
+
74
+ def random_string(chars)
75
+ (0...chars).map { (97 + rand(26)).chr }.join
76
+ end
77
+
78
+ def teardown
79
+ keys = redis.keys("#{ prefix }:*")
80
+ redis.del keys
81
+ end
82
+
83
+ end
@@ -0,0 +1,4 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/pride'
3
+
4
+ require 'mindtrick'
data/test/text_test.rb ADDED
@@ -0,0 +1,25 @@
1
+ require 'test_helper'
2
+
3
+ class TextTest < Minitest::Test
4
+ def test_each_fragment_enumerator
5
+ text = Mindtrick::Text.new("abcd")
6
+ assert_kind_of Enumerator, text.each_fragment
7
+ end
8
+
9
+ def test_each_fragment_map
10
+ text = Mindtrick::Text.new(" abc de")
11
+ expected = ['', 'a', 'ab', 'abc', 'abc ', 'abc d', 'abc de']
12
+ result = text.each_fragment.map(&:to_s)
13
+ assert_equal expected, result
14
+ end
15
+
16
+ def test_term
17
+ text = Mindtrick::Text.new(" the dude \t abides. ")
18
+ assert_equal text.term, "the dude abides."
19
+ end
20
+
21
+ def test_prefix
22
+ text = Mindtrick::Text.new("bar")
23
+ assert_equal text.prefixed('foo'), "foo:bar"
24
+ end
25
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mindtrick
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Josh Lewis
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redis
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.6'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.0'
69
+ description: Search completion suggestions using Redis's sorted sets.
70
+ email:
71
+ - josh.w.lewis@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - Gemfile
78
+ - LICENSE.txt
79
+ - README.md
80
+ - Rakefile
81
+ - lib/mindtrick.rb
82
+ - lib/mindtrick/set.rb
83
+ - lib/mindtrick/text.rb
84
+ - lib/mindtrick/version.rb
85
+ - mindtrick.gemspec
86
+ - test/set_test.rb
87
+ - test/test_helper.rb
88
+ - test/text_test.rb
89
+ homepage: ''
90
+ licenses:
91
+ - MIT
92
+ metadata: {}
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubyforge_project:
109
+ rubygems_version: 2.2.0
110
+ signing_key:
111
+ specification_version: 4
112
+ summary: Search completion suggestions using Redis's sorted sets.
113
+ test_files:
114
+ - test/set_test.rb
115
+ - test/test_helper.rb
116
+ - test/text_test.rb
117
+ has_rdoc: