timemaster 0.1.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.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ .rake_test_cache
2
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 brianthecoder
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,17 @@
1
+ = Timemaster
2
+
3
+ Description goes here.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010 brianthecoder. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,69 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "timemaster"
8
+ gem.summary = %Q{A way to easily store time based data scalably in riak}
9
+ gem.description = %Q{Creates buckets for a given resolution and will create all the appropriate links}
10
+ gem.email = "wbsmith83@gmail.com"
11
+ gem.homepage = "http://github.com/BrianTheCoder/chronos"
12
+ gem.authors = ["brianthecoder"]
13
+ gem.files.include %w(lib/timemaster.rb lib/timemaster/document.rb lib/timemaster/extensions.rb lib/timemaster/helpers.rb lib/timemaster/resolution.rb lib/timemaster/version.rb)
14
+ gem.add_development_dependency "yard"
15
+ gem.add_dependency "riak-client"
16
+ gem.add_dependency "activesupport"
17
+ gem.add_dependency "hashie"
18
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
19
+ end
20
+ Jeweler::GemcutterTasks.new
21
+ rescue LoadError
22
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
23
+ end
24
+
25
+ require 'rake/testtask'
26
+ Rake::TestTask.new(:test) do |test|
27
+ test.libs << 'lib' << 'test'
28
+ test.pattern = 'test/**/*_test.rb'
29
+ test.verbose = true
30
+ end
31
+
32
+ begin
33
+ require 'rcov/rcovtask'
34
+ Rcov::RcovTask.new do |test|
35
+ test.libs << 'test'
36
+ test.pattern = 'test/**/*_test.rb'
37
+ test.verbose = true
38
+ end
39
+ rescue LoadError
40
+ task :rcov do
41
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
42
+ end
43
+ end
44
+
45
+ task :test => :check_dependencies
46
+
47
+ begin
48
+ require 'reek/rake_task'
49
+ Reek::RakeTask.new do |t|
50
+ t.fail_on_error = true
51
+ t.verbose = false
52
+ t.source_files = 'lib/**/*.rb'
53
+ end
54
+ rescue LoadError
55
+ task :reek do
56
+ abort "Reek is not available. In order to run reek, you must: sudo gem install reek"
57
+ end
58
+ end
59
+
60
+ task :default => :test
61
+
62
+ begin
63
+ require 'yard'
64
+ YARD::Rake::YardocTask.new
65
+ rescue LoadError
66
+ task :yardoc do
67
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
68
+ end
69
+ end
data/TODO ADDED
@@ -0,0 +1,26 @@
1
+ class Request
2
+ include Chronos::Document
3
+
4
+ # default, assumes all levels above it
5
+ resolution :minute
6
+
7
+ # generates automatically if not defined
8
+ def key
9
+ # blarg
10
+ end
11
+ end
12
+
13
+ * saving documents
14
+ * check that lined docs exist
15
+ * resolution content_type?
16
+ * queurying
17
+ * single doc
18
+ * epoch
19
+ * range
20
+ * stats (map/reduce)
21
+ * counts
22
+ * sum
23
+ * avg
24
+
25
+ * easy custom links
26
+ * query by custom links
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,42 @@
1
+ require File.join(File.dirname(__FILE__),'../lib/chronos')
2
+
3
+ require 'timecop'
4
+ require 'randexp'
5
+
6
+ Chronos.riak.port = 8091
7
+
8
+ class Request
9
+ include Chronos::Document
10
+
11
+ resolution :minute
12
+
13
+ tag :controller_name
14
+ tag :action_name
15
+ end
16
+
17
+ module Riak
18
+ class Bucket
19
+ def wipe!
20
+ keys do |key_array|
21
+ key_array.each{|key| delete(key)}
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ def wipe_all!
28
+ %w(years months days hours minutes requests).each{|name| Chronos.riak.bucket(name).wipe! }
29
+ end
30
+
31
+ def seed
32
+ controllers = %w(users posts settings photos)
33
+ actions = %w(index new create edit update destroy)
34
+
35
+ Timecop.freeze(Time.now)
36
+ 200.of do
37
+ Timecop.travel(rand(10))
38
+ req = Request.new(:controller_name => controllers.pick,
39
+ :action_name => actions.pick,
40
+ :response_time => 5.of{ 60 + rand(40)}.sum)
41
+ end
42
+ end
data/lib/timemaster.rb ADDED
@@ -0,0 +1,22 @@
1
+ require 'active_support/core_ext'
2
+ require 'riak/client'
3
+ require 'hashie'
4
+
5
+ $:.unshift File.dirname(__FILE__)
6
+
7
+ require 'timemaster/helpers'
8
+ require 'timemaster/extensions'
9
+ require 'timemaster/document'
10
+ require 'timemaster/resolution'
11
+
12
+ module Timemaster
13
+ extend self
14
+
15
+ def riak
16
+ @riak ||= Riak::Client.new
17
+ end
18
+
19
+ def riak=(client)
20
+ @riak ||= Riak::Client.new
21
+ end
22
+ end
@@ -0,0 +1,158 @@
1
+ module Timemaster
2
+ module Document
3
+ def self.included(klass)
4
+ klass.extend ClassMethods
5
+ klass.extend Helpers
6
+ klass.send(:include, Helpers)
7
+ klass.send(:attr_accessor, *[:key, :attributes])
8
+ klass.send(:attr_reader, :time)
9
+ klass.cattr_accessor :resolution_period
10
+ klass.cattr_accessor :tags
11
+ end
12
+
13
+ module ClassMethods
14
+ def get(id)
15
+ begin
16
+ new bucket.get(id)
17
+ rescue Riak::FailedRequest
18
+ nil
19
+ end
20
+ end
21
+
22
+ def bucket_name
23
+ @bucket_name ||= self.to_s.tableize
24
+ end
25
+
26
+ def resolution(period)
27
+ self.resolution_period = period
28
+ end
29
+
30
+ def epoch(period = :hour, time = Time.now.utc, tag = nil)
31
+ # res_instance = Resolution.new(options[:resolution], options[:time])
32
+ index = Resolution.index(resolution_period)
33
+ res_index = Resolution.index(period)
34
+ resolutions = []
35
+ RESOLUTIONS[0..index].each_with_index do |res_period, i|
36
+ resolutions[i] = Resolution.new(res_period, (i <= res_index) ? time : nil)
37
+ end
38
+ root = resolutions.shift.riak_object
39
+ root.walk(resolutions.map(&:walk_spec).insert(-1, Riak::WalkSpec.new('requests', tag, true))).flatten
40
+ end
41
+
42
+ def period(resolution = :hour, start = 1.day.ago, stop = Time.now)
43
+ res_instance = Resolution.new(:hour)
44
+ keys = (start..stop).send(:"to_#{res_instance.bucket_name}")
45
+ keys.map! do |time|
46
+ res_instance.time = time
47
+ res_instance.key
48
+ end
49
+ keys
50
+ end
51
+
52
+ def tag(new_tag, options = {})
53
+ self.tags ||= []
54
+ self.tags << new_tag
55
+ end
56
+ end
57
+
58
+ def initialize(params = {})
59
+ case params
60
+ when Hash
61
+ @time = params.delete(:time) || Time.now.utc
62
+ @attributes = Hashie::Mash.new(params)
63
+ when Riak::RObject
64
+ self.riak_object = params
65
+ end
66
+ end
67
+
68
+ def make_links
69
+ return link_for if self.class.tags.blank?
70
+ self.class.tags.map{|key| link_for("#{key}_#{@attributes[key.to_sym]}")}.flatten.compact
71
+ end
72
+
73
+ def time_from_link(link)
74
+ Time.utc(*link.url.split('/').last.split('_'))
75
+ end
76
+
77
+ def resolutions
78
+ @resolutions ||= RESOLUTIONS[0..resolution_index].inject({}) do |hash, period|
79
+ hash[period] = Resolution.new(period, time)
80
+ hash
81
+ end
82
+ end
83
+
84
+ def resolution_index
85
+ @resolution_index ||= RESOLUTIONS.index(self.class.resolution_period)
86
+ end
87
+
88
+ def time=(timestamp)
89
+ case timestamp
90
+ when Date
91
+ self.time = timestamp.to_time
92
+ when Time
93
+ @time = timestamp.utc
94
+ when Riak::Link
95
+ self.time = time_from_link(timestamp)
96
+ end
97
+ end
98
+
99
+ def [](key)
100
+ @attributes[key]
101
+ end
102
+
103
+ def []=(key, value)
104
+ @attributes[key] = value
105
+ end
106
+
107
+ def method_missing(method_symbol, *args)
108
+ method_name = method_symbol.to_s
109
+ if %w(? =).include?(method_name[-1,1])
110
+ method = method_name[0..-2]
111
+ operator = method_name[-1,1]
112
+ if operator == '='
113
+ set_value(method, args.first)
114
+ elsif operator == '?'
115
+ !@attributes[method].blank?
116
+ end
117
+ else
118
+ @attributes[method_name]
119
+ end
120
+ end
121
+
122
+ def set_value(method, val)
123
+ if val.blank?
124
+ @attributes.delete(method)
125
+ else
126
+ @attributes[method] = val
127
+ end
128
+ end
129
+
130
+ def bucket_name
131
+ @bucket_name ||= self.class.bucket_name
132
+ end
133
+
134
+ def resolution_docs
135
+ RESOLUTIONS[0..resolution_index].each do |period|
136
+ res_period = resolutions[period]
137
+ unless res_period.exists?
138
+ if (child = resolutions[res_period.next])
139
+ res_period.links << child.link
140
+ end
141
+ res_period.store
142
+ if (parent = resolutions[res_period.prev])
143
+ parent.update_links!(res_period.link)
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ def save
150
+ resolution_docs
151
+ data = @attributes
152
+ store
153
+ self.key = riak_object.key
154
+ linked_res = resolutions[self.class.resolution_period]
155
+ linked_res.update_links!(make_links)
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,84 @@
1
+ module Timemaster
2
+ module Extensions
3
+ extend self
4
+
5
+ def time_formats
6
+ { :year => '%Y',
7
+ :month => '%m',
8
+ :day => '%d',
9
+ :hour => '%H',
10
+ :minute => '%M',
11
+ :second => '%S'}
12
+ end
13
+ end
14
+ end
15
+
16
+ Timemaster::Extensions.time_formats.each do |k, v|
17
+ Date::DATE_FORMATS.update(k => v)
18
+ Time::DATE_FORMATS.update(k => v)
19
+ end
20
+
21
+ class Range
22
+ ##
23
+ # Takes a range and converts it to an array of months
24
+ #
25
+ # (Time.now..1.day.from_now).to_hours
26
+ #
27
+ def to_months
28
+ return if first > last
29
+ arr = []
30
+ time = first
31
+ while time <= last
32
+ arr << time
33
+ time += 1.month
34
+ end
35
+ return arr
36
+ end
37
+ ##
38
+ # Takes a range and converts it to an array of days
39
+ #
40
+ # (Time.now..7.days.from_now).to_days
41
+ #
42
+ def to_days
43
+ return if first > last
44
+ arr = []
45
+ time = first
46
+ while time <= last
47
+ arr << time
48
+ time += 1.day
49
+ end
50
+ return arr
51
+ end
52
+
53
+ ##
54
+ # Takes a range and converts it to an array of hours
55
+ #
56
+ # (Time.now..1.day.from_now).to_hours
57
+ #
58
+ def to_hours
59
+ return if first > last
60
+ arr = []
61
+ time = first
62
+ while time <= last
63
+ arr << time
64
+ time += 1.hour
65
+ end
66
+ return arr
67
+ end
68
+
69
+ ##
70
+ # Takes a range and converts it to an array of minutes
71
+ #
72
+ # (Time.now..1.day.from_now).to_hours
73
+ #
74
+ def to_minutes
75
+ return if first > last
76
+ arr = []
77
+ time = first
78
+ while time <= last
79
+ arr << time
80
+ time += 1.minute
81
+ end
82
+ return arr
83
+ end
84
+ end
@@ -0,0 +1,29 @@
1
+ module Timemaster
2
+ module Helpers
3
+ def bucket
4
+ @bucket ||= Chronos.riak.bucket(bucket_name)
5
+ end
6
+
7
+ def riak_object
8
+ @riak_object ||= bucket.new(key)
9
+ end
10
+
11
+ delegate :walk, :links, :store, :data, :to => :riak_object
12
+
13
+ def path
14
+ @path ||= ['', 'riak', bucket_name, key].join('/')
15
+ end
16
+
17
+ def exists?
18
+ @exists ||= (key.blank? ? false : bucket.exists?(key))
19
+ end
20
+
21
+ def link_for(tag = nil)
22
+ Riak::Link.new(path, tag)
23
+ end
24
+
25
+ def walk_spec(tag = nil, keep = false)
26
+ Riak::WalkSpec.new(bucket_name, (tag||default_tag), keep)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,62 @@
1
+ module Timemaster
2
+ RESOLUTIONS = [:year, :month, :day, :hour, :minute, :second]
3
+ class Resolution
4
+ include Helpers
5
+
6
+ def self.index(resolution)
7
+ RESOLUTIONS.index(resolution)
8
+ end
9
+
10
+ attr_accessor :name, :time
11
+ def initialize(name, time)
12
+ @name = name
13
+ @time = case time
14
+ when Time then
15
+ time
16
+ when String
17
+ Time.utc(*time.split('_'))
18
+ when Nil
19
+ Time.now.utc
20
+ end
21
+ end
22
+
23
+ def index
24
+ @index ||= self.class.index(name)
25
+ end
26
+
27
+ def default_tag
28
+ return @tag if @tag
29
+ return nil unless @time
30
+ @tag = @time.to_s(name)
31
+ end
32
+
33
+ def prev
34
+ (index >= 1) ? RESOLUTIONS[index - 1] : nil
35
+ end
36
+
37
+ def next
38
+ RESOLUTIONS[index + 1]
39
+ end
40
+
41
+ def bucket_name
42
+ @bucket_name ||= name.to_s.tableize
43
+ end
44
+
45
+ def update_links!(*args)
46
+ obj = bucket.get(key)
47
+ obj.links.merge args.flatten
48
+ obj.store
49
+ end
50
+
51
+ def key
52
+ return @key if @key
53
+ # join string up to resolution and user to format time
54
+ return @key = nil if index.blank?
55
+ @key = RESOLUTIONS[0..index].inject(''){|str, res| "#{str}_#{time.to_s(res)}"}[1..-1]
56
+ end
57
+
58
+ def link
59
+ @link ||= link_for(default_tag)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,5 @@
1
+ module Timemaster
2
+ module Version
3
+
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ require 'teststrap'
2
+
3
+ context "chronos" do
4
+ setup do
5
+ @time = Time.local(2010, 4, 20, 16, 20, 0)
6
+ Timecop.freeze(@time)
7
+ end
8
+
9
+ asserts "creates all the appropriate time based keys" do
10
+
11
+ end
12
+ end
data/test/teststrap.rb ADDED
@@ -0,0 +1,30 @@
1
+ require 'rubygems'
2
+ require 'riot'
3
+ require 'chronos'
4
+ require 'timecop'
5
+
6
+ Chronos.riak.port = 8091
7
+
8
+ CONTROLLERS = %w(users posts settings photos)
9
+ ACTIONS = %w(index new create edit update destroy)
10
+
11
+ class Request
12
+ include Chronos::Document
13
+
14
+ resolution :minute
15
+
16
+ tag :controller_name
17
+ tag :action_name
18
+
19
+ def self.make
20
+ new(:controller_name => CONTROLLERS.pick,
21
+ :action_name => ACTIONS.pick,
22
+ :request_time => 5.of{ 60 + rand(40)}.sum)
23
+ end
24
+
25
+ def self.gen
26
+ make.save
27
+ end
28
+ end
29
+
30
+
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: timemaster
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - brianthecoder
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-06-30 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: yard
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :development
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: riak-client
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: activesupport
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ type: :runtime
62
+ version_requirements: *id003
63
+ - !ruby/object:Gem::Dependency
64
+ name: hashie
65
+ prerelease: false
66
+ requirement: &id004 !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ type: :runtime
76
+ version_requirements: *id004
77
+ description: Creates buckets for a given resolution and will create all the appropriate links
78
+ email: wbsmith83@gmail.com
79
+ executables: []
80
+
81
+ extensions: []
82
+
83
+ extra_rdoc_files:
84
+ - LICENSE
85
+ - README.rdoc
86
+ - TODO
87
+ files:
88
+ - .document
89
+ - .gitignore
90
+ - LICENSE
91
+ - README.rdoc
92
+ - Rakefile
93
+ - TODO
94
+ - VERSION
95
+ - examples/request.rb
96
+ - lib/timemaster.rb
97
+ - lib/timemaster/document.rb
98
+ - lib/timemaster/extensions.rb
99
+ - lib/timemaster/helpers.rb
100
+ - lib/timemaster/resolution.rb
101
+ - lib/timemaster/version.rb
102
+ - test/chronos_test.rb
103
+ - test/teststrap.rb
104
+ has_rdoc: true
105
+ homepage: http://github.com/BrianTheCoder/chronos
106
+ licenses: []
107
+
108
+ post_install_message:
109
+ rdoc_options:
110
+ - --charset=UTF-8
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ hash: 3
119
+ segments:
120
+ - 0
121
+ version: "0"
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ none: false
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ hash: 3
128
+ segments:
129
+ - 0
130
+ version: "0"
131
+ requirements: []
132
+
133
+ rubyforge_project:
134
+ rubygems_version: 1.3.7
135
+ signing_key:
136
+ specification_version: 3
137
+ summary: A way to easily store time based data scalably in riak
138
+ test_files:
139
+ - test/chronos_test.rb
140
+ - test/teststrap.rb
141
+ - examples/request.rb