soulheart 0.0.5 → 0.0.6

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: cd989a4107aacd141e1ecbcdcab17c4565237768
4
- data.tar.gz: eadaae2aad79d613be888a6cd39fe130071c8b00
3
+ metadata.gz: 0ee2b8a0da3cca62bb1236f16a0537e3b6d153e9
4
+ data.tar.gz: bc09f384eb18f2162d3eb7b852e137ecfe3a48d0
5
5
  SHA512:
6
- metadata.gz: 8f476069f2c8ff7491142570abc525ca25d0ad9870ac22398b6ee1fa6c55653b828ae6f3cc2bc427ea32103fbd8cd4a3649483aefebdf31cdf3a0e4fad4714b1
7
- data.tar.gz: a4a3682db05d5227469b5291399be897cb006ebed4654aae8a305f2c84eb388afce580974fbe4d6578cac17ce0521300921e1f6d76001df0b8c84e645a66fb12
6
+ metadata.gz: b1cc93669b128ddd68f594124ec0e9bee2b7ee7fa441a8c135c099f00513e288f37ccc0cd02efb67918b101b84220c8c9cc098d10f435678cabb6a4fbb17da42
7
+ data.tar.gz: 2de3adf63591024964833931ddfd2bff99573711509e633618b7e6a842ca4b2907ade4e91e836ee9e41a8ca5b902cb29ba20db90a2824b2f9b2e969fe4acfa34
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
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)
2
2
 
3
+ To get started, check out examples and documentation at [sethherr.github.io/soulheart/](https://sethherr.github.io/soulheart/).
4
+
3
5
  **Soulheart is a ready-to-use remote data source for autocomplete**. It supports:
4
6
 
5
7
  - pagination
@@ -11,199 +13,22 @@
11
13
 
12
14
  ... and is [instantly deployable to heroku](https://heroku.com/deploy) (for free).
13
15
 
14
- To get started, check out examples and documentation at [sethherr.github.io/soulheart/](https://sethherr.github.io/soulheart/).
15
-
16
- ----
17
-
18
- Soulheart is in Beta. It's probably appropriate to use in production... but maybe wait? There are a few more changes coming, and some documentation improvements to be made.
19
-
20
- ### Adding data
21
-
22
- You can add data from json, CSV and TSV files.
23
-
24
- Adding data is very simple - all you need is a `text` value.
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.
27
-
28
- For the simplest case, with just text values in JSON:
29
-
30
- { "text": "Jamis" }
31
- { "text": "Specialized" }
32
- { "text": "Trek" }
33
-
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
16
 
43
- In addition to term, there are a few optional values -
17
+ This project is in Beta. It's probably appropriate to use in production... but maybe wait? There are a few more changes coming, and some documentation improvements to be made.
44
18
 
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. |
19
+ =======
50
20
 
51
- Here is an example of what a possible hash you could pass is
21
+ I'm testing with: `ruby 2.1` and `redis 3.0`.
52
22
 
53
- { "text": "Jamis", "category": "Bike Manufacturer" }
54
- { "text": "Specialized" }
55
- { "text": "Trek" }
23
+ Run `bundle exec guard` to run the specs when they change as you work.
56
24
 
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.*
25
+ 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. if you build it separately.
58
26
 
59
27
  ======
60
28
 
61
- I'm testing with: `ruby >= 2.1` and `redis >= 3`.
62
-
63
- Run `bundle exec guard` to run the specs while you work, it will just test the files you change.
64
-
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.
66
-
67
-
68
- ======
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
-
74
- :x::o::x::o::x::o: *Soulmate's README follows ([issue for making new documentation](../../issues/1))*
75
-
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.
77
-
78
- Soulmate was designed to be simple and fast, and offers the following:
79
-
80
- * Provide suggestions for multiple types of items in a single query (at SeatGeek we're autocompleting for performers, events, and venues)
81
- * Results are ordered by a user-specified score
82
- * Arbitrary metadata for each item (at SeatGeek we're storing both a url and a subtitle)
83
-
84
- An item is a simple JSON object that looks like:
85
-
86
- {
87
- "id": 3,
88
- "term": "Citi Field",
89
- "score": 81,
90
- "data": {
91
- "url": "/citi-field-tickets/",
92
- "subtitle": "Flushing, NY"
93
- }
94
- }
95
-
96
- Where `id` is a unique identifier (within the specific type), `term` is the phrase you wish to provide completions for, `score` is a user-specified ranking metric (redis will order things lexicographically for items with the same score), and `data` is an optional container for metadata you'd like to return when this item is matched (at SeatGeek we're including a url for the item as well as a subtitle for when we present it in an autocomplete dropdown).
97
-
98
- Getting Started
99
- ---------------
100
-
101
- As always, kick things off with a `gem install`:
102
-
103
- gem install soulmate
104
-
105
- ### Loading Items
106
-
107
- You can load data into Soulmate by piping items in the JSON lines format into `soulmate load TYPE`.
108
-
109
- Here's a sample `venues.json` (one JSON item per line):
110
-
111
- {"id":1,"term":"Dodger Stadium","score":85,"data":{"url":"\/dodger-stadium-tickets\/","subtitle":"Los Angeles, CA"}}
112
- {"id":28,"term":"Angel Stadium","score":85,"data":{"url":"\/angel-stadium-tickets\/","subtitle":"Anaheim, CA"}}
113
- {"id":30,"term":"Chase Field ","score":85,"data":{"url":"\/chase-field-tickets\/","subtitle":"Phoenix, AZ"}}
114
- {"id":29,"term":"Sun Life Stadium","score":84,"data":{"url":"\/sun-life-stadium-tickets\/","subtitle":"Miami, FL"}}
115
- {"id":2,"term":"Turner Field","score":83,"data":{"url":"\/turner-field-tickets\/","subtitle":"Atlanta, GA"}}
116
-
117
- And here's the load command (Soulmate assumes redis is running locally on the default port, or you can specify a redis connection string with the `--redis` argument):
118
-
119
- $ soulmate load venue --redis=redis://localhost:6379/0 < venues.json
120
-
121
- You can also provide an array of strings under the `aliases` key that will also be added to the index for this item.
122
-
123
- ### Querying for Data
124
-
125
- Once it's loaded, we can query this data by starting `soulmate-web`:
126
-
127
- $ soulmate-web --foreground --no-launch --redis=redis://localhost:6379/0
128
-
129
- And viewing the service in your browser: http://localhost:5678/search?types[]=venue&term=stad. You should see something like:
130
-
131
- {
132
- "term": "stad",
133
- "results": {
134
- "venue": [
135
- {
136
- "id": 28,
137
- "term": "Angel Stadium",
138
- "score": 85,
139
- "data": {
140
- "url": "/angel-stadium-tickets/",
141
- "subtitle": "Anaheim, CA"
142
- }
143
- },
144
- {
145
- "id": 1,
146
- "term": "Dodger Stadium",
147
- "score": 85,
148
- "data": {
149
- "url": "/dodger-stadium-tickets/",
150
- "subtitle": "Los Angeles, CA"
151
- }
152
- },
153
- {
154
- "id": 29,
155
- "term": "Sun Life Stadium",
156
- "score": 84,
157
- "data": {
158
- "url": "/sun-life-stadium-tickets/",
159
- "subtitle": "Miami, FL"
160
- }
161
- }
162
- ]
163
- }
164
- }
165
-
166
- The `/search` method supports multiple `types` as well as an optional `limit`. For example: `http://localhost:5678/search?types[]=event&types[]=venue&types[]=performer&limit=3&term=yank`. You can also add the `callback` parameter to enable JSONP output.
167
-
168
- ### Mounting soulmate into a rails app
169
-
170
- If you are integrating Soulmate into a rails app, an alternative to launching a separate 'soulmate-web' server is to mount the sinatra app inside of rails.
171
-
172
- Add this to routes.rb:
173
-
174
- mount Soulmate::Server, :at => "/sm"
175
-
176
- Add this to gemfile:
177
-
178
- gem 'rack-contrib'
179
- gem 'soulmate', :require => 'soulmate/server'
180
-
181
- Then you can query soulmate at the /sm url, for example: http://localhost:3000/sm/search?types[]=venues&limit=6&term=kitten
182
-
183
- You can also config your redis instance:
184
-
185
- # config/initializers/soulmate.rb
186
-
187
- Soulmate.redis = 'redis://127.0.0.1:6379/0'
188
- # or you can asign an existing instance of Redis, Redis::Namespace, etc.
189
- # Soulmate.redis = $redis
190
-
191
- ### Rendering an autocompleter
192
-
193
- Soulmate doesn't include any client-side code necessary to render an autocompleter, but Mitch Crowe put together a pretty cool looking jquery plugin designed for exactly that: <a href="https://github.com/mcrowe/soulmate.js">soulmate.js</a>.
194
-
195
- Contributing to soulmate
196
- ------------------------
197
-
198
- * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
199
- * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
200
- * Fork the project
201
- * Start a feature/bugfix branch
202
- * Commit and push until you are happy with your contribution
203
- * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
29
+ [There is issue for improving documentation](../../issues/1) because the documentation needs to be improved. Also, for serious, check out [sethherr.github.io/soulheart/](https://sethherr.github.io/soulheart/).
204
30
 
205
- Copyright
206
- ---------
31
+ =====
207
32
 
208
- Copyright (c) 2011 Eric Waller. See LICENSE.txt for further details.
209
33
 
34
+ This is a fork of [Soulmate](https://github.com/seatgeek/soulmate), it's goal isn't backward compatibility.
data/bin/soulheart CHANGED
@@ -38,10 +38,8 @@ parser = OptionParser.new do |opts|
38
38
 
39
39
  opts.separator ''
40
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
- 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.'
41
+ opts.separator ' load FILE Loads data from a FILE - can be a local file or a url. Accepted formats are .json, .tsv and .csv'
42
+ opts.separator " reset FILE Removes all existing data, then runs load on the FILE."
45
43
  end
46
44
 
47
45
 
@@ -91,59 +89,19 @@ def load(file)
91
89
  puts "Loaded a total of #{count} items in #{Time.now.to_i - start_time} second(s)"
92
90
  end
93
91
 
94
- def add(type)
95
- puts "Adding items of type #{type}..."
96
- loader = Soulheart::Loader.new(type)
97
- items = $stdin.read.split("\n").map { |l| MultiJson.decode(l) }
98
- items.each do |item|
99
- loader.add(item)
100
- end
101
- puts "Loaded a total of #{items.size} items"
102
- end
103
-
104
- def remove(type)
105
- puts "Removing items of type #{type}..."
106
- loader = Soulheart::Loader.new(type)
107
- items = $stdin.read.split("\n").map { |l| MultiJson.decode(l) }
108
- items.each do |item|
109
- loader.remove(item)
110
- end
111
- puts "Removed a total of #{items.size} items"
112
- end
113
-
114
- def query(type, query)
115
- puts "> Querying '#{type}' for '#{query}'"
116
- matcher = Soulheart::Matcher.new(type)
117
- results = matcher.matches_for_term(query, limit: 0)
118
- results.each do |item|
119
- puts MultiJson.encode(item)
120
- end
121
- puts "> Found #{results.size} matches"
122
- end
123
-
124
- def gen_redis_proto(*cmd)
125
- proto = '*' + cmd.length.to_s + "\r\n"
126
- cmd.each{|arg|
127
- proto << '$' + arg.bytesize.to_s + "\r\n"
128
- proto << arg + "\r\n"
129
- }
130
- proto
92
+ def clear
93
+ Soulheart::Loader.new.clear
131
94
  end
132
95
 
133
96
  parser.parse!
134
97
  BATCH_SIZE ||= 1000
135
98
 
136
99
  case ARGV[0]
137
- when 'generate'
138
- generate ARGV[1], ARGV[2]
139
100
  when 'load'
140
101
  load ARGV[1]
141
- when 'add'
142
- add ARGV[1]
143
- when 'remove'
144
- remove ARGV[1]
145
- when 'query'
146
- query ARGV[1], ARGV[2]
102
+ when 'reset'
103
+ clear
104
+ load ARGV[1]
147
105
  else
148
106
  puts parser.help
149
107
  end
@@ -15,6 +15,7 @@ module Soulheart
15
15
  end
16
16
 
17
17
  def delete_categories
18
+ redis.expire category_combos_id, 0
18
19
  redis.expire categories_id, 0
19
20
  end
20
21
 
@@ -23,27 +24,39 @@ module Soulheart
23
24
  redis.sadd categories_id, categories
24
25
  end
25
26
 
26
- def delete_data
27
+ def delete_data(id="#{base_id}:")
27
28
  # delete the sorted sets for this type
28
29
  phrases = redis.smembers(base_id)
29
30
  redis.pipelined do
30
31
  phrases.each do |p|
31
- redis.del("#{base_id}:#{p}")
32
+ redis.del("#{id}#{p}")
32
33
  end
33
- redis.del(base_id)
34
+ redis.del(id)
34
35
  end
35
36
 
36
- # Redis can continue serving cached requests for this type while the reload is
37
- # occuring. Some requests may be cached incorrectly as empty set (for requests
37
+ # Redis can continue serving cached requests while the reload is
38
+ # occurring. Some requests may be cached incorrectly as empty set (for requests
38
39
  # which come in after the above delete, but before the loading completes). But
39
40
  # everything will work itself out as soon as the cache expires again.
41
+ end
40
42
 
41
- # delete the data stored for this type
43
+ def remove_results_hash
44
+ # delete the data store
45
+ # We don't do this every time we clear because because it breaks the caching feature.
46
+ # The option to clear this is only called in testing right now.
47
+ # There should be an option to clear it other times though.
48
+ redis.expire results_hashes_id, 0
42
49
  redis.del(results_hashes_id)
43
50
  end
44
51
 
45
- def load(items)
52
+ def clear(remove_results: false)
53
+ category_combos.each {|cat| delete_data(category_id(cat)) }
54
+ delete_categories
46
55
  delete_data
56
+ remove_results_hash if remove_results
57
+ end
58
+
59
+ def load(items)
47
60
  # Replace with item return so we know we have category_id
48
61
  items.each { |item| item.replace(add_item(item)) }
49
62
  set_category_combos_array.each do |category_combo|
@@ -87,22 +100,5 @@ module Soulheart
87
100
  end
88
101
  item
89
102
  end
90
-
91
- # remove only cares about an item's id, but for consistency takes an object
92
- def remove(item)
93
- prev_item = Soulheart.redis.hget(base_id, item['term'])
94
- if prev_item
95
- prev_item = MultiJson.decode(prev_item)
96
- # undo the operations done in add
97
- Soulheart.redis.pipelined do
98
- Soulheart.redis.hdel(base_id, prev_item['term'])
99
- phrase = ([prev_item['term']] + (prev_item['aliases'] || [])).join(' ')
100
- prefixes_for_phrase(phrase).each do |p|
101
- Soulheart.redis.srem(base_id, p)
102
- Soulheart.redis.zrem("#{base_id}:#{p}", prev_item['term'])
103
- end
104
- end
105
- end
106
- end
107
103
  end
108
104
  end
@@ -1,3 +1,3 @@
1
1
  module Soulheart
2
- VERSION = '0.0.5'
2
+ VERSION = '0.0.6'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: soulheart
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Seth Herr