module Lunar class Index FUZZY_MAX_LENGTH = 100 FuzzyFieldTooLong = Class.new(StandardError) attr :ns def self.create(prefix) new(prefix).tap { |i| yield i }.index end def self.delete(prefix, key) new(prefix).tap { |i| i.key key }.delete end def initialize(prefix, redis = nil) @ns = Lunar.nest[prefix] @attrs = {} @redis = (redis || Lunar.redis) @numeric_attrs = {} @fuzzy_attrs = {} end def key(key = nil) @key = key if key @key end def attrs=(attrs) attrs.each { |k, v| attr k, v } end def attr(field, value = nil) @attrs[field.to_sym] = value if value @attrs[field.to_sym] end def numeric_attr(field, value = nil) @numeric_attrs[field.to_sym] = value if value @numeric_attrs[field.to_sym] end alias :integer :numeric_attr alias :float :numeric_attr def fuzzy(field, value = nil) @fuzzy_attrs[field.to_sym] = value if value @fuzzy_attrs end def index index_str_attrs index_fuzzy_attrs index_numeric_attrs return self end def delete keys = @redis.keys @ns[key]['*'] keys.each do |k| field = k.gsub("#{ @ns[key] }:", '') words = @redis.smembers(k) words.each do |w| @redis.zrem @ns[field][Lunar.encode(w)], key end @redis.del k end fuzzy_keys = @redis.keys @ns[:Fuzzy][key]['*'] fuzzy_keys.each do |k| field = k.gsub("#{ @ns[:Fuzzy][key] }:", '') words = @redis.smembers(k) words.each do |w| remove_fuzzy_values field end @redis.del k end end protected def index_str_attrs @attrs.each do |field, value| scoring = Scoring.new(value) words = [] scoring.scores.each do |word, score| words << word @redis.zadd @ns[field][Lunar.encode(word)], score, key @redis.sadd @ns[key][field], word end unused_words = @redis.smembers(@ns[key][field]) - words unused_words.each do |word| @redis.zrem @ns[field][Lunar.encode(word)], key @redis.srem @ns[key][field], word end end end def index_fuzzy_attrs @fuzzy_attrs.each do |field, value| if value.to_s.length > FUZZY_MAX_LENGTH raise FuzzyFieldTooLong, "#{field} has a value #{value} exceeding #{FUZZY_MAX_LENGTH} chars" end words = Words.new(value).uniq words.each do |word| FuzzyWord.new(word).partials.each do |partial| @redis.sadd @ns[:Fuzzy][field][Lunar.encode(partial)], key end @redis.sadd @ns[:Fuzzy][key][field], word end remove_fuzzy_values field, words end end def remove_fuzzy_values(field, words = []) unused_words = @redis.smembers(@ns[:Fuzzy][key][field]) - words unused_words.each do |word| FuzzyWord.new(word).partials.each do |partial| next if words.grep(/^#{partial}/u).any? @redis.srem @ns[:Fuzzy][field][Lunar.encode(partial)], key end @redis.srem @ns[:Fuzzy][key][field], word end end def index_numeric_attrs @numeric_attrs.each do |field, value| @redis.zadd @ns[field], value, key end end end end