soulheart 0.0.2 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eb5870c5f1cf50c4ebac25363602c2f3cf0c4dd8
4
- data.tar.gz: ab86a41e246856d395eacc5b896f36f2cb0ed254
3
+ metadata.gz: b170565afcbbfc53d9ab5f0a46713af88803a3a4
4
+ data.tar.gz: 58445b7ecdf5713487ba1af3539ad06137a04877
5
5
  SHA512:
6
- metadata.gz: 83fff39abcf059750d36f89a5aae935e2a8cc579cbfa248b0ede8abe29b2d79316ec25f486415132c8570e4a6a05f8a9300d5a8f41eea1517aeaeaede5830d31
7
- data.tar.gz: 5f1ec6654a3b373ea5a4ab686005b85484e4a11d2ffd7b7e00d39932121736475d5d8176801f8f3823500780c14d4283f6c749ab663caed75ebefe0337f59ef1
6
+ metadata.gz: 169f0de49bcd01b4ec5b2182f0075e220ea25b0059f16bffb9347567a1012d2925c6efef9a35b212825ba6989a97e60f18970b47f8d5df2e9bed2ea6dedf7fb6
7
+ data.tar.gz: b5b5de55c31b6cd03be318f05c08780302b3e1eee20b124e4eda1b737ffba4eee526b051d6969643ee7709b9f90123c485b21f9efdcdf2c9677c6fe0e66201be
@@ -1,70 +1,76 @@
1
- Soulheart [![Build Status](https://travis-ci.org/sethherr/soulheart.svg)](https://travis-ci.org/sethherr/soulheart) [![Code Climate](https://codeclimate.com/github/sethherr/soulheart/badges/gpa.svg)](https://codeclimate.com/github/sethherr/soulheart) [![Test Coverage](https://codeclimate.com/github/sethherr/soulheart/badges/coverage.svg)](https://codeclimate.com/github/sethherr/soulheart/coverage)
2
- ========
1
+ # <img src="https://raw.githubusercontent.com/sethherr/soulheart/master/logo.png" alt="Soulheart" width="200"> Soulheart [![Build Status](https://travis-ci.org/sethherr/soulheart.svg)](https://travis-ci.org/sethherr/soulheart) [![Code Climate](https://codeclimate.com/github/sethherr/soulheart/badges/gpa.svg)](https://codeclimate.com/github/sethherr/soulheart) [![Test Coverage](https://codeclimate.com/github/sethherr/soulheart/badges/coverage.svg)](https://codeclimate.com/github/sethherr/soulheart/coverage)
3
2
 
4
- This is an updated fork of [Seatgeek/Soulmate](https://github.com/seatgeek/soulmate) to address a few issues - namely [CORS support](../../issues/2), [minimum entry length](../../issues/3) and [playing better with Selectize & Select2](../../issues/4) - also the future.
5
3
 
6
- Since [Seatgeek no longer uses Soulmate](https://news.ycombinator.com/item?id=9317891), and this is a pretty large rewrite that doesn't emphasize backward compatibility, it's a new project and gem.
4
+ **Soulheart is a ready to use remote data source for autocomplete**. It supports:
7
5
 
8
- ========
6
+ - pagination
7
+ - categories
8
+ - sorting by priority (not just alphabetically)
9
+ - arbitrary returns
10
+ - loading data via gists
11
+ - mounting standalone or inside of a rails app
9
12
 
10
- **Every item has to have a unique name**
13
+ ... and is [instantly deployable to heroku](https://heroku.com/deploy) (for free).
11
14
 
12
- Loading data:
15
+ To get started, check out examples and documentation at [sethherr.github.io/soulheart](https://sethherr.github.io/soulheart).
13
16
 
14
- Pushing new options via rake task. Use a gist url! Do it with .csvs!
15
17
 
16
- Structuring data
18
+ ---
17
19
 
18
- The only required attribute is term, which is the search term to go by. So this would be a valid thing:
20
+ ### Adding data
19
21
 
20
- ```javascript
21
- [
22
- { "term": "Something sweet" },
23
- { "term": "The color blue" }
24
- ]
25
- ```
22
+ You can add data from json, CSV and TSV files.
26
23
 
27
- Here are all the possible (non-required) values:
24
+ Adding data is very simple - all you need is a `text` value.
28
25
 
26
+ Soulheart uses [line delineated JSON streams](https://en.wikipedia.org/wiki/JSON_Streaming#Line_delimited_JSON), so it doesn't have to load the whole file into memory. Which just means - put each object onto a seperate line.
29
27
 
30
- | Key | Type | Default |
31
- | ----------- | ----- | ---------- |
32
- | `priority` | integer | 100 |
33
- | `types` | string | 'default' |
34
- | `data` | hash | {} |
28
+ For the simplest case, with just text values in JSON:
35
29
 
36
- e.g.
30
+ { "text": "Jamis" }
31
+ { "text": "Specialized" }
32
+ { "text": "Trek" }
37
33
 
38
- ```javascript
39
- [
40
- {
41
- "term": "Something sweet",
42
- "priority": 99,
43
- "types": "niceness", // A set type for searching by type
44
- "data": { // Any key value pair you want, this is the return value
45
- "id": 99,
46
- "url": "http://example.com",
47
- "whatever": "The color purple"
48
- }
49
- },
50
- { "term": "The color blue" }
51
- ]
52
- ```
34
+ It accepts local files:
35
+
36
+ soulheart load my_json_file.json
37
+
38
+ or remote files:
39
+
40
+ soulheart load https://gist.githubusercontent.com/sethherr/96dbc011e508330ceec4/raw/95122b1fc9de85f241cd048f32b94568f54134e0/manufacturers.tsv
41
+
42
+
43
+ In addition to term, there are a few optional values -
53
44
 
54
- *If you set `term` in `data`, it will respond with that rather than the term it searches by. I haven't figured out a use case for this yet, but I'm sure one exists.*
45
+ | Key | Default | What it does |
46
+ | ------------ | ----------- | ------------ |
47
+ | `priority` | `100` | Higher numbers come first |
48
+ | `category` | `'default'` | Sets the category |
49
+ | `data` | `{}` | Returned object from search - the text and category will be added to this if you don't specify them. |
50
+
51
+ Here is an example of what a possible hash you could pass is
52
+
53
+ { "text": "Jamis", "category": "Bike Manufacturer" }
54
+ { "text": "Specialized" }
55
+ { "text": "Trek" }
56
+
57
+ *If you set `text` in `data`, it will respond with that rather than the term it searches by. I haven't figured out a use case for this yet, but I'm sure one exists.*
55
58
 
56
59
  ======
57
60
 
58
- I'm testing with:
61
+ I'm testing with: `ruby >= 2.1` and `redis >= 3`.
59
62
 
60
- - ruby >= 2.1
61
- - redis >= 3
63
+ Run `bundle exec guard` to run the specs while you work, it will just test the files you change.
62
64
 
63
- To test, run `bundle exec guard`. It will live reload the files you change.
65
+ This repo includes a `config.ru` and a `Gemfile.lock` so it (and any forks of it) can be deployed to heroku. They shouldn't be in the Gem itself.
64
66
 
65
67
 
66
68
  ======
67
69
 
70
+ This is an updated fork of [Seatgeek/Soulmate](https://github.com/seatgeek/soulmate) to address a few issues - namely [CORS support](../../issues/2), [minimum entry length](../../issues/3) and [playing better with Selectize & Select2](../../issues/4) - also the future.
71
+
72
+ Since [Seatgeek no longer uses Soulmate](https://news.ycombinator.com/item?id=9317891), and this isn't backward compatible it's a new project and gem.
73
+
68
74
  :x::o::x::o::x::o: *Soulmate's README follows ([issue for making new documentation](../../issues/1))*
69
75
 
70
76
  Soulmate is a tool to help solve the common problem of developing a fast autocomplete feature. It uses Redis's sorted sets to build an index of partially completed words and the corresponding top matching items, and provides a simple sinatra app to query them. Soulmate finishes your sentences.
data/Rakefile CHANGED
@@ -1,29 +1,29 @@
1
1
  #!/usr/bin/env rake
2
2
 
3
- require "bundler/gem_helper"
3
+ require 'bundler/gem_helper'
4
4
 
5
5
  begin
6
6
  Bundler.setup(:default, :development)
7
7
  rescue Bundler::BundlerError => e
8
8
  $stderr.puts e.message
9
- $stderr.puts "Run `bundle install` to install missing gems"
9
+ $stderr.puts 'Run `bundle install` to install missing gems'
10
10
  exit e.status_code
11
11
  end
12
12
  require 'rake'
13
13
 
14
- REDIS_DIR = File.expand_path(File.join("..", "test"), __FILE__)
15
- REDIS_CNF = File.join(REDIS_DIR, "test.conf")
16
- REDIS_PID = File.join(REDIS_DIR, "db", "redis.pid")
14
+ REDIS_DIR = File.expand_path(File.join('..', 'test'), __FILE__)
15
+ REDIS_CNF = File.join(REDIS_DIR, 'test.conf')
16
+ REDIS_PID = File.join(REDIS_DIR, 'db', 'redis.pid')
17
17
  REDIS_LOCATION = ENV['REDIS_LOCATION']
18
18
 
19
- desc "Run rcov and manage server start/stop"
20
- task :rcoverage => [:start, :rcov, :stop]
19
+ desc 'Run rcov and manage server start/stop'
20
+ task rcoverage: [:start, :rcov, :stop]
21
21
 
22
- desc "Start the Redis server"
22
+ desc 'Start the Redis server'
23
23
  task :start do
24
24
  redis_running = \
25
25
  begin
26
- File.exists?(REDIS_PID) && Process.kill(0, File.read(REDIS_PID).to_i)
26
+ File.exist?(REDIS_PID) && Process.kill(0, File.read(REDIS_PID).to_i)
27
27
  rescue Errno::ESRCH
28
28
  FileUtils.rm REDIS_PID
29
29
  false
@@ -36,21 +36,20 @@ task :start do
36
36
  end
37
37
  end
38
38
 
39
- desc "Stop the Redis server"
39
+ desc 'Stop the Redis server'
40
40
  task :stop do
41
- if File.exists?(REDIS_PID)
42
- Process.kill "INT", File.read(REDIS_PID).to_i
41
+ if File.exist?(REDIS_PID)
42
+ Process.kill 'INT', File.read(REDIS_PID).to_i
43
43
  FileUtils.rm REDIS_PID
44
44
  end
45
45
  end
46
46
 
47
+ require 'rspec/core/rake_task'
47
48
 
48
- require "rspec/core/rake_task"
49
-
50
- desc "Run all specs"
49
+ desc 'Run all specs'
51
50
  RSpec::Core::RakeTask.new(:spec) do |t|
52
51
  t.rspec_opts = %w(--color)
53
52
  t.verbose = false
54
53
  end
55
54
 
56
- task :default => :spec
55
+ task default: :spec
@@ -12,95 +12,45 @@ require 'optparse'
12
12
  require 'tempfile'
13
13
 
14
14
  parser = OptionParser.new do |opts|
15
- opts.banner = "Usage: soulheart [options] COMMAND"
15
+ opts.banner = 'Usage: soulheart [options] COMMAND'
16
16
 
17
- opts.separator ""
18
- opts.separator "Options:"
17
+ opts.separator ''
18
+ opts.separator 'Options:'
19
19
 
20
- opts.on("-r", "--redis [HOST:PORT]", "Redis connection string") do |host|
20
+ opts.on('-r', '--redis [HOST:PORT]', 'Redis connection string') do |host|
21
21
  Soulheart.redis = host
22
22
  end
23
23
 
24
- opts.on("-s", "--stop-words [FILE]", "Path to file containing a list of stop words") do |fn|
24
+ opts.on('-s', '--stop-words [FILE]', 'Path to file containing a list of stop words') do |fn|
25
25
  File.open(fn) do |file|
26
- Soulheart.stop_words = file.readlines.map{ |l| l.strip }.reject{ |w| w.empty? }
26
+ Soulheart.stop_words = file.readlines.map(&:strip).reject(&:empty?)
27
27
  end
28
28
  end
29
29
 
30
- opts.on("-h", "--help", "Show this message") do
30
+ opts.on('-h', '--help', 'Show this message') do
31
31
  puts opts
32
32
  exit
33
33
  end
34
34
 
35
- opts.on("-b", "--batch-size", "Number of lines to read at a time") do |size|
35
+ opts.on('-b', '--batch-size', 'Number of lines to read at a time') do |size|
36
36
  BATCH_SIZE = size
37
37
  end
38
38
 
39
- opts.separator ""
40
- opts.separator "Commands:"
41
- opts.separator " load TYPE FILE Replaces collection specified by TYPE with items read from FILE in the JSON lines format."
42
- opts.separator " add TYPE Adds items to collection specified by TYPE read from stdin in the JSON lines format."
39
+ opts.separator ''
40
+ opts.separator 'Commands:'
41
+ opts.separator ' load TYPE FILE Replaces collection specified by TYPE with items read from FILE in the JSON lines format.'
42
+ opts.separator ' add TYPE Adds items to collection specified by TYPE read from stdin in the JSON lines format.'
43
43
  opts.separator " remove TYPE Removes items from collection specified by TYPE read from stdin in the JSON lines format. Items only require an 'id', all other fields are ignored."
44
- opts.separator " query TYPE QUERY Queries for items from collection specified by TYPE."
44
+ opts.separator ' query TYPE QUERY Queries for items from collection specified by TYPE.'
45
45
  end
46
46
 
47
- def generate(type, file)
48
- include Soulheart::Helpers
49
-
50
- begin
51
- temp = Tempfile.new("soulheart")
52
-
53
- if File.exists?(file)
54
- start_time = Time.now.to_i
55
- base = "soulheart-index:#{type}"
56
- database = "soulheart-data:#{type}"
57
- # hset = "*4\r\n$4\r\nHSET\r\n$#{database.length}\r\n#{database}\r\n$"
58
- # del = "*2\r\n$3\r\nDEL\r\n$"
59
- begin
60
- f = File.open(file)
61
- # cleanup
62
- phrases = Soulheart.redis.smembers(base)
63
- phrases.each do |phrase|
64
- temp << gen_redis_proto("DEL", phrase)
65
- # temp << del + phrase.length.to_s + "\r\n" + phrase + "\r\n"
66
- end
67
- temp << gen_redis_proto("DEL", base)
68
- # temp << del + base.length.to_s + "\r\n" + base + "\r\n"
69
- while !f.eof?
70
- line = f.gets.chomp
71
- line =~ /"id":(\d+)/
72
- id = $1
73
- line =~ /"score":(\d+)/
74
- score = $1
75
- json = MultiJson.decode(line)
76
- temp << gen_redis_proto("HSET", database, id, line)
77
- # temp << hset + $1.length.to_s + "\r\n" + $1 + "\r\n$" + line.length.to_s + "\r\n" + line + "\r\n"
78
- phrase = json.key?("aliases") ? json["term"] + " " + json["aliases"] : json["term"]
79
- prefixes_for_phrase(phrase).each do |p|
80
- temp << gen_redis_proto("SADD", base, p)
81
- temp << gen_redis_proto("ZADD", base + ":" + p, score, id)
82
- end
83
- end
84
- ensure
85
- f.close
86
- end
87
- puts "Converted in #{Time.now.to_i - start_time} second(s)"
88
- puts "Importing into redis ..."
89
- `time redis-cli --pipe < #{temp.path}`
90
- else
91
- puts "Couldn't open file: #{file}"
92
- end
93
- ensure
94
- temp.close
95
- end
96
- end
97
47
 
98
48
  def load(file)
99
49
  require 'uri'
100
- if file =~ URI::regexp
50
+ if file =~ URI.regexp
101
51
  require 'open-uri'
102
52
  f = open(file)
103
- elsif File.exists?(file)
53
+ elsif File.exist?(file)
104
54
  f = File.open(file)
105
55
  else
106
56
  puts "Couldn't open file: #{file}"
@@ -113,17 +63,17 @@ def load(file)
113
63
  lines = []
114
64
  begin
115
65
  if file.match(/(c|t)sv\z/i)
116
- puts "Reading a CSV"
66
+ puts 'Reading a CSV'
117
67
  require 'csv'
118
- sep = file.match(/tsv\z/i) ? "\t" : ","
68
+ sep = file.match(/tsv\z/i) ? "\t" : ','
119
69
  CSV.foreach(f, headers: true, col_sep: sep) do |row|
120
70
  lines << row.to_hash
121
71
  count += 1
122
72
  end
123
73
  elsif file.match(/json\z/i)
124
- puts "Reading JSON"
74
+ puts 'Reading JSON'
125
75
  puts "Loading items in batches of #{BATCH_SIZE} ..."
126
- while !f.eof?
76
+ until f.eof?
127
77
  lines = []
128
78
  BATCH_SIZE.times do
129
79
  break if f.eof?
@@ -132,7 +82,7 @@ def load(file)
132
82
  end
133
83
  end
134
84
  else
135
- puts "unknown File type"
85
+ puts 'unknown File type'
136
86
  end
137
87
  ensure
138
88
  f.close
@@ -164,7 +114,7 @@ end
164
114
  def query(type, query)
165
115
  puts "> Querying '#{type}' for '#{query}'"
166
116
  matcher = Soulheart::Matcher.new(type)
167
- results = matcher.matches_for_term(query, :limit => 0)
117
+ results = matcher.matches_for_term(query, limit: 0)
168
118
  results.each do |item|
169
119
  puts MultiJson.encode(item)
170
120
  end
@@ -172,10 +122,10 @@ def query(type, query)
172
122
  end
173
123
 
174
124
  def gen_redis_proto(*cmd)
175
- proto = "*"+cmd.length.to_s+"\r\n"
125
+ proto = '*' + cmd.length.to_s + "\r\n"
176
126
  cmd.each{|arg|
177
- proto << "$"+arg.bytesize.to_s+"\r\n"
178
- proto << arg+"\r\n"
127
+ proto << '$' + arg.bytesize.to_s + "\r\n"
128
+ proto << arg + "\r\n"
179
129
  }
180
130
  proto
181
131
  end
@@ -6,23 +6,17 @@ begin
6
6
  rescue LoadError
7
7
  require 'rubygems'
8
8
  require 'vegas'
9
- end
9
+ end
10
10
  require 'soulheart/server'
11
11
 
12
-
13
- Vegas::Runner.new(Soulheart::Server, 'soulheart-web', {
14
- :before_run => lambda {|v|
15
- # path = (ENV['RESQUECONFIG'] || v.args.first)
16
- # load path.to_s.strip if path
17
- }
18
- }) do |runner, opts, app|
19
- opts.on("-r", "--redis [HOST:PORT]", "Redis connection string") do |host|
12
+ Vegas::Runner.new(Soulheart::Server, 'soulheart-web') do |runner, opts|
13
+ opts.on('-r', '--redis [HOST:PORT]', 'Redis connection string') do |host|
20
14
  runner.logger.info "Using Redis connection string '#{host}'"
21
15
  Soulheart.redis = host
22
16
  end
23
- opts.on("-s", "--stop-words [FILE]", "Path to file containing a list of stop words") do |fn|
17
+ opts.on('-s', '--stop-words [FILE]', 'Path to file containing a list of stop words') do |fn|
24
18
  File.open(fn) do |file|
25
- Soulheart.stop_words = file.readlines.map{ |l| l.strip }.reject{ |w| w.empty? }
19
+ Soulheart.stop_words = file.readlines.map(&:strip).reject(&:empty?)
26
20
  end
27
21
  end
28
22
  end
@@ -1,9 +1,7 @@
1
1
  module Soulheart
2
-
3
2
  class Base
4
-
5
3
  include Helpers
6
-
4
+
7
5
  attr_accessor :type
8
6
 
9
7
  def redis
@@ -13,15 +11,15 @@ module Soulheart
13
11
  def cache_length
14
12
  10 * 60 # Setting to 10 minutes, but making it possible to edit down the line
15
13
  end
16
-
14
+
17
15
  def base_id
18
- ENV['RACK_ENV'] != 'test' ? "soulheart:" : "soulheart_test:"
16
+ ENV['RACK_ENV'] != 'test' ? 'soulheart:' : 'soulheart_test:'
19
17
  end
20
18
 
21
19
  def set_category_combos_array
22
20
  redis.expire category_combos_id, 0
23
- ar = redis.smembers(categories_id).map{ |c| normalize(c) }.uniq.sort
24
- ar = 1.upto(ar.size).flat_map {|n| ar.combination(n).map{|el| el.join('')}}
21
+ ar = redis.smembers(categories_id).map { |c| normalize(c) }.uniq.sort
22
+ ar = 1.upto(ar.size).flat_map { |n| ar.combination(n).map { |el| el.join('') } }
25
23
  ar.last.replace('all')
26
24
  redis.sadd category_combos_id, ar
27
25
  ar
@@ -31,15 +29,19 @@ module Soulheart
31
29
  "#{base_id}category_combos:"
32
30
  end
33
31
 
32
+ def category_combos
33
+ redis.smembers(category_combos_id)
34
+ end
35
+
34
36
  def categories_id
35
37
  "#{base_id}categories:"
36
38
  end
37
39
 
38
- def category_id(name='all')
40
+ def category_id(name = 'all')
39
41
  "#{categories_id}#{name}:"
40
42
  end
41
43
 
42
- def no_query_id(category=category_id)
44
+ def no_query_id(category = category_id)
43
45
  "all:#{category}"
44
46
  end
45
47
 
@@ -47,8 +49,8 @@ module Soulheart
47
49
  "#{base_id}database:"
48
50
  end
49
51
 
50
- def cache_id(type='all')
52
+ def cache_id(type = 'all')
51
53
  "#{base_id}cache:#{type}:"
52
54
  end
53
55
  end
54
- end
56
+ end
@@ -3,7 +3,7 @@ require 'redis'
3
3
 
4
4
  module Soulheart
5
5
  module Config
6
- DEFAULT_STOP_WORDS = ["vs", "at", "the"]
6
+ DEFAULT_STOP_WORDS = %w(vs at the)
7
7
 
8
8
  # Accepts:
9
9
  # 1. A Redis URL String 'redis://host:port/db'
@@ -23,14 +23,12 @@ module Soulheart
23
23
  # create a new one.
24
24
  def redis
25
25
  @redis ||= (
26
- url = URI(@redis_url || ENV["REDIS_URL"] || "redis://127.0.0.1:6379/0")
27
- ::Redis.new({
28
- # driver: :hiredis,
26
+ url = URI(@redis_url || ENV['REDIS_URL'] || 'redis://127.0.0.1:6379/0')
27
+ ::Redis.new( # driver: :hiredis,
29
28
  host: url.host,
30
29
  port: url.port,
31
30
  db: url.path[1..-1],
32
- password: url.password
33
- })
31
+ password: url.password)
34
32
  )
35
33
  end
36
34
 
@@ -2,21 +2,17 @@
2
2
 
3
3
  module Soulheart
4
4
  module Helpers
5
- def blank?
6
- respond_to?(:empty?) ? !!empty? : !self
7
- end
8
-
9
5
  def normalize(str)
10
6
  # Letter, Mark, Number, Connector_Punctuation (Chinese, Japanese, etc.)
11
7
  str.downcase.gsub(/[^\p{Word}\ ]/i, '').strip
12
8
  end
13
-
9
+
14
10
  def prefixes_for_phrase(phrase)
15
11
  words = normalize(phrase).split(' ').reject do |w|
16
12
  Soulheart.stop_words.include?(w)
17
13
  end
18
14
  words.map do |w|
19
- (0..(w.length-1)).map{ |l| w[0..l] }
15
+ (0..(w.length - 1)).map { |l| w[0..l] }
20
16
  end.flatten.uniq
21
17
  end
22
18
  end
@@ -1,7 +1,5 @@
1
1
  module Soulheart
2
-
3
2
  class Loader < Base
4
-
5
3
  def default_items_hash(text, category)
6
4
  category ||= 'default'
7
5
  {
@@ -12,7 +10,7 @@ module Soulheart
12
10
  'data' => {
13
11
  'text' => text,
14
12
  'category' => category
15
- },
13
+ }
16
14
  }
17
15
  end
18
16
 
@@ -57,31 +55,36 @@ module Soulheart
57
55
  end
58
56
 
59
57
  def clean(item)
60
- raise ArgumentError, "Items must have text" unless item["text"]
61
- default_items_hash(item.delete('text'), item.delete('category')).
62
- tap{ |i| i['data'].merge!(item.delete('data')) if item['data'] }.
63
- tap{ |i| i['priority'] = item.delete('priority').to_f if item['priority'] }.
64
- merge item
58
+ fail ArgumentError, 'Items must have text' unless item['text']
59
+ default_items_hash(item.delete('text'), item.delete('category'))
60
+ .tap { |i| i['data'].merge!(item.delete('data')) if item['data'] }
61
+ .tap { |i| i['priority'] = item.delete('priority').to_f if item['priority'] }
62
+ .merge item
65
63
  end
66
64
 
67
- def add_item(item, category_base_id=nil, cleaned: false)
65
+ def add_item(item, category_base_id = nil, cleaned: false)
68
66
  unless cleaned
69
67
  item = clean(item)
70
68
  category_base_id ||= category_id(item['category'])
69
+ item.keys.select{ |k| k[/data-/i] }.each do |key|
70
+ item['data'].merge!({
71
+ "#{key.gsub(/data-/i,'')}" => item.delete(key)
72
+ })
73
+ end
71
74
  unless redis.smembers(categories_id).include?(item['category'])
72
75
  redis.sadd categories_id, item['category']
73
76
  end
74
77
  end
75
78
  redis.pipelined do
76
- redis.zadd(no_query_id(category_base_id), item["priority"], item["term"]) # Add to master set for queryless searches
79
+ redis.zadd(no_query_id(category_base_id), item['priority'], item['term']) # Add to master set for queryless searches
77
80
  # store the raw data in a separate key to reduce memory usage, if it's cleaned it's done
78
81
  redis.hset(results_hashes_id, item['term'], MultiJson.encode(item['data'])) unless cleaned
79
- phrase = ([item["term"]] + (item["aliases"] || [])).join(' ')
82
+ phrase = ([item['term']] + (item['aliases'] || [])).join(' ')
80
83
  # Store all the prefixes
81
84
  prefixes_for_phrase(phrase).each do |p|
82
85
  redis.sadd(base_id, p) unless cleaned # remember prefix in a master set
83
86
  # store the normalized term in the index for each of the categories
84
- redis.zadd("#{category_base_id}#{p}", item["priority"], item["term"])
87
+ redis.zadd("#{category_base_id}#{p}", item['priority'], item['term'])
85
88
  end
86
89
  end
87
90
  item
@@ -89,19 +92,19 @@ module Soulheart
89
92
 
90
93
  # remove only cares about an item's id, but for consistency takes an object
91
94
  def remove(item)
92
- prev_item = Soulheart.redis.hget(base_id, item["term"])
95
+ prev_item = Soulheart.redis.hget(base_id, item['term'])
93
96
  if prev_item
94
97
  prev_item = MultiJson.decode(prev_item)
95
98
  # undo the operations done in add
96
99
  Soulheart.redis.pipelined do
97
- Soulheart.redis.hdel(base_id, prev_item["term"])
98
- phrase = ([prev_item["term"]] + (prev_item["aliases"] || [])).join(' ')
100
+ Soulheart.redis.hdel(base_id, prev_item['term'])
101
+ phrase = ([prev_item['term']] + (prev_item['aliases'] || [])).join(' ')
99
102
  prefixes_for_phrase(phrase).each do |p|
100
103
  Soulheart.redis.srem(base_id, p)
101
- Soulheart.redis.zrem("#{base_id}:#{p}", prev_item["term"])
104
+ Soulheart.redis.zrem("#{base_id}:#{p}", prev_item['term'])
102
105
  end
103
106
  end
104
107
  end
105
108
  end
106
109
  end
107
- end
110
+ end
@@ -1,28 +1,29 @@
1
1
  module Soulheart
2
-
3
2
  class Matcher < Base
4
- def initialize(params={})
3
+ def initialize(params = {})
5
4
  @opts = self.class.default_params_hash.merge params
6
5
  clean_opts
7
6
  end
8
7
 
8
+ attr_accessor :opts
9
+
9
10
  def self.default_params_hash
10
11
  {
11
12
  'page' => 1,
12
13
  'per_page' => 5,
13
14
  'categories' => [],
14
- 'query' => '',
15
+ 'q' => '', # Query
15
16
  'cache' => true
16
17
  }
17
18
  end
18
19
 
19
20
  def clean_opts
20
21
  unless @opts['categories'] == '' || @opts['categories'] == []
21
- @opts['categories'] = @opts['categories'].split(/,|\+/) unless @opts['categories'].kind_of?(Array)
22
- @opts['categories'] = @opts['categories'].map{ |s| normalize(s) }.uniq.sort
22
+ @opts['categories'] = @opts['categories'].split(/,|\+/) unless @opts['categories'].is_a?(Array)
23
+ @opts['categories'] = @opts['categories'].map { |s| normalize(s) }.uniq.sort
23
24
  @opts['categories'] = [] if @opts['categories'].length == redis.scard(categories_id)
24
25
  end
25
- @opts['query'] = normalize(@opts['query']).split(' ')
26
+ @opts['q'] = normalize(@opts['q']).split(' ') unless @opts['q'].is_a?(Array)
26
27
  # .reject{ |i| i && i.length > 0 } .split(' ').reject{ Soulmate.stop_words.include?(w) }
27
28
  @opts
28
29
  end
@@ -36,12 +37,12 @@ module Soulheart
36
37
  end
37
38
 
38
39
  def cache_id_from_opts
39
- "#{cache_id(categories_string)}#{@opts['query'].join(':')}"
40
+ "#{cache_id(categories_string)}#{@opts['q'].join(':')}"
40
41
  end
41
42
 
42
43
  def interkeys_from_opts(cid)
43
44
  # If there isn't a query, we use a special key in redis
44
- @opts['query'].empty? ? [no_query_id(cid)] : @opts['query'].map { |w| "#{cid}#{w}" }
45
+ @opts['q'].empty? ? [no_query_id(cid)] : @opts['q'].map { |w| "#{cid}#{w}" }
45
46
  end
46
47
 
47
48
  def matches
@@ -55,17 +56,16 @@ module Soulheart
55
56
  end
56
57
  offset = (@opts['page'].to_i - 1) * @opts['per_page'].to_i
57
58
  limit = @opts['per_page'].to_i + offset - 1
58
-
59
+
59
60
  limit = 0 if limit < 0
60
61
  ids = redis.zrevrange(cachekey, offset, limit) # Using 'ids', even though keys are now terms
61
62
  if ids.size > 0
62
63
  results = redis.hmget(results_hashes_id, *ids)
63
- results = results.reject{ |r| r.nil? } # handle cached results for ids which have since been deleted
64
+ results = results.reject(&:nil?) # handle cached results for ids which have since been deleted
64
65
  results.map { |r| MultiJson.decode(r) }
65
66
  else
66
67
  []
67
- end
68
+ end
68
69
  end
69
-
70
70
  end
71
- end
71
+ end
@@ -1,33 +1,30 @@
1
1
  require 'sinatra/base'
2
2
  require 'soulheart'
3
- require 'rack/contrib'
4
3
 
5
4
  module Soulheart
6
-
7
5
  class Server < Sinatra::Base
8
6
  include Helpers
9
-
7
+
10
8
  before do
11
- content_type 'application/json', :charset => 'utf-8'
9
+ content_type 'application/json', charset: 'utf-8'
12
10
  headers['Access-Control-Allow-Origin'] = '*'
13
11
  headers['Access-Control-Allow-Methods'] = 'POST, PUT, GET, OPTIONS'
14
12
  headers['Access-Control-Request-Method'] = '*'
15
13
  headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, Authorization'
16
14
  end
17
-
15
+
18
16
  get '/' do
19
17
  matches = Matcher.new(params).matches
20
- MultiJson.encode({ matches: matches })
18
+ MultiJson.encode(matches: matches)
21
19
  end
22
20
 
23
- get '/status' do
24
- MultiJson.encode({ soulheart: Soulheart::VERSION, :status => "ok" })
21
+ get '/status' do
22
+ MultiJson.encode(soulheart: Soulheart::VERSION, status: 'ok')
25
23
  end
26
-
24
+
27
25
  not_found do
28
- content_type 'application/json', :charset => 'utf-8'
29
- MultiJson.encode({ :error => "not found" })
26
+ content_type 'application/json', charset: 'utf-8'
27
+ MultiJson.encode(error: 'not found')
30
28
  end
31
-
32
29
  end
33
30
  end
@@ -1,3 +1,3 @@
1
1
  module Soulheart
2
- VERSION = "0.0.2"
3
- end
2
+ VERSION = '0.0.4'
3
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: soulheart
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Seth Herr
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-17 00:00:00.000000000 Z
11
+ date: 2015-06-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hiredis
@@ -80,20 +80,6 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: rack-contrib
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
83
  - !ruby/object:Gem::Dependency
98
84
  name: rake
99
85
  requirement: !ruby/object:Gem::Requirement
@@ -145,13 +131,8 @@ executables:
145
131
  extensions: []
146
132
  extra_rdoc_files: []
147
133
  files:
148
- - ".gitignore"
149
- - ".rspec"
150
- - ".travis.yml"
151
- - Gemfile
152
- - Guardfile
153
134
  - LICENSE.md
154
- - README.markdown
135
+ - README.md
155
136
  - Rakefile
156
137
  - bin/soulheart
157
138
  - bin/soulheart-web
@@ -163,14 +144,6 @@ files:
163
144
  - lib/soulheart/matcher.rb
164
145
  - lib/soulheart/server.rb
165
146
  - lib/soulheart/version.rb
166
- - logo.png
167
- - soulheart.gemspec
168
- - spec/fixtures/multiple_categories.json
169
- - spec/soulheart/loader_spec.rb
170
- - spec/soulheart/matcher_spec.rb
171
- - spec/soulheart/server_spec.rb
172
- - spec/soulheart_spec.rb
173
- - spec/spec_helper.rb
174
147
  homepage: https://github.com/sethherr/soulheart
175
148
  licenses:
176
149
  - MIT
data/.gitignore DELETED
@@ -1,21 +0,0 @@
1
- # rdoc generated
2
- rdoc
3
-
4
- # yard generated
5
- doc
6
- .yardoc
7
-
8
- # bundler
9
- .bundle
10
-
11
- gem
12
-
13
- test/db/*.rdb
14
-
15
- dump.rdb
16
- tmp/
17
-
18
- Gemfile.lock
19
-
20
- .rvmrc
21
- .ruby-*
data/.rspec DELETED
@@ -1,2 +0,0 @@
1
- --color
2
- --format documentation
@@ -1,12 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.1.0
4
- bundler_args: "--without=guard"
5
- notifications:
6
- disabled: true
7
- script:
8
- - bundle exec rake spec
9
- # - bundle exec rubocop
10
- addons:
11
- code_climate:
12
- repo_token: e72d8fa152922cef05c311cd49d3db9016b82486b712399a8e7c7da2af5e071e
data/Gemfile DELETED
@@ -1,22 +0,0 @@
1
- source 'http://rubygems.org'
2
-
3
- group :development do
4
- gem 'rspec', '~> 2.14.1'
5
- gem 'bundler'
6
- gem 'guard'
7
- gem 'guard-rspec', '~> 4.2.8'
8
- gem 'rack-contrib'
9
- gem 'rubocop'
10
- end
11
-
12
- group :test do
13
- gem 'rack-test'
14
- gem "codeclimate-test-reporter", require: nil
15
- end
16
-
17
- gem 'rake'
18
- gem 'redis', '>= 3.0.1'
19
- gem 'hiredis', '~> 0.4.5'
20
- gem 'vegas', '>= 0.1.0'
21
- gem 'sinatra'
22
- gem 'multi_json', '>= 1.11.0'
data/Guardfile DELETED
@@ -1,9 +0,0 @@
1
- rspec_opts = {
2
- failed_mode: :focus
3
- }
4
-
5
- guard :rspec, cmd: "bundle exec rspec" do
6
- watch(%r{^spec/.+_spec\.rb$})
7
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
8
- watch(%r{^lib/soulheart/(.+)\.rb$}) { |m| "spec/soulheart/#{m[1]}_spec.rb" }
9
- end
data/logo.png DELETED
Binary file
@@ -1,24 +0,0 @@
1
- # -*- encoding: utf-8 -*-
2
- require File.expand_path('../lib/soulheart/version', __FILE__)
3
-
4
- Gem::Specification.new do |gem|
5
- gem.version = Soulheart::VERSION
6
- gem.authors = ["Seth Herr"]
7
- gem.email = ["seth.william.herr@gmail.com"]
8
- gem.description = gem.summary = "Simple, fast autocomplete server for Ruby and Rails"
9
- gem.homepage = "https://github.com/sethherr/soulheart"
10
- gem.license = "MIT"
11
- gem.executables = ["soulheart", "soulheart-web"]
12
- gem.files = `git ls-files | grep -Ev '^(test)'`.split("\n")
13
- gem.name = "soulheart"
14
- gem.require_paths = ["lib"]
15
- gem.add_dependency 'hiredis', '~> 0.4.5'
16
- gem.add_dependency 'redis', '>= 3.0.6'
17
- gem.add_dependency 'vegas', '>= 0.1.0'
18
- gem.add_dependency 'json'
19
- gem.add_dependency 'sinatra'
20
- gem.add_development_dependency 'rack-contrib'
21
- gem.add_development_dependency 'rake'
22
- gem.add_development_dependency 'rspec'
23
- gem.add_development_dependency 'rubocop'
24
- end
@@ -1,15 +0,0 @@
1
- {"text":"Steel","category":"Frame Materials" }
2
- {"text":"Brompton Bicycle","priority":51,"category":"Frame Manufacturer","data":{"id":8,"url":"http://www.brompton.com"}}
3
- {"text":"Jamis","priority":75,"category":"Frame Manufacturer","data":{"id":2222,"url":"http://jamisbikes.com"}}
4
- {"text":"Surly","priority":102,"category":"Frame Manufacturer"}
5
- {"text":"Jagwire","priority":40,"category":"Manufacturer"}
6
- {"text":"Jannd","priority":41,"category":"Manufacturer"}
7
- {"text":"Sram","priority":50,"category":"Manufacturer","data":{"id":8,"url":"http://sram.com"}}
8
- {"text":"Brooks England LTD.","priority":50,"category":"Manufacturer","data":{"id":200,"url":"http://www.brooksengland.com/"}}
9
- {"text":"Dodger Stadium","priority":84,"data":{"id":1,"url":"\/dodger-stadium-tickets\/","subtitle":"Los Angeles, CA"},"aliases":["Chavez Ravine"]}
10
- {"text":"Angel Stadium","priority":90,"data":{"id":28,"url":"\/angel-stadium-tickets\/","subtitle":"Anaheim, CA"},"aliases":["Edison International Field of Anaheim"]}
11
- {"text":"Chase Field ","priority":80,"data":{"id":30,"url":"\/chase-field-tickets\/","subtitle":"Phoenix, AZ"},"aliases":["Bank One Ballpark", "Bank One Stadium"]}
12
- {"text":"Sun Life Stadium","priority":75,"data":{"id":29,"url":"\/sun-life-stadium-tickets\/","subtitle":"Miami, FL"},"aliases":["Dolphins Stadium","Land Shark Stadium"]}
13
- {"text":"Turner Field","priority":50,"data":{"id":2,"url":"\/turner-field-tickets\/","subtitle":"Atlanta, GA"}}
14
- {"text":"Citi Field","priority":92,"data":{"id":3,"url":"\/citi-field-tickets\/","subtitle":"Atlanta, GA"},"aliases":["Shea Stadium"]}
15
- {"text":"中国佛山 李小龙","priority":94,"data":{"id":8,"url":"\/Bruce Lee\/","subtitle":"Chinese Foshan"},"aliases":["Li XiaoLong"]}
@@ -1,96 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Soulheart::Loader do
4
-
5
- describe :clean_data do
6
- it "sets the default category, priority and normalizes term" do
7
- item = { 'text' => ' FooBar' }
8
- result = Soulheart::Loader.new.clean(item)
9
- expect(result['priority']).to eq(100)
10
- expect(result['term']).to eq('foobar')
11
- expect(result['category']).to eq('default')
12
- expect(result['data']['text']).to eq(' FooBar')
13
- end
14
-
15
- it "doesn't overwrite the submitted params" do
16
- item = {
17
- 'text' => 'Cool ',
18
- 'priority' => '50',
19
- 'category' => 'Gooble',
20
- 'data' => {
21
- 'id' => 199
22
- }
23
- }
24
- result = Soulheart::Loader.new.clean(item)
25
- expect(result['term']).to eq('cool')
26
- expect(result['priority']).to eq(50)
27
- expect(result['data']['text']).to eq('Cool ')
28
- expect(result['data']['id']).to eq(199)
29
- expect(result['category']).to eq('gooble')
30
- expect(result['data']['category']).to eq('Gooble')
31
- end
32
-
33
- it "raises argument error if text is passed" do
34
- expect{
35
- Soulheart::Loader.new.clean({'name' => 'stuff'})
36
- }.to raise_error(/must have/i)
37
- end
38
- end
39
-
40
- describe :add_item do
41
- it "adds an item, adds prefix scopes, adds category" do
42
- item = {
43
- 'text' => 'Brompton Bicycle',
44
- 'priority' => 50,
45
- 'category' => 'Gooble',
46
- 'data' => {
47
- 'id' => 199
48
- }
49
- }
50
- loader = Soulheart::Loader.new
51
- redis = loader.redis
52
- redis.expire loader.results_hashes_id, 0
53
- loader.add_item(item)
54
- redis = loader.redis
55
- target = "{\"text\":\"Brompton Bicycle\",\"category\":\"Gooble\",\"id\":199}"
56
- result = redis.hget(loader.results_hashes_id, 'brompton bicycle')
57
- expect(result).to eq(target)
58
- prefixed = redis.zrevrange "#{loader.category_id('gooble')}:brom", 0, -1
59
- expect(prefixed[0]).to eq('brompton bicycle')
60
- expect(redis.smembers(loader.categories_id).include?('gooble')).to be_true
61
- end
62
- end
63
-
64
- describe :store_terms do
65
- it "stores terms by priority and adds categories for each possible category combination" do
66
- items = []
67
- file = File.read('spec/fixtures/multiple_categories.json')
68
- file.each_line { |l| items << MultiJson.decode(l) }
69
- loader = Soulheart::Loader.new
70
- redis = loader.redis
71
- loader.delete_categories
72
- loader.load(items)
73
-
74
- cat_prefixed = redis.zrevrange "#{loader.category_id('frame manufacturermanufacturer')}:brom", 0, -1
75
- expect(cat_prefixed.count).to eq(1)
76
- expect(redis.smembers(loader.categories_id).count).to be > 3
77
-
78
- prefixed = redis.zrevrange "#{loader.category_id('all')}:bro", 0, -1
79
- expect(prefixed.count).to eq(2)
80
- expect(prefixed[0]).to eq('brompton bicycle')
81
- end
82
-
83
- it "stores terms by priority and doesn't add run categories if none are present" do
84
- items = [
85
- {'text' => 'cool thing', 'category' => 'AWESOME'},
86
- {'text' => 'Sweet', 'category' => ' awesome'}
87
- ]
88
- loader = Soulheart::Loader.new
89
- redis = loader.redis
90
- loader.delete_categories
91
- loader.load(items)
92
- expect(redis.smembers(loader.category_combos_id).count).to eq(1)
93
- end
94
- end
95
-
96
- end
@@ -1,104 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Soulheart::Matcher do
4
-
5
- describe :clean_opts do
6
- it "Has the keys we need" do
7
- target_keys = ['categories', 'query', 'page', 'per_page']
8
- keys = Soulheart::Matcher.default_params_hash.keys
9
- expect((target_keys - keys).count).to eq(0)
10
- end
11
-
12
- it "obeys stop words"
13
- end
14
-
15
- describe :category_id_from_opts do
16
- it 'gets the id for one' do
17
- Soulheart::Loader.new.reset_categories(['cool', 'test'])
18
- matcher = Soulheart::Matcher.new('categories' => ['some_category'])
19
- expect(matcher.category_id_from_opts).to eq(matcher.category_id('some_category'))
20
- end
21
-
22
- it 'gets the id for all of them' do
23
- Soulheart::Loader.new.reset_categories(['cool', 'test', 'boo'])
24
- matcher = Soulheart::Matcher.new('categories' => 'cool, boo, test')
25
- expect(matcher.category_id_from_opts).to eq(matcher.category_id('all'))
26
- end
27
- end
28
-
29
- describe :categories_string do
30
- it 'does all if none' do
31
- Soulheart::Loader.new.reset_categories(['cool', 'test'])
32
- matcher = Soulheart::Matcher.new('categories' => '')
33
- expect(matcher.categories_string).to eq('all')
34
- end
35
- it "correctly concats a string of categories" do
36
- Soulheart::Loader.new.reset_categories(['cool', 'some_category', 'another cat', 'z9', 'stuff'])
37
- matcher = Soulheart::Matcher.new({'categories' => 'some_category, another cat, z9'})
38
- expect(matcher.categories_string).to eq('another catsome_categoryz9')
39
- end
40
- end
41
-
42
-
43
- describe :matches do
44
- it "With no params, gets all the matches, ordered by priority and name" do
45
- store_terms_fixture
46
- opts = { 'per_page' => 100, 'cache' => false }
47
- matches = Soulheart::Matcher.new(opts).matches
48
- expect(matches.count).to be > 10
49
- expect(matches[0]['text']).to eq('Surly')
50
- end
51
-
52
- it "With no query but with categories, matches categories" do
53
- store_terms_fixture
54
- opts = { 'per_page' => 100, 'cache' => false, 'categories' => 'manufacturer' }
55
- matches = Soulheart::Matcher.new(opts).matches
56
- expect(matches.count).to eq(4)
57
- expect(matches[0]['text']).to eq('Sram')
58
- end
59
-
60
- it "Gets the matches matching query and priority for one item in query, all categories" do
61
- store_terms_fixture
62
- opts = { 'per_page' => 100, 'query' => 'j', 'cache' => false }
63
- matches = Soulheart::Matcher.new(opts).matches
64
- expect(matches.count).to eq(3)
65
- expect(matches[0]['text']).to eq('Jamis')
66
- end
67
-
68
- it "Gets the matches matching query and priority for one item in query, one category" do
69
- store_terms_fixture
70
- opts = { 'per_page' => 100, 'query' => 'j', 'cache' => false, 'categories' => 'manufacturer' }
71
- matches = Soulheart::Matcher.new(opts).matches
72
- expect(matches.count).to eq(2)
73
- expect(matches[0]['text']).to eq('Jannd')
74
- end
75
-
76
- it "Gets pages and uses them" do
77
- # Pagination wrecked my mind, hence the multitude of tests
78
- items = [
79
- {"text" => 'First item', 'priority' => '11000' },
80
- {"text" => 'Second item', 'priority' => '1999' },
81
- {"text" => 'Third item', 'priority' => 1900 },
82
- {"text" => 'Fourth item', 'priority' => 1800 },
83
- {"text" => 'Fifth item', 'priority' => 1750 },
84
- {"text" => 'Sixth item', 'priority' => 1700 },
85
- {"text" => 'Seventh item', 'priority' => 1699 }
86
- ]
87
- loader = Soulheart::Loader.new
88
- loader.delete_categories
89
- loader.load(items)
90
-
91
- page1 = Soulheart::Matcher.new({'per_page' => 1, 'cache' => false}).matches
92
- expect(page1[0]['text']).to eq('First item')
93
-
94
- page2 = Soulheart::Matcher.new({'per_page' => 1, 'page' => 2, 'cache' => false}).matches
95
- expect(page2.count).to eq(1)
96
- expect(page2[0]['text']).to eq('Second item')
97
-
98
- page3 = Soulheart::Matcher.new({'per_page' => 2, 'page' => 3, 'cache' => false}).matches
99
- expect(page3[0]['text']).to eq('Fifth item')
100
- expect(page3[1]['text']).to eq('Sixth item')
101
- end
102
- end
103
-
104
- end
@@ -1,26 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Soulheart::Server do
4
-
5
- describe :search do
6
- it "Has CORS headers, JSON Content-Type and it succeeds" do
7
- get '/'
8
- expect(last_response.headers['Access-Control-Allow-Origin']).to eq('*')
9
- expect(last_response.headers['Access-Control-Request-Method']).to eq('*')
10
- expect(last_response.headers['Content-Type']).to match('json')
11
- expect(last_response.status).to eq(200)
12
- end
13
- end
14
-
15
- describe :status do
16
- it "Has cors headers and is valid JSON" do
17
- get '/status'
18
- expect(last_response.headers['Access-Control-Allow-Origin']).to eq('*')
19
- expect(last_response.headers['Access-Control-Request-Method']).to eq('*')
20
- expect(last_response.headers['Content-Type']).to match('json')
21
- expect(JSON.parse(last_response.body)['soulheart']).to match(/\d/)
22
- end
23
- end
24
-
25
-
26
- end
@@ -1,29 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Soulheart do
4
-
5
- it 'has a version number' do
6
- expect(Soulheart::VERSION).not_to be nil
7
- end
8
-
9
- it "has a test base_id" do
10
- expect(Soulheart::Base.new.base_id).to eq('soulheart_test:')
11
- end
12
-
13
- it "collates (? probably not the word I'm looking for) all the things" do
14
- base = Soulheart::Base.new
15
- base.redis.expire base.categories_id, 0
16
- base.redis.sadd base.categories_id, ['George', 'category one', 'other thing ']
17
- result = base.set_category_combos_array
18
- expect(result.include?("category one")).to be_true
19
- expect(result.include?("george")).to be_true
20
- expect(result.include?("other thing")).to be_true
21
- expect(result.include?("georgeother thing")).to be_true
22
- expect(result.include?("category oneother thing")).to be_true
23
- expect(result.include?("category onegeorge")).to be_true
24
- expect(result.include?("georgecategory one")).to be_false
25
- expect(result.include?("all")).to be_true
26
- expect(result.include?("category onegeorgeother thing")).to be_false
27
- end
28
-
29
- end
@@ -1,31 +0,0 @@
1
- if ENV['CODECLIMATE_REPO_TOKEN']
2
- require "codeclimate-test-reporter"
3
- CodeClimate::TestReporter.start
4
- end
5
- require 'rack/test'
6
- require 'rspec'
7
-
8
- $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
9
- require 'soulheart'
10
- require 'soulheart/server'
11
-
12
- ENV['RACK_ENV'] = 'test'
13
-
14
- module RSpecMixin
15
- include Rack::Test::Methods
16
- def app() Soulheart::Server end
17
- end
18
-
19
- RSpec.configure do |config|
20
- config.treat_symbols_as_metadata_keys_with_true_values = true
21
- config.include RSpecMixin
22
- end
23
-
24
- def store_terms_fixture
25
- items = []
26
- file = File.read('spec/fixtures/multiple_categories.json')
27
- file.each_line { |l| items << MultiJson.decode(l) }
28
- loader = Soulheart::Loader.new
29
- loader.delete_categories
30
- loader.load(items)
31
- end