popular_stream 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6df6ce9f9726447977830802fe8a7e6f15cb8c00
4
+ data.tar.gz: 72253cc504abd85e4893ce25ac9d1310c3b5ddc3
5
+ SHA512:
6
+ metadata.gz: 3952dc948f1bef9002534db2d36891ad893e05ad5b790d49b6ab27b08e2cb90a217f8c09310c553f057617a99456f00dca7250d39946fd3c6de3ec410baf2b60
7
+ data.tar.gz: 5e7cdca746f55e2922ecdaec2ee25e06b1120562bf78222a86983c3c3f0e77221f9b2cce34226f051de3b42b952af032ddd35bba8c22458488032da9dadc01bc
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+ --order random
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in popular_stream.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Nicolás Hock Isaza
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # Popular Stream
2
+
3
+ # THIS IS NOT READY YET!
4
+
5
+ Simple popular content tracker with a Redis backend.
6
+
7
+ Mostly taken from the "Popular Stream" code
8
+ [here](http://stdout.heyzap.com/2013/04/08/surfacing-interesting-content/) but
9
+ bundled up as a gem.
10
+
11
+ PopularStream tracks an "event" on a group of "fields" and returns the ones that
12
+ are currently popular.
13
+
14
+ For example, if you want to track the most popular tags right now you, the event
15
+ would be "tagging" and the field would be the tag name ("rubygems" for example).
16
+
17
+ The way this works is that "old" votes will count less than newer votes, that way
18
+ a tag that was used 20 times today will be more popular than a tag used 30 times
19
+ last week.
20
+
21
+ ## Installation
22
+
23
+ Add this line to your application's Gemfile:
24
+
25
+ ```ruby
26
+ gem 'popular_stream'
27
+ ```
28
+
29
+ And then execute:
30
+
31
+ $ bundle
32
+
33
+ Or install it yourself as:
34
+
35
+ $ gem install popular_stream
36
+
37
+ ## Usage
38
+
39
+ There are two main methods `vote` and `get`. `vote` adds an event to the list
40
+ and `get` gets the most popular fields on the distribution.
41
+
42
+ ```ruby
43
+ stream = PopularStream.new("popular_tags")
44
+
45
+ stream.vote(field: 'rubygems')
46
+
47
+ # You can also add an optional `weight` param.
48
+ stream.vote(field: 'ruby', weight: 2)
49
+
50
+ # And you can even specify when the event happened.
51
+ # Notice that time is a number, meaning seconds since Epoc.
52
+ time = Date.yesterday.to_time
53
+ stream.vote(field: 'rubygems', time: time.to_i)
54
+
55
+ stream.get # => ['rubygems', 'ruby']
56
+
57
+ # You can pass `limit` and `offset`
58
+ stream.get(limit: 1, offset: 1) # => ['ruby']
59
+ stream.get(offset: 10) # => []
60
+ ```
61
+
62
+ ## Seting up & Configuring Redis
63
+
64
+ Popular Stream uses Redis as the storage database. By default it will connect to
65
+ the redis client on `ENV['REDIS_URL']`. You can also specify what redis client to
66
+ use with `PopularStream.redis = Redis.new(host: 'example.com')`.
67
+
68
+ Notice that this is for *all* streams. This is because we don't want to create new
69
+ connections for every new stream instance we create, so the same client is used.
70
+
71
+ ## TODO
72
+
73
+ * Sinatra application. I want to bundle an optional Sinatra application that
74
+ makes this gem super simple to setup as a service.
75
+
76
+ * Multiple storage databases. Right now everything's stored on Redis but it should
77
+ be simple to use more stuff. People should be able to create "adapters" and use
78
+ them as needed.
79
+
80
+ ## Contributing
81
+
82
+ 1. Fork it ( https://github.com/nhocki/popular_stream/fork )
83
+ 2. Create your a new branch (`git checkout -b my-new-feature`)
84
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
85
+ 4. Push to the branch (`git push origin my-new-feature`)
86
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ begin
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+ rescue LoadError
5
+ require 'rubygems'
6
+ require 'bundler/gem_tasks'
7
+ require 'rspec/core/rake_task'
8
+ end
9
+
10
+ RSpec::Core::RakeTask.new(:spec)
11
+ task :default => :spec
@@ -0,0 +1,3 @@
1
+ class PopularStream
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,56 @@
1
+ require "popular_stream/version"
2
+
3
+ # Most of this was taken from:
4
+ # http://stdout.heyzap.com/2013/04/08/surfacing-interesting-content/
5
+ class PopularStream
6
+ attr_reader :name, :epoch, :max_items
7
+
8
+ # 2.5 * half_life (in days) years from now. Make this far in the future!
9
+ DEFAULT_EPOC = Date.new(2017, 5, 29).to_time.to_i
10
+ DAY = 60 * 60 * 24
11
+ HALF_LIFE = 1 * DAY
12
+
13
+ class << self
14
+ attr_accessor :redis
15
+
16
+ def redis
17
+ @redis ||= Redis.new(url: ENV['REDIS_URL'])
18
+ end
19
+ end
20
+
21
+ def initialize(name, **options)
22
+ @name = name
23
+ @epoch = options.fetch(:epoch) { DEFAULT_EPOC }
24
+ @max_items = options.fetch(:max_items) { 10_000 }
25
+ end
26
+
27
+ def vote(field:, time: Time.now.to_i, weight: 1)
28
+ time = time.to_i if time.respond_to?(:to_i)
29
+
30
+ delta = 2 ** ((time - epoch) / HALF_LIFE)
31
+ redis.zincrby(name, weight * delta.to_f, field)
32
+ trim
33
+ end
34
+
35
+ def get(limit: 20, offset: 0)
36
+ redis.zrevrange(name, offset, offset + limit - 1)
37
+ end
38
+
39
+ def clear!
40
+ redis.del(name)
41
+ end
42
+
43
+ def count
44
+ redis.zcard(name)
45
+ end
46
+
47
+ def trim
48
+ redis.zremrangebyrank(name, 0, -max_items)
49
+ end
50
+
51
+ private
52
+
53
+ def redis
54
+ self.class.redis
55
+ end
56
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'popular_stream/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "popular_stream"
8
+ spec.version = PopularStream::VERSION
9
+ spec.authors = ["Nicolás Hock Isaza"]
10
+ spec.email = ["nhocki@gmail.com"]
11
+ spec.summary = %q{Ruby gem to track popular streams with a redis backend.}
12
+ spec.description = %q{Ruby gem to track popular streams with a redis backend.}
13
+ spec.homepage = "https://github.com/nhocki/popular_streams"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rspec", "~> 3.1.0"
24
+ spec.add_development_dependency "timecop", "~> 0.7.1"
25
+ spec.add_development_dependency "fakeredis", "~> 0.5.0"
26
+
27
+ spec.add_dependency "redis", "~> 3.1.0"
28
+ end
@@ -0,0 +1,74 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe PopularStream do
4
+ let(:one_week) { 60 * 60 * 24 * 7 } # 1 week in seconds.
5
+ let(:stream) { PopularStream.new("popular_stream") }
6
+
7
+ describe ".redis" do
8
+ let(:redis) { double }
9
+
10
+ it "is possible to set a redis instace" do
11
+ expect { PopularStream.redis = redis }.not_to raise_error
12
+ end
13
+ end
14
+
15
+ describe "#get" do
16
+ before do
17
+ stream.vote(field: '1', time: Time.now - one_week)
18
+ stream.vote(field: '2')
19
+ end
20
+
21
+ it "ranks older votes lower than newer votes" do
22
+ expect(stream.get).to eql(['2', '1'])
23
+ end
24
+
25
+ it "#get takes limit & offset arguments" do
26
+ expect(stream.get(limit: 1)).to eql(['2'])
27
+ expect(stream.get(offset: 1)).to eql(['1'])
28
+ end
29
+
30
+ it "paginates" do
31
+ stream.vote(field: '3', time: Time.now + one_week)
32
+ expect(stream.get(limit: 1, offset: 0)).to eql([ '3' ])
33
+ expect(stream.get(limit: 1, offset: 1)).to eql([ '2' ])
34
+ expect(stream.get(limit: 1, offset: 2)).to eql([ '1' ])
35
+ end
36
+ end
37
+
38
+ it "ranks most popular for same time as higher" do
39
+ Timecop.freeze do
40
+ stream.vote(field: '1')
41
+ stream.vote(field: '1')
42
+ stream.vote(field: '2')
43
+ expect(stream.get).to eql(['1', '2'])
44
+ end
45
+ end
46
+
47
+ it "accepts a weight for the vote of a specific field" do
48
+ Timecop.freeze do
49
+ stream.vote(field: '1')
50
+ stream.vote(field: '1')
51
+ stream.vote(field: '2', weight: 3)
52
+ expect(stream.get).to eql(['2', '1'])
53
+ end
54
+ end
55
+
56
+ it "keeps old votes with lower weight on values" do
57
+ Timecop.freeze do
58
+ stream.vote(field: '1', time: Time.now - 2 * one_week)
59
+ stream.vote(field: '2', time: Time.now - 3 * one_week)
60
+
61
+ stream.vote(field: '1')
62
+ stream.vote(field: '2')
63
+ end
64
+ expect(stream.get).to eql(['1', '2'])
65
+ end
66
+
67
+ it "#count gets the number of elements and #clear! removes them all" do
68
+ stream.vote(field: '1')
69
+ stream.vote(field: '1')
70
+ expect(stream.count).to eql(1)
71
+ stream.clear!
72
+ expect(stream.count).to eql(0)
73
+ end
74
+ end
@@ -0,0 +1,17 @@
1
+ require 'popular_stream'
2
+ require 'fakeredis/rspec'
3
+
4
+ Bundler.require(:default, :test, :development)
5
+
6
+ RSpec.configure do |config|
7
+ config.expect_with :rspec do |expectations|
8
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
9
+ expectations.syntax = :expect
10
+ end
11
+
12
+ config.mock_with :rspec do |mocks|
13
+ mocks.verify_partial_doubles = true
14
+ end
15
+
16
+ config.disable_monkey_patching!
17
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: popular_stream
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Nicolás Hock Isaza
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 3.1.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 3.1.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: timecop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.7.1
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.7.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: fakeredis
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.5.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.5.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: redis
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 3.1.0
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 3.1.0
97
+ description: Ruby gem to track popular streams with a redis backend.
98
+ email:
99
+ - nhocki@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".rspec"
106
+ - Gemfile
107
+ - LICENSE.txt
108
+ - README.md
109
+ - Rakefile
110
+ - lib/popular_stream.rb
111
+ - lib/popular_stream/version.rb
112
+ - popular_stream.gemspec
113
+ - spec/lib/popular_stream_spec.rb
114
+ - spec/spec_helper.rb
115
+ homepage: https://github.com/nhocki/popular_streams
116
+ licenses:
117
+ - MIT
118
+ metadata: {}
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ requirements: []
134
+ rubyforge_project:
135
+ rubygems_version: 2.2.2
136
+ signing_key:
137
+ specification_version: 4
138
+ summary: Ruby gem to track popular streams with a redis backend.
139
+ test_files:
140
+ - spec/lib/popular_stream_spec.rb
141
+ - spec/spec_helper.rb