cache_reduce 0.0.1

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: 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: []