Last active
August 29, 2015 14:08
-
-
Save nhocki/2ccc4dcd8a1cdac3e9ba to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# http://stdout.heyzap.com/2013/04/08/surfacing-interesting-content/ | |
class PopularStream | |
attr_reader :name, :epoch, :max_items | |
# 2.5 * half_life (in days) years from now | |
DEFAULT_EPOC = Date.new(2017, 4, 27).to_time.to_i | |
HALF_LIFE = 1.day.to_i | |
def self.redis | |
RedisConfig.client(:trending_stuff) | |
end | |
def initialize(name, **options) | |
@name = name | |
@epoch = options.fetch(:epoch) { DEFAULT_EPOC } | |
@max_items = options.fetch(:max_items) { 10_000 } | |
end | |
def vote(field:, time: Time.now.to_i, weight: 1) | |
time = time.to_i if time.respond_to?(:to_i) | |
lambda = 2 ** ((time - epoch) / HALF_LIFE) | |
redis.zincrby(name, weight * lambda.to_f, field) | |
trim | |
end | |
def get(limit: 20, offset: 0) | |
redis.zrevrange(name, offset, offset + limit - 1) | |
end | |
def clear! | |
redis.del(name) | |
end | |
def count | |
redis.zcard(name) | |
end | |
def trim | |
redis.zremrangebyrank(name, 0, -max_items) | |
end | |
private | |
def redis | |
self.class.redis | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "spec_helper" | |
describe PopularStream do | |
let(:stream) { PopularStream.new("popular_collections") } | |
describe "#get" do | |
before do | |
stream.vote(field: '1', time: 2.weeks.ago) | |
stream.vote(field: '2') | |
end | |
it "ranks older votes lower than newer votes" do | |
expect(stream.get).to eql(['2', '1']) | |
end | |
it "#get takes limit & offset arguments" do | |
expect(stream.get(limit: 1)).to eql(['2']) | |
expect(stream.get(offset: 1)).to eql(['1']) | |
end | |
it "paginates" do | |
stream.vote(field: '3', time: 1.week.from_now) | |
expect(stream.get(limit: 1, offset: 0)).to eql([ '3' ]) | |
expect(stream.get(limit: 1, offset: 1)).to eql([ '2' ]) | |
expect(stream.get(limit: 1, offset: 2)).to eql([ '1' ]) | |
end | |
end | |
it "ranks most popular for same time as higher" do | |
Timecop.freeze do | |
stream.vote(field: '1') | |
stream.vote(field: '1') | |
stream.vote(field: '2') | |
expect(stream.get).to eql(['1', '2']) | |
end | |
end | |
it "accepts a weight for the vote of a specific field" do | |
Timecop.freeze do | |
stream.vote(field: '1') | |
stream.vote(field: '1') | |
stream.vote(field: '2', weight: 3) | |
expect(stream.get).to eql(['2', '1']) | |
end | |
end | |
it "keeps old votes with lower weight on values" do | |
Timecop.freeze do | |
stream.vote(field: '1', time: 2.weeks.ago) | |
stream.vote(field: '2', time: 3.weeks.ago) | |
stream.vote(field: '1') | |
stream.vote(field: '2') | |
end | |
expect(stream.get).to eql(['1', '2']) | |
end | |
it "#count gets the number of elements and #clear! removes them all" do | |
stream.vote(field: '1') | |
stream.vote(field: '1') | |
expect(stream.count).to eql(1) | |
stream.clear! | |
expect(stream.count).to eql(0) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment