Module: Predictor::Base

Defined in:
lib/predictor/base.rb

Defined Under Namespace

Modules: ClassMethods

Class Method Summary collapse

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args) ⇒ Object



54
55
56
57
58
59
60
# File 'lib/predictor/base.rb', line 54

def method_missing(method, *args)
  if input_matrices.has_key?(method)
    input_matrices[method]
  else
    raise NoMethodError.new(method.to_s)
  end
end

Class Method Details

.included(base) ⇒ Object



2
3
4
# File 'lib/predictor/base.rb', line 2

def self.included(base)
  base.extend(ClassMethods)
end

Instance Method Details

#add_to_matrix(matrix, set, *items) ⇒ Object



70
71
72
73
# File 'lib/predictor/base.rb', line 70

def add_to_matrix(matrix, set, *items)
  items = items.flatten if items.count == 1 && items[0].is_a?(Array)  # Old syntax
  input_matrices[matrix].add_to_set(set, *items)
end

#add_to_matrix!(matrix, set, *items) ⇒ Object



75
76
77
78
79
# File 'lib/predictor/base.rb', line 75

def add_to_matrix!(matrix, set, *items)
  items = items.flatten if items.count == 1 && items[0].is_a?(Array)  # Old syntax
  add_to_matrix(matrix, set, *items)
  process_items!(*items)
end

#all_itemsObject



66
67
68
# File 'lib/predictor/base.rb', line 66

def all_items
  Predictor.redis.smembers(redis_key(:all_items))
end

#clean!Object



171
172
173
174
175
176
# File 'lib/predictor/base.rb', line 171

def clean!
  keys = Predictor.redis.keys("#{self.redis_prefix}:*")
  unless keys.empty?
    Predictor.redis.del(keys)
  end
end

#delete_from_matrix!(matrix, item) ⇒ Object



145
146
147
148
149
150
151
# File 'lib/predictor/base.rb', line 145

def delete_from_matrix!(matrix, item)
  # Deleting from a specific matrix, so get related_items, delete, then update the similarity of those related_items
  items = related_items(item)
  input_matrices[matrix].delete_item(item)
  items.each { |related_item| cache_similarity(item, related_item) }
  return self
end

#delete_item!(item) ⇒ Object



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/predictor/base.rb', line 153

def delete_item!(item)
  Predictor.redis.srem(redis_key(:all_items), item)
  Predictor.redis.watch(redis_key(:similarities, item)) do
    items = related_items(item)
    Predictor.redis.multi do |multi|
      items.each do |related_item|
        multi.zrem(redis_key(:similarities, related_item), item)
      end
      multi.del redis_key(:similarities, item)
    end
  end

  input_matrices.each do |k,m|
    m.delete_item(item)
  end
  return self
end

#ensure_similarity_limit_is_obeyed!Object



178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/predictor/base.rb', line 178

def ensure_similarity_limit_is_obeyed!
  if similarity_limit
    items = all_items
    Predictor.redis.multi do |multi|
      items.each do |item|
        key = redis_key(:similarities, item)
        multi.zremrangebyrank(key, 0, -(similarity_limit + 1))
        multi.zunionstore key, [key] # Rewrite zset to take advantage of ziplist implementation.
      end
    end
  end
end

#input_matricesObject



35
36
37
38
39
40
# File 'lib/predictor/base.rb', line 35

def input_matrices
  @input_matrices ||= Hash[self.class.input_matrices.map{ |key, opts|
    opts.merge!(:key => key, :redis_prefix => redis_prefix)
    [ key, Predictor::InputMatrix.new(opts) ]
  }]
end

#predictions_for(set = nil, item_set: nil, matrix_label: nil, with_scores: false, offset: 0, limit: -1,, exclusion_set: []) ⇒ Object



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/predictor/base.rb', line 91

def predictions_for(set=nil, item_set: nil, matrix_label: nil, with_scores: false, offset: 0, limit: -1, exclusion_set: [])
  fail "item_set or matrix_label and set is required" unless item_set || (matrix_label && set)

  if matrix_label
    matrix = input_matrices[matrix_label]
    item_set = Predictor.redis.smembers(matrix.redis_key(:items, set))
  end

  item_keys = item_set.map { |item| redis_key(:similarities, item) }
  return [] if item_keys.empty?
  predictions = nil
  Predictor.redis.multi do |multi|
    multi.zunionstore 'temp', item_keys
    multi.zrem 'temp', item_set
    multi.zrem 'temp', exclusion_set if exclusion_set.length > 0
    predictions = multi.zrevrange 'temp', offset, limit == -1 ? limit : offset + (limit - 1), with_scores: with_scores
    multi.del 'temp'
  end
  predictions.value
end

#process!Object



140
141
142
143
# File 'lib/predictor/base.rb', line 140

def process!
  process_items!(*all_items)
  return self
end

#process_item!(item) ⇒ Object



128
129
130
# File 'lib/predictor/base.rb', line 128

def process_item!(item)
  process_items!(item)  # Old method
end

#process_items!(*items) ⇒ Object



132
133
134
135
136
137
138
# File 'lib/predictor/base.rb', line 132

def process_items!(*items)
  items = items.flatten if items.count == 1 && items[0].is_a?(Array)  # Old syntax
  items.each do |item|
    related_items(item).each{ |related_item| cache_similarity(item, related_item) }
  end
  return self
end

#redis_key(*append) ⇒ Object



50
51
52
# File 'lib/predictor/base.rb', line 50

def redis_key(*append)
  ([redis_prefix] + append).flatten.compact.join(":")
end

#redis_prefixObject



42
43
44
# File 'lib/predictor/base.rb', line 42

def redis_prefix
  "predictor"
end


81
82
83
84
85
86
87
88
89
# File 'lib/predictor/base.rb', line 81

def related_items(item)
  keys = []
  input_matrices.each do |key, matrix|
    sets = Predictor.redis.smembers(matrix.redis_key(:sets, item))
    keys.concat(sets.map { |set| matrix.redis_key(:items, set) })
  end

  keys.empty? ? [] : (Predictor.redis.sunion(keys) - [item.to_s])
end

#respond_to?(method) ⇒ Boolean

Returns:

  • (Boolean)


62
63
64
# File 'lib/predictor/base.rb', line 62

def respond_to?(method)
  input_matrices.has_key?(method) ? true : super
end

#sets_for(item) ⇒ Object



123
124
125
126
# File 'lib/predictor/base.rb', line 123

def sets_for(item)
  keys = input_matrices.map{ |k,m| m.redis_key(:sets, item) }
  Predictor.redis.sunion keys
end

#similarities_for(item, with_scores: false, offset: 0, limit: -1,, exclusion_set: []) ⇒ Object



112
113
114
115
116
117
118
119
120
121
# File 'lib/predictor/base.rb', line 112

def similarities_for(item, with_scores: false, offset: 0, limit: -1, exclusion_set: [])
  neighbors = nil
  Predictor.redis.multi do |multi|
    multi.zunionstore 'temp', [1, redis_key(:similarities, item)]
    multi.zrem 'temp', exclusion_set if exclusion_set.length > 0
    neighbors = multi.zrevrange('temp', offset, limit == -1 ? limit : offset + (limit - 1), with_scores: with_scores)
    multi.del 'temp'
  end
  return neighbors.value
end

#similarity_limitObject



46
47
48
# File 'lib/predictor/base.rb', line 46

def similarity_limit
  self.class.similarity_limit
end