timemaster 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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