Skip to content

Commit

Permalink
Client API reworked, Multiple searches are possible now
Browse files Browse the repository at this point in the history
  • Loading branch information
Crenshinibon committed Sep 25, 2013
1 parent ad75f75 commit d70d5c6
Show file tree
Hide file tree
Showing 11 changed files with 238 additions and 169 deletions.
51 changes: 44 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,24 @@ It is a quite simple and limited fulltext search engine for [Meteor](http://mete

This repository is itself a Meteor app and should serve as an example, of how to actually use Spomet.

I tried to make it as simple as possible:
Get Started
===========

I tried to make using the package as simple as possible:

Include the search box in your template:

{{> spometSearch}}

Access the results through a call to:
Access the results, found by using the search box, through a call to:

Spomet.Results()
Spomet.defaultSearch.results()

It returns a Meteor Collections Cursor.

Add documents to the search by calling the method *add* with a *Findable* instance:
Add documents to the search by calling the method *add* with a *Spomet.Findable* instance:

Spomet.add new Spomet.Findable text, path, base, rev
Spomet.add new Spomet.Findable text, path, base, type, rev

* text
The first parameter is the text, to be indexed.
Expand All @@ -35,6 +38,27 @@ Add documents to the search by calling the method *add* with a *Findable* instan
The documents type. Might be useful to distinguish between different types of documents.
* rev
A revision number to support multiple version of a document.


Advanced
========

You can delete documents from the search by calling *Spomet.remove* with a *Spomet.Findable* instance as the parameter or with the *docId*.

Spomet.remove 'post-id1234-description-2'
Spomet.remove new Spomet.Findable null, 'description', 'id1234', 'post', 2

You can update already indexed documents, dismissing the prior version.

Spomet.update new Spomet.Findable text, path, base, type, rev

The document, with *rev - 1* gets removed from the search as a result.

You can create your own searches by instantiating *Spomet.Search*.

mySearch = new Spomet.Search
mySearch.find 'some text'
mySearch.results()


Technology
Expand All @@ -48,7 +72,20 @@ Future enhancements might include stemming, algorithm based (e.g. Porter) or bas

Furthermore is the implementation not very efficient, I fear. There is plenty of room to optimize certain aspects.

The server process handles the heavy lifting of indexing the documents. So when there are many documents to include the server will stall. A future enhancement might include establishing a separate process (deployable on a different host) for the indexing. Client side indexing might not be doable, because of security considerations.
The server process handles the heavy lifting of indexing, finding and scoring the documents.

When there are many documents to index the server might stall.

A future enhancement might include establishing a separate process (deployable on a different host) for the indexing. Client side indexing might not be doable, because of security considerations.

If you experience performance issues you might want to disable certain Indexes, you should start with the 3Gram index.

There are handy Meteor methods to achieve this:

Meteor.call 'disableThreeGramIndex'
Meteor.call 'disableCustomIndex'
Meteor.call 'disableWordGroupIndex'
Meteor.call 'disableFullWordIndex'

Tests
=====
Expand All @@ -64,7 +101,7 @@ Note: There might be some false errors, indicating some curly braces problem, wh
Warning
=======

This package is in it's really really early stages. As it should allow for some basic usage, there might be some essential things missing.
This package is still in it's really really early stages. As it should allow for some basic usage, there might be some essential things missing or going wrong.

There is of course no guarantee for it's correct functioning. And I'm not liable on any consequences resulting from the usage of this software.

Expand Down
237 changes: 115 additions & 122 deletions packages/spomet/client.coffee
Original file line number Diff line number Diff line change
@@ -1,140 +1,133 @@
Deps.autorun () ->
Meteor.subscribe 'documents'
Meteor.subscribe 'common-terms'
Meteor.subscribe 'search-results',
Session.get 'spomet-current-search',
Session.get 'spomet-search-sort',
Session.get 'spomet-search-offset',
Session.get 'spomet-search-limit'

Spomet.find = (phrase) ->
#
# THIS IS A HACK, I have to wait shortly, otherwise
# the intermediary results are not shown and Meteor
# waits for the end before displaying results.
#
# The problem might be some optimization that's going
# on under the hood.
#
Session.set 'spomet-searching', true
find = () ->
if phrase? and phrase.length > 0
Meteor.call 'spometFind', phrase, () ->
Session.set 'spomet-searching', null
Meteor.setTimeout find, 5

Spomet.searching = () ->
Session.get 'spomet-searching'

Spomet.clearSearch = () ->
Session.set 'spomet-searching', null
Session.set 'spomet-current-search', null
Session.set 'spomet-search-offset', null


Spomet.add = (findable) ->
Meteor.call 'spometAdd', findable, () ->
Spomet.clearSearch()

Spomet.update = (findable) ->
Meteor.call 'spometUpdate', findable, () ->
Spomet.clearSearch()


Spomet.remove = (findable) ->
Meteor.call 'spometRemove', findable, () ->
Spomet.clearSearch()

Spomet.setSort = (sort) ->
Session.set 'spomet-search-sort', sort

Spomet.getSort = () ->
Session.get 'spomet-search-sort'

Spomet.setOffset = (offset) ->
Session.set 'spomet-search-offset', offset
class Spomet.Search

Spomet.getOffset = () ->
Session.get 'spomet-search-offset'
constructor: () ->
@collection = new Meteor.Collection null
@subHandle = null

Spomet.setLimit = (limit) ->
Session.set 'spomet-search-limit', limit
set: (key, value) =>
upd = {}
upd[key] = value

sel = {}
sel[key] = {$exists: true}
existing = @collection.findOne sel
if existing?
@collection.update {_id: existing._id}, upd
else
@collection.insert upd

get: (key) =>
sel = {}
sel[key] = {$exists: true}
existing = @collection.findOne sel
if existing?
existing[key]
else
null

Spomet.getLimit = () ->
Session.get 'spomet-search-limit'

Spomet.Results = () ->
phrase = Session.get 'spomet-current-search'
if phrase?
[selector, opts] = Spomet.buildSearchQuery phrase,
Spomet.getSort(),
Spomet.getOffset(),
Spomet.getLimit()
reSubscribe: () =>
if @subHandle
@subHandle.stop()
search = @

Spomet.Search.find selector, opts

Template.spometSearch.latestPhrase = () ->
phrase = Session.get 'spomet-current-search'
if phrase? then phrase else ''

Template.spometSearch.searchInProgress = () ->
Session.get('spomet-searching')?

Template.spometSearch.searching = () ->
Session.get('spomet-current-search')?

createIntermediaryResults = (item) ->
words = item.split(' ')
cur = Spomet.CommonTerms.find {token: {$in: words}}
cur.forEach (e) ->
e.documents.forEach (d) ->
doc = Spomet.Documents.collection.findOne {docId: d.docId}
res =
phraseHash: Spomet.phraseHash item
docId: d.docId
score: 0
type: doc.findable.type
base: doc.findable.base
path: doc.findable.path
version: doc.findable.version
hits: []
queried: new Date()
interim: true
Spomet.Search.insert res

typeaheadSource = (query) ->
[start..., last] = @query.split ' '
r = new RegExp "^#{last}"
cursor = Spomet.CommonTerms.find
token: r
tlength: {$gt: last.length}
Deps.autorun () ->
search.subHandle = Meteor.subscribe 'search-results',
search.get 'current-phrase',
search.get 'search-sort',
search.get 'search-offset',
search.get 'search-limit'

setCurrentPhrase: (phrase) =>
@set 'current-phrase', phrase
@reSubscribe()

fixed = start.join ' '
cursor.map (e) ->
if fixed and fixed.length > 0
fixed + ' ' + e.token
else
e.token

Template.spometSearch.rendered = () ->
$('input.spomet-search-field').typeahead
source: typeaheadSource
updater: (item) ->
Spomet.clearSearch()
$('input.spomet-search-field')[0].value = item
createIntermediaryResults item
Spomet.find item
Session.set 'spomet-current-search', item
matcher: (item) ->
true
getCurrentPhrase: () =>
@get 'current-phrase'

setSort: (sort) =>
@set 'search-sort', sort
@reSubscribe()

getSort: () =>
@get 'search-sort'

setOffset: (offset) =>
@set 'search-offset', offset
@reSubscribe()

getOffset: () =>
@get 'search-offset'

setLimit: (limit) =>
@set 'search-limit', limit
@reSubscribe()

getLimit: () =>
@get 'search-limit'

setSearching: (searching) =>
@set 'searching', searching

isSearching: () =>
@get 'searching'

Template.spometSearch.events
'submit form': (e) ->
e.preventDefault()
Spomet.clearSearch()
phrase = $('input.spomet-search-field')[0].value

find: (phrase) =>
if phrase? and phrase.length > 0
Spomet.find phrase
Session.set 'spomet-current-search', phrase
'click button.spomet-reset-search': (e) ->
Spomet.clearSearch()
'mouseup input.spomet-search-field': (e) ->
e.preventDefault()
@clearSearch phrase
@createIntermediaryResults phrase

search = @
Meteor.call 'spometFind', phrase, () ->
search.setSearching null


clearSearch: (newPhrase) ->
@set 'searching', if newPhrase then true else null
@set 'current-phrase', newPhrase
@set 'search-offset', null
@set 'search-limit', null
@reSubscribe()

createIntermediaryResults: (phrase) ->
words = phrase.split ' '
cur = Spomet.CommonTerms.find {token: {$in: words}}
cur.forEach (e) ->
e.documents.forEach (d) ->
doc = Spomet.Documents.collection.findOne {docId: d.docId}
res =
phraseHash: Spomet.phraseHash phrase
docId: d.docId
score: 0
type: doc.findable.type
base: doc.findable.base
path: doc.findable.path
version: doc.findable.version
hits: []
queried: new Date()
interim: true
Spomet.Searches.insert res

results: () ->
phrase = @getCurrentPhrase()
if phrase?
[selector, opts] = Spomet.buildSearchQuery phrase,
@getSort(),
@getOffset(),
@getLimit()

Spomet.Searches.find selector, opts

3 changes: 2 additions & 1 deletion packages/spomet/package.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ Package.on_use(function (api) {

api.add_files('server.coffee','server');

api.add_files('search_field.html','client');
api.add_files('client.coffee','client');
api.add_files('search_field.html','client');
api.add_files('search_field.coffee','client');

api.export('Spomet',['server','client']);
});
Loading

0 comments on commit d70d5c6

Please sign in to comment.