index.rb |
|
---|---|
IonA search engine written in Ruby and uses Redis. Github: http://github.com/rstacruz/ion Ion is under a state merciless refactoring until it reaches a useable feature set—use at your own risk :) |
|
Why use it?
|
|
Setup |
|
Do the |
require 'ion'
Ion.connect url: 'redis://127.0.0.1:6379/0' |
Setting up your model |
|
Any ORM will do. As long as you can hook it to update Ion’s indices, you’ll be fine. |
class Album < Ohm::Model
include Ion::Entity
include Ohm::Callbacks # for `after` and `before` |
So, say you have these fields. |
attribute :name
attribute :artist |
You can set them up to be indexed like so: |
ion {
text :name
metaphone :artist
} |
Just call these funcions on save and delete. |
after :save, :update_ion_indices
before :delete, :delete_ion_indices
end |
Searching |
|
Searching is easy. |
results = Album.ion.search {
text :name, "Dancing Galaxy"
}
results = Album.ion.search {
metaphone :artist, "Astral Projection"
} |
The results will be an |
results.each do |album|
puts "Album '#{album.name}' (by #{album.artist})"
end |
You can also get the raw results easily. |
results.to_a #=> [<#Album>, <#Album>, ... ]
results.ids #=> ["1", "2", "10", ... ] |
Features |
|
Custom indexing functions By default, doing |
class Book < Ohm::Model
attribute :name
attribute :synopsis
reference :author, Person
ion {
text(:author) { author.name } # Supply your own indexing function
}
end
Book.ion.search { text :author, "Patrick Suskind" } |
Nested conditions By default, doing a |
Book.ion.search {
all_of {
text :name, "perfume the story of a murderer"
text :synopsis, "base note"
any_of {
text :tags, "fiction"
text :tags, "thriller"
}
}
} |
Important rulesYou can make certain rules score higher than the rest. In this example, if the search string is found in the name, it’ll rank higher than if it was found in the synopsis. |
Book.ion.search {
any_of {
score(5.0) { text :name, "Darkly Dreaming Dexter" }
score(1.0) { text :synopsis, "Darkly Dreaming Dexter" }
}
} |
BoostingYou can define rules on what will rank higher. This is different from This example will boost the score of sale items by x2.0. |
Book.ion.search {
text :name, "The Taking of Sleeping Beauty"
boost(2.0) { text :tags, "sale" }
} |
MetaphonesIndexing via metaphones allows you to search by how something sounds like, rather than with exact spellings. |
class Person < Ohm::Model
attribute :name
ion {
metaphone :name
}
end
Person.create name: "Stephane Michael Cook" |
Any of these will work. |
Person.ion.search { metaphone :name, 'stiefen michel cooke' }
Person.ion.search { metaphone :name, 'steven quoc' } |
RangesYou may limit your results set like so. |
results = Book.ion.search {
text :author, "Anne Rice"
}
results.range from: 54, limit: 10
results.range from: 3
results.range page: 1, limit: 30
results.range (0..3)
results.range (0..-1)
results.range from: 3, to: 9
results.size # This will not change even if you change the range...
results.ids.size # However, this will.
results.range :all # Reset |
Numeric indicesYou may index numerical attributes and perform some simple matching on them. |
class Recipe < Ohm::Model
attribute :serving_size
ion {
number :serving_size # Define a number index
}
end
Recipe.ion.search { number :serving_size, 1 } # n == 1
Recipe.ion.search { number :serving_size, gt:1 } # n > 1
Recipe.ion.search { number :serving_size, gt:2, lt:5 } # 2 < n < 5
Recipe.ion.search { number :serving_size, min: 4 } # n >= 4
Recipe.ion.search { number :serving_size, max: 10 } # n <= 10 |
SortingFirst, define a sort index in your model. |
class Element < Ohm::Model
attribute :name
attribute :protons
attribute :electrons
ion {
sort :name # <-- like this
number :protons
}
end |
Now sort it like so. This will not take the search relevancy scores into account. |
results = Element.ion.search { number :protons, gt: 3.5 }
results.sort_by :name |
Note that this sorting (unlike in Ohm, et al) is case insensitive, and takes English articles into account (eg, “The Beatles” will come before “Rolling Stones”). |
|
Extending Ion |
|
You may override it with some fancy stuff. |
class Ion::Search
def to_ohm
set_key = model.key['~']['mysearch']
ids.each { |id| set_key.sadd id }
Ohm::Set.new(set_key, model)
end
end
set = Album.ion.search { ... }.to_ohm |
Or extend the DSL. |
class Ion::Scope
def keywords(what)
any_of {
text :title, what
metaphone :artist, what
}
end
end
Album.ion.search { keywords "Foo" } |
Features in the works |
|
These features are not implemented yet, but will be. |
|
TODO: search keyword blacklist. |
Ion.config.ignored_words += %w(at it the) |
TODO: Quoted searching. |
Item.ion.search {
text :title, 'apple "MacBook Pro"'
}
results = Item.ion.search {
text :title, "Macbook" |
TODO: exclusions. |
exclude {
text :title, "Case"
}
} |
TODO: descending sort (and multi-field sort) |
results.sort_by :name, order: :desc |
TODO: facets |
results.facet_counts #=> { :name => { "Ape" => 2, "Banana" => 3 } } ?? |
Quirks |
|
Searching with arity The search DSL may leave some things in accessible since the block will
be ran through |
Book.ion.search { text :name, @name } # fail
Book.ion.search { |q| q.text :name, @name } # good |
Or you may also take advantage of Ruby closures: |
name = @name
Book.ion.search { text :name, name } # good |