cache_reduce 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 22c4d6f4018a2ace22fb44c20f93130d2ce67c88
4
+ data.tar.gz: 5dfbd810aced627db4b1897abbf155f07419166b
5
+ SHA512:
6
+ metadata.gz: c8e89c4c2c759111988ca57328477e7b5139200d4115769dc5e7286aa61f51bfff326bbf0c3e299a5a3a17b9d94c2dc3301b55d3d9a5ef50fa3dea87918b5f32
7
+ data.tar.gz: 2ee7f24160e00e21ccf47e5b47aae36a872ae5ed16f907ed6758256f2fc41bcf1c4902bf5a1d8ba89e13a96f096c7cab1bf12e060ad860b9646a0000de0dd29a
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cache_reduce.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Andrew Kane
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,147 @@
1
+ # CacheReduce
2
+
3
+ A simple, powerful pattern for caching data
4
+
5
+ :warning: Work in progress - interface not finalized
6
+
7
+ ## How It Works
8
+
9
+ 1. Cache step - data is cached in small buckets by time
10
+ - Break time range into small buckets
11
+ - Read values from cache
12
+ - Caclulate missing values
13
+ - Cache new values, except the current period
14
+
15
+ 2. Reduce step - group buckets and reduce
16
+
17
+ Since the current period is always calculated, CacheReduce provides real-time results.
18
+
19
+ ## Examples
20
+
21
+ ### Searches
22
+
23
+ ```ruby
24
+ class SearchesCount
25
+ include CacheReduce::Magic
26
+
27
+ def value(time_range)
28
+ Search.where(created_at: time_range).count
29
+ end
30
+ end
31
+ ```
32
+
33
+ Count all searches
34
+
35
+ ```ruby
36
+ SearchesCount.all(time_range: time_range)
37
+ ```
38
+
39
+ Count searches by day
40
+
41
+ ```ruby
42
+ SearchesCount.by_day(time_range: time_range)
43
+ ```
44
+
45
+ ### New Users
46
+
47
+ Use the preload method for faster cache warming.
48
+
49
+ ```ruby
50
+ class NewUsersCount
51
+ include CacheReduce::Magic
52
+
53
+ def preload(time_range)
54
+ @users = User.group_by_hour(:created_at, range: time_range).count
55
+ end
56
+
57
+ def value(time_range)
58
+ @users[time_range.first]
59
+ end
60
+ end
61
+ ```
62
+
63
+ And:
64
+
65
+ ```ruby
66
+ NewUsersCount.all(time_range: time_range)
67
+ NewUsersCount.by_day(time_range: time_range)
68
+ ```
69
+
70
+ ### Visitors
71
+
72
+ Reduce ids instead of numbers - great for creating funnels
73
+
74
+ ```ruby
75
+ class VisitorIds
76
+ include CacheReduce::Magic
77
+
78
+ def value(time_range)
79
+ Visit.where(created_at: time_range).uniq.pluck(:user_id)
80
+ end
81
+
82
+ def reduce(values)
83
+ values.flatten
84
+ end
85
+ end
86
+ ```
87
+
88
+ ### Events
89
+
90
+ Pass your own arguments
91
+
92
+ ```ruby
93
+ class EventCount
94
+ include CacheReduce::Magic
95
+
96
+ def initialize(name)
97
+ @name = name
98
+ end
99
+
100
+ def key
101
+ ["event", @name]
102
+ end
103
+
104
+ def value(time_range)
105
+ Ahoy::Event.where(name: @name, time: time_range).count
106
+ end
107
+
108
+ end
109
+ ```
110
+
111
+ ### Reducers
112
+
113
+ Built-in methods
114
+
115
+ - all
116
+ - by_day
117
+ - by_hour
118
+
119
+ Built-in operations
120
+
121
+ - sum
122
+ - flatten
123
+
124
+ ## Installation
125
+
126
+ Add this line to your application’s Gemfile:
127
+
128
+ ```ruby
129
+ gem 'cache_reduce'
130
+ ```
131
+
132
+ ## TODO
133
+
134
+ - more reducer methods - `by_week`, `by_month`, etc.
135
+ - custom cache period, not just hours
136
+ - more data stores
137
+ - better interface
138
+ - better readme
139
+
140
+ ## Contributing
141
+
142
+ Everyone is encouraged to help improve this project. Here are a few ways you can help:
143
+
144
+ - [Report bugs](https://github.com/ankane/cache_reduce/issues)
145
+ - Fix bugs and [submit pull requests](https://github.com/ankane/cache_reduce/pulls)
146
+ - Write, clarify, or fix documentation
147
+ - Suggest or add new features
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cache_reduce/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "cache_reduce"
8
+ spec.version = CacheReduce::VERSION
9
+ spec.authors = ["Andrew Kane"]
10
+ spec.email = ["andrew@chartkick.com"]
11
+ spec.summary = %q{A simple, powerful pattern for caching data}
12
+ spec.description = %q{A simple, powerful pattern for caching data}
13
+ spec.homepage = "https://github.com/ankane/cache_reduce"
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.6"
22
+ spec.add_development_dependency "rake"
23
+ end
@@ -0,0 +1,122 @@
1
+ require "cache_reduce/version"
2
+
3
+ module CacheReduce
4
+ module Magic
5
+
6
+ def all(options = {})
7
+ reduce(fetch(options).values)
8
+ end
9
+
10
+ def by_day(options = {})
11
+ result = fetch(options)
12
+ time_range = result.keys.first..result.keys.last
13
+ days = [time_range.first.beginning_of_day]
14
+ while day = days.last + 1.day and time_range.cover?(day)
15
+ days << day
16
+ end
17
+ grouped = result.group_by{|k, v| k.beginning_of_day }
18
+
19
+ final = {}
20
+ days.each do |day|
21
+ final[day] = reduce((grouped[day] || []).map(&:last))
22
+ end
23
+ final
24
+ end
25
+
26
+ def by_hour(options = {})
27
+ fetch(options)
28
+ end
29
+
30
+ protected
31
+
32
+ def preload(time_range)
33
+ end
34
+
35
+ def value(time_range)
36
+ raise "Must define value"
37
+ end
38
+
39
+ def reduce(values)
40
+ values.sum
41
+ end
42
+
43
+ def key
44
+ self.class.name.underscore
45
+ end
46
+
47
+ def cache_store
48
+ @cache_store ||= CacheStore.new
49
+ end
50
+
51
+ def fetch(options = {})
52
+ time_range = options[:time_range]
53
+
54
+ hours = [time_range.first.beginning_of_hour]
55
+ while hour = hours.last + 1.hour and time_range.cover?(hour)
56
+ hours << hour
57
+ end
58
+
59
+ # TODO handle advanced objects as keys
60
+ key = self.key
61
+ key = key.join("/") if key.is_a?(Array)
62
+ keys = hours.map{|h| [key, "hour", h.to_i].join("/") }
63
+ values = options[:fresh] ? {} : cache_store.read(keys)
64
+
65
+ # TODO smarter groups
66
+ range_start = nil
67
+ hours.each_with_index do |hour, i|
68
+ value = values[keys[i]]
69
+ if !value
70
+ range_start = hour
71
+ break
72
+ end
73
+ end
74
+
75
+ preload(range_start...hours.last + 1.hour)
76
+
77
+ result = {}
78
+ hours.each_with_index do |hour, i|
79
+ key = keys[i]
80
+ result[hour] =
81
+ if values[key]
82
+ values[key]
83
+ else
84
+ v = value(hour...hour + 1.hour)
85
+ if hour.end_of_hour < Time.now
86
+ cache_store.write(key, v)
87
+ end
88
+ v
89
+ end
90
+ end
91
+
92
+ result
93
+ end
94
+
95
+ end
96
+
97
+ class CacheStore
98
+
99
+ # TODO update interface
100
+ # key, period, hours
101
+ def read(keys)
102
+ # puts "READ: #{keys}"
103
+ values = {}
104
+ redis.mget(*keys).each_with_index do |value, i|
105
+ key = keys[i]
106
+ value = redis.get(key)
107
+ values[key] = value ? JSON.parse("[" + value + "]")[0] : nil
108
+ end
109
+ values
110
+ end
111
+
112
+ def write(key, value)
113
+ # puts "WRITE: #{key} #{value}"
114
+ redis.set(key, value.to_json)
115
+ end
116
+
117
+ def redis
118
+ @redis ||= Redis.new
119
+ end
120
+
121
+ end
122
+ end
@@ -0,0 +1,3 @@
1
+ module CacheReduce
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cache_reduce
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Kane
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-20 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.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: A simple, powerful pattern for caching data
42
+ email:
43
+ - andrew@chartkick.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - Gemfile
50
+ - LICENSE.txt
51
+ - README.md
52
+ - Rakefile
53
+ - cache_reduce.gemspec
54
+ - lib/cache_reduce.rb
55
+ - lib/cache_reduce/version.rb
56
+ homepage: https://github.com/ankane/cache_reduce
57
+ licenses:
58
+ - MIT
59
+ metadata: {}
60
+ post_install_message:
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubyforge_project:
76
+ rubygems_version: 2.2.2
77
+ signing_key:
78
+ specification_version: 4
79
+ summary: A simple, powerful pattern for caching data
80
+ test_files: []