Module: Redis::Search

Extended by:
ActiveSupport::Concern
Defined in:
lib/redis/search/base.rb,
lib/redis/search/index.rb,
lib/redis/search/config.rb,
lib/redis/search/finder.rb,
lib/redis/search/railtie.rb

Defined Under Namespace

Modules: ClassMethods Classes: Config, Index, Railtie

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.configObject

Returns the value of attribute config.



5
6
7
# File 'lib/redis/search/config.rb', line 5

def config
  @config
end

.indexed_modelsObject

Returns the value of attribute indexed_models.



5
6
7
# File 'lib/redis/search/config.rb', line 5

def indexed_models
  @indexed_models
end

Class Method Details

.complete(type, w, options = {}) ⇒ Object

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

h3. usage:

  • Redis::Search.complete(“Tag”,“r”) => [“Ruby”,“Rails”, “REST”, “Redis”, “Redmine”]

  • Redis::Search.complete(“Tag”,“re”) => [“Redis”, “Redmine”]

  • Redis::Search.complete(“Tag”,“red”) => [“Redis”, “Redmine”]

  • Redis::Search.complete(“Tag”,“redi”) => [“Redis”]



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/redis/search/finder.rb', line 21

def complete(type, w, options = {})
  limit      = options[:limit] || 10
  conditions = options[:conditions] || []
  return [] if (w.blank? && conditions.blank?) || type.blank?

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

  if start = self.config.redis.zrank(key,prefix)
    count = limit
    max_range = start + (rangelen * limit) - 1
    range = self.config.redis.zrange(key,start,max_range)
    while prefix_matchs.length <= count
      start += rangelen
      break if !range || range.length == 0
      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| self.mk_sets_key(type,w) }

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

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

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

  ids = self.config.redis.sort(temp_store_key,
                               limit: [0,limit],
                               by: self.mk_score_key(type,"*"),
                               order: "desc")
  return [] if ids.blank?
  self.hmget(type,ids)
end

.configure {|@config ||= Config.new| ... } ⇒ Object

Yields:



7
8
9
10
11
12
13
14
15
# File 'lib/redis/search/config.rb', line 7

def configure
  yield @config ||= Config.new

  if not @config.disable_rmmseg
    require "rmmseg"
    # loading RMMSeg chinese word dicts.
    RMMSeg::Dictionary.load_dictionaries
  end
end

.query(type, text, options = {}) ⇒ Object

Search items, this will split words by Libmmseg

h3. params:

type      model name
text         search text
:limit    result limit

h3. usage:

  • Redis::Search.query(“Tag”,“Ruby vs Python”)



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/redis/search/finder.rb', line 107

def query(type, text, options = {})
  tm         = Time.now
  result     = []
  limit      = options[:limit] || 10
  sort_field = options[:sort_field] || "id"
  conditions = options[:conditions] || []

  # 如果搜索文本和查询条件均没有,那就直接返回 []
  return result if text.strip.blank? && conditions.blank?

  words = self.split(text)
  words = words.collect { |w| self.mk_sets_key(type,w) }

  condition_keys = []
  if !conditions.blank?
    conditions = conditions[0] if conditions.is_a?(Array)
    conditions.keys.each do |c|
      condition_keys << self.mk_condition_key(type,c,conditions[c])
    end
    # 将条件的 key 放入关键词搜索集合内,用于 sinterstore 搜索
    words += condition_keys
  end

  return result if words.blank?

  temp_store_key = "tmpinterstore:#{words.join("+")}"

  if words.length > 1
    if !self.config.redis.exists(temp_store_key)
      self.config.redis.pipelined do
        # 将多个词语组合对比,得到交集,并存入临时区域
        self.config.redis.sinterstore(temp_store_key,*words)
        # 将临时搜索设为1天后自动清除
        self.config.redis.expire(temp_store_key,86400)

        # 拼音搜索
        if self.config.pinyin_match
          pinyin_words = self.split_pinyin(text)
          pinyin_words = pinyin_words.collect { |w| self.mk_sets_key(type,w) }
          pinyin_words += condition_keys
          temp_sunion_key = "tmpsunionstore:#{words.join("+")}"
          temp_pinyin_store_key = "tmpinterstore:#{pinyin_words.join("+")}"
          # 找出拼音的
          self.config.redis.sinterstore(temp_pinyin_store_key,*pinyin_words)
          # 合并中文和拼音的搜索结果
          self.config.redis.sunionstore(temp_sunion_key,*[temp_store_key,temp_pinyin_store_key])
          # 将临时搜索设为1天后自动清除
          self.config.redis.expire(temp_pinyin_store_key,86400)
          self.config.redis.expire(temp_sunion_key,86400)
        end
        temp_store_key = temp_sunion_key
      end
    end
  else
    temp_store_key = words.first
  end

  # 根据需要的数量取出 ids
  ids = self.config.redis.sort(temp_store_key,
                               limit: [0,limit],
                               by: self.mk_score_key(type,"*"),
                               order: "desc")
  result = self.hmget(type, ids, sort_field: sort_field)
  self.info("{#{type} : \"#{text}\"} | Time spend: #{Time.now - tm}s")
  result
end

.split(text) ⇒ Object

use rmmseg to split words



6
7
8
# File 'lib/redis/search/finder.rb', line 6

def split(text)
  _split(text)
end

Instance Method Details

#redis_search_alias_value(field) ⇒ Object



24
25
26
27
28
29
30
31
32
# File 'lib/redis/search/base.rb', line 24

def redis_search_alias_value(field)
  return [] if field.blank? || field == "_was".freeze
  val = (instance_eval("self.#{field}") || "".freeze).clone
  return [] if !val.class.in?([String,Array])
  if val.is_a?(String)
    val = val.to_s.split(",")
  end
  val
end

#redis_search_fields_to_hash(ext_fields) ⇒ Object



16
17
18
19
20
21
22
# File 'lib/redis/search/base.rb', line 16

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_saveObject



106
107
108
109
110
111
# File 'lib/redis/search/base.rb', line 106

def redis_search_index_after_save
  if self.redis_search_index_need_reindex || self.new_record?
    self.redis_search_index_create
  end
  true
end

#redis_search_index_after_updateObject



95
96
97
98
99
100
101
102
103
104
# File 'lib/redis/search/base.rb', line 95

def redis_search_index_after_update
  if self.redis_search_index_need_reindex
    titles = []
    titles = redis_search_alias_value("#{self.redis_search_options[:alias_field]}_was")
    titles << self.send("#{self.redis_search_options[:title_field]}_was")
    redis_search_index_delete(titles)
  end

  true
end

#redis_search_index_before_destroyObject



59
60
61
62
63
64
65
66
# File 'lib/redis/search/base.rb', line 59

def redis_search_index_before_destroy
  titles = []
  titles = redis_search_alias_value(self.redis_search_options[:alias_field])
  titles << self.send(self.redis_search_options[:title_field])

  redis_search_index_delete(titles)
  true
end

#redis_search_index_createObject

Rebuild search index with create



35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/redis/search/base.rb', line 35

def redis_search_index_create
  s = Search::Index.new(title: self.send(self.redis_search_options[:title_field]),
                        aliases: self.redis_search_alias_value(self.redis_search_options[:alias_field]),
                        id: self.id,
                        exts: self.redis_search_fields_to_hash(self.redis_search_options[:ext_fields]),
                        type: self.redis_search_options[:class_name] || self.class.name,
                        condition_fields: self.redis_search_options[:condition_fields],
                        score: self.send(self.redis_search_options[:score_field]).to_i,
                        prefix_index_enable: self.redis_search_options[:prefix_index_enable])
  s.save
  # release s
  s = nil
  true
end

#redis_search_index_delete(titles) ⇒ Object



50
51
52
53
54
55
56
57
# File 'lib/redis/search/base.rb', line 50

def redis_search_index_delete(titles)
  titles.uniq!
  titles.each do |title|
    next if title.blank?
    Search::Index.remove(id: self.id, title: title, type: self.class.name)
  end
  true
end

#redis_search_index_need_reindexObject



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/redis/search/base.rb', line 68

def redis_search_index_need_reindex
  index_fields_changed = false
  self.redis_search_options[:ext_fields].each do |f|
    next if f.to_s == "id".freeze
    field_method = "#{f}_changed?"
    if self.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 self.send("#{self.redis_search_options[:title_field]}_changed?")
      index_fields_changed = true
    end

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

  return index_fields_changed
end