require 'ohm' require 'ohm/contrib' module Ohm module Tallyable module Macros def tally(attribute, options={}) tallies[attribute] = options end def tallies @tallies ||= {} end def leaderboard(attribute, by=nil) tally = tallies[attribute] if tally.nil? || (tally[:by] && (by.nil? || !by.include?(tally[:by]))) raise ArgumentError end key = self.key[:tallies][attribute] if by key = key[by.keys.first][by.values.first] end _load_zset(key) .map { |k, v| [k, v.to_i] } .sort_by { |k, v| [-v, k] } end if Redis::VERSION.to_i == 2 def _load_zset(key) key.zrevrange(0, -1, with_scores: true).each_slice(2) end else def _load_zset(key) key.zrevrange(0, -1, with_scores: true) end end end def self.included(model) model.before(:delete, :_decrement_tallies) model.before(:save, :_decrement_tallies) model.after(:save, :_increment_tallies) model.extend(Macros) end def _decrement_tallies _update_tallies(-1) { |attribute| read_remote(attribute) } end def _increment_tallies _update_tallies(1) { |attribute| send(attribute) } end def _update_tallies(amount, &block) self.class.tallies.each do |attribute, options| value = yield(attribute) key = self.class.key[:tallies][attribute] if options[:by] value_by = yield(options[:by]) key = key[options[:by]][value_by] end if value key.zincrby(amount, value) key.zrem(value) if key.zscore(value) == 0.0 end end end end end