module Redis::Search

nodoc

Constants

DOT
VERSION

Attributes

config[RW]
indexed_models[RW]

Public Class Methods

complete(type, w, options = {}) click to toggle source

Use for short title search, this method is search by chars, for example Tag, User, Category …

h3. params:

type      model name
w         search char
:limit    result limit
:order    result order

h3. usage:

# File lib/redis-search/finder.rb, line 19
def complete(type, w, options = {})
  limit      = options[:limit] || 10
  conditions = options[:conditions] || []
  order      = options[:order] || 'desc'
  return [] if (w.blank? && conditions.blank?) || type.blank?

  prefix_matchs = []
  # This is not random, try to get replies < MTU size
  rangelen = config.complete_max_length
  prefix = w.downcase
  key = mk_complete_key(type)

  if start = config.redis.zrank(key, prefix)
    count = limit
    max_range = start + (rangelen * limit) - 1
    range = config.redis.zrange(key, start, max_range)
    while prefix_matchs.length <= count
      start += rangelen
      break if !range || range.empty?
      range.each do |entry|
        minlen = [entry.length, prefix.length].min
        if entry[0...minlen] != prefix[0...minlen]
          count = prefix_matchs.count
          break
        end
        if entry[-1..-1] == '*' && prefix_matchs.length != count
          prefix_matchs << entry[0...-1]
        end
      end

      range = range[start..max_range]
    end
  end
  prefix_matchs.uniq!

  # 组合 words 的特别 key 名
  words = prefix_matchs.collect { |w| mk_sets_key(type, w) }

  # 组合特别 key ,但这里不会像 query 那样放入 words, 因为在 complete 里面 words 是用 union 取的,condition_keys 和 words 应该取交集
  condition_keys = []
  unless conditions.blank?
    conditions = conditions[0] if conditions.is_a?(Array)
    conditions.keys.each do |c|
      condition_keys << mk_condition_key(type, c, conditions[c])
    end
  end

  # 按词语搜索
  temp_store_key = "tmpsunionstore:#{words.join('+')}"
  if words.length > 1
    unless config.redis.exists(temp_store_key)
      # 将多个词语组合对比,得到并集,并存入临时区域
      config.redis.sunionstore(temp_store_key, *words)
      # 将临时搜索设为1天后自动清除
      config.redis.expire(temp_store_key, 86_400)
    end
    # 根据需要的数量取出 ids
  else
    temp_store_key = words.first
  end

  # 如果有条件,这里再次组合一下
  unless condition_keys.blank?
    condition_keys << temp_store_key unless words.blank?
    temp_store_key = "tmpsinterstore:#{condition_keys.join('+')}"
    unless config.redis.exists(temp_store_key)
      config.redis.sinterstore(temp_store_key, *condition_keys)
      config.redis.expire(temp_store_key, 86_400)
    end
  end

  ids = config.redis.sort(temp_store_key,
                          limit: [0, limit],
                          by: mk_score_key(type, '*'),
                          order: order)
  return [] if ids.blank?
  hmget(type, ids)
end
Also aliased as: query
configure() { |config ||= config| ... } click to toggle source
# File lib/redis-search/config.rb, line 6
def configure
  yield @config ||= Config.new
end
query(type, w, options = {})
Alias for: complete

Private Class Methods

hmget(type, ids, options = {}) click to toggle source
# File lib/redis-search/finder.rb, line 142
def self.hmget(type, ids, options = {})
  result = []
  return result if ids.blank?
  config.redis.hmget(type, *ids).each do |r|
    begin
      result << JSON.parse(r) unless r.blank?
    rescue => e
      warn("Search.query failed: #{e}")
    end
  end
  result
end
info(msg) click to toggle source
# File lib/redis-search/finder.rb, line 115
def self.info(msg)
  return unless Redis::Search.config.debug
  msg = "\e[32m[redis-search] #{msg}\e[0m"
  if defined?(Rails) == 'constant' && Rails.class == Class
    ::Rails.logger.debug(msg)
  else
    puts msg
  end
end
mk_complete_key(type) click to toggle source
# File lib/redis-search/finder.rb, line 138
def self.mk_complete_key(type)
  "Compl#{type}"
end
mk_condition_key(type, field, id) click to toggle source
# File lib/redis-search/finder.rb, line 134
def self.mk_condition_key(type, field, id)
  "#{type}:_by:_#{field}:#{id}"
end
mk_score_key(type, id) click to toggle source
# File lib/redis-search/finder.rb, line 130
def self.mk_score_key(type, id)
  "#{type}:_score_:#{id}"
end
mk_sets_key(type, key) click to toggle source

生成 uuid,用于作为 hashes 的 field, sets 关键词的值

# File lib/redis-search/finder.rb, line 126
def self.mk_sets_key(type, key)
  "#{type}:#{key.downcase}"
end
warn(msg) click to toggle source
# File lib/redis-search/finder.rb, line 105
def self.warn(msg)
  return unless Redis::Search.config.debug
  msg = "\e[33m[redis-search] #{msg}\e[0m"
  if defined?(Rails) == 'constant' && Rails.class == Class
    ::Rails.logger.warn(msg)
  else
    puts msg
  end
end

Public Instance Methods

redis_search_alias_value(field) click to toggle source
# File lib/redis-search/base.rb, line 26
def redis_search_alias_value(field)
  return [] if field.blank? || field == '_was'.freeze
  val = (instance_eval("self.#{field}") || ''.freeze).clone
  return [] unless val.class.in?([String, Array])
  val = val.to_s.split(',') if val.is_a?(String)
  val
end
redis_search_fields_to_hash(ext_fields) click to toggle source
# File lib/redis-search/base.rb, line 18
def redis_search_fields_to_hash(ext_fields)
  exts = {}
  ext_fields.each do |f|
    exts[f] = instance_eval(f.to_s)
  end
  exts
end
redis_search_index_after_save() click to toggle source
# File lib/redis-search/base.rb, line 106
def redis_search_index_after_save
  if redis_search_index_need_reindex || new_record?
    redis_search_index_create
  end
  true
end
redis_search_index_after_update() click to toggle source
# File lib/redis-search/base.rb, line 96
def redis_search_index_after_update
  if redis_search_index_need_reindex
    titles = redis_search_alias_value("#{redis_search_options[:alias_field]}_was")
    titles << send("#{redis_search_options[:title_field]}_was")
    redis_search_index_delete(titles)
  end

  true
end
redis_search_index_before_destroy() click to toggle source
# File lib/redis-search/base.rb, line 60
def redis_search_index_before_destroy
  titles = redis_search_alias_value(redis_search_options[:alias_field])
  titles << send(redis_search_options[:title_field])

  redis_search_index_delete(titles)
  true
end
redis_search_index_create() click to toggle source

Rebuild search index with create

# File lib/redis-search/base.rb, line 35
def redis_search_index_create
  opts = {
    title: send(redis_search_options[:title_field]),
    aliases: redis_search_alias_value(redis_search_options[:alias_field]),
    id: id,
    exts: redis_search_fields_to_hash(redis_search_options[:ext_fields]),
    type: redis_search_options[:class_name] || self.class.name,
    condition_fields: redis_search_options[:condition_fields],
    score: send(redis_search_options[:score_field]).to_i
  }

  s = Search::Index.new(opts)
  s.save
  true
end
redis_search_index_delete(titles) click to toggle source
# File lib/redis-search/base.rb, line 51
def redis_search_index_delete(titles)
  titles.uniq!
  titles.each do |title|
    next if title.blank?
    Search::Index.remove(id: id, title: title, type: self.class.name)
  end
  true
end
redis_search_index_need_reindex() click to toggle source
# File lib/redis-search/base.rb, line 68
def redis_search_index_need_reindex
  index_fields_changed = false
  redis_search_options[:ext_fields].each do |f|
    next if f.to_s == 'id'.freeze
    field_method = "#{f}_changed?"
    if methods.index(field_method.to_sym).nil?
      Redis::Search.warn("#{self.class.name} model reindex on update need #{field_method} method.")
      next
    end

    index_fields_changed = true if instance_eval(field_method)
  end

  begin
    if send("#{redis_search_options[:title_field]}_changed?")
      index_fields_changed = true
    end

    if send(redis_search_options[:alias_field]) ||
       send("#{redis_search_options[:title_field]}_changed?")
      index_fields_changed = true
    end
  rescue
  end

  index_fields_changed
end