periodic_counter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2009 Winton Welsh
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ the Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,43 @@
1
+ PeriodicCounter
2
+ ===============
3
+
4
+ Maintains period fields on any counter column in your database
5
+
6
+ Requirements
7
+ ------------
8
+
9
+ <pre>
10
+ sudo gem install periodic_template
11
+ </pre>
12
+
13
+ Create columns
14
+ --------------
15
+
16
+ You should already have a counter column defined that is being incremented by something (let's call the column X).
17
+
18
+ Define period columns using this format: <code>X\_last\_week</code> or <code>X\_last\_6\_hours</code>.
19
+
20
+ The period name should follow the format of [ActiveSupport's time extensions](http://api.rubyonrails.org/classes/ActiveSupport/CoreExtensions/Numeric/Time.html).
21
+
22
+ Also add an <code>X_data</code> varchar column with a length of 2048.
23
+
24
+ Create configuration
25
+ --------------------
26
+
27
+ Create a file, <code>config/counters.yml</code>:
28
+
29
+ <pre>
30
+ my_table_name:
31
+ - my_counter
32
+ </pre>
33
+
34
+ The plugin assumes that the <code>config</code> directory also houses your <code>database.yml</code> file.
35
+
36
+ Create cron entry
37
+ -----------------
38
+
39
+ Add the following command to your crontab at a period of your choosing:
40
+
41
+ <pre>
42
+ cd /path/to/your/app && RAILS_ENV=production periodic_counter
43
+ </pre>
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ require "#{File.dirname(__FILE__)}/require"
2
+ Require.rakefile!
3
+
4
+ # You can delete this after you use it
5
+ desc "Rename project"
6
+ task :rename do
7
+ name = ENV['NAME'] || File.basename(Dir.pwd)
8
+ begin
9
+ dir = Dir['**/gem_template*']
10
+ from = dir.pop
11
+ if from
12
+ rb = from.include?('.rb')
13
+ to = File.dirname(from) + "/#{name}#{'.rb' if rb}"
14
+ FileUtils.mv(from, to)
15
+ end
16
+ end while dir.length > 0
17
+ Dir["**/*"].each do |path|
18
+ next if path.include?('Rakefile')
19
+ if File.file?(path)
20
+ `sed -i "" 's/gem_template/#{name}/g' #{path}`
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path("#{File.dirname(__FILE__)}/../require")
4
+ Require.bin!
5
+
6
+ PeriodicCounter.new(
7
+ ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development',
8
+ Dir.pwd
9
+ )
@@ -0,0 +1,84 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../require")
2
+ Require.lib!
3
+
4
+ class PeriodicCounter
5
+
6
+ def initialize(environment, root)
7
+ @db, @log, @mail = ActiveWrapper.setup(
8
+ :base => root,
9
+ :env => environment
10
+ )
11
+ @db.establish_connection
12
+
13
+ @tables = ActiveRecord::Base.connection.tables.inject({}) do |hash, table|
14
+ hash[table] = ActiveRecord::Base.connection.columns(table).collect(&:name)
15
+ hash
16
+ end
17
+
18
+ if File.exists?(counters_yml = "#{root}/config/counters.yml")
19
+ @counters = YAML::load(File.open(counters_yml))
20
+ else
21
+ raise "#{counters_yml} not found"
22
+ end
23
+
24
+ @counters.each do |table, counters|
25
+ columns = @tables[table]
26
+ if columns
27
+ columns.each do |column|
28
+ if counters.include?(column)
29
+ # Find period columns
30
+ period = columns.select do |col|
31
+ col =~ /^#{column}/ &&
32
+ col != column &&
33
+ !col.include?('_data')
34
+ end
35
+ # Grab all records
36
+ select_columns = [ 'id', column, "#{column}_data" ]
37
+ select_columns += period
38
+ records = ActiveRecord::Base.connection.select_all <<-SQL
39
+ SELECT #{select_columns.join(', ')}
40
+ FROM #{table}
41
+ SQL
42
+ records.each do |record|
43
+ id = record.delete('id')
44
+ data = YAML::load(record["#{column}_data"] || '') || {}
45
+ count = record.delete(column).to_i
46
+ # Set period counters
47
+ period.each do |col|
48
+ computed_at = data["#{col}_at"] || Time.now.utc
49
+ duration = column_to_period_integer(col)
50
+ time_since_compute = Time.now.utc - computed_at
51
+ starting_value = data[col].to_i
52
+ if (time_since_compute - duration) >= 0
53
+ record[col] = count - starting_value
54
+ data[col] = count
55
+ data["#{col}_at"] = Time.now.utc
56
+ else
57
+ data[col] ||= count
58
+ data["#{col}_at"] ||= Time.now.utc
59
+ end
60
+ end
61
+ # Update record
62
+ record["#{column}_data"] = "'#{YAML::dump(data)}'"
63
+ set = record.collect { |col, value| "#{col} = #{value || 0}" }
64
+ ActiveRecord::Base.connection.update <<-SQL
65
+ UPDATE #{table}
66
+ SET #{set.join(', ')}
67
+ WHERE id = #{id}
68
+ SQL
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ def column_to_period_integer(column)
77
+ column = column.split('_')[-2..-1]
78
+ column[0] = column[0].to_i
79
+ if column[0] == 0
80
+ column[0] = 1
81
+ end
82
+ eval(column.join('.'))
83
+ end
84
+ end
data/require.rb ADDED
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ gem 'require'
3
+ require 'require'
4
+
5
+ Require do
6
+ gem(:active_wrapper, '=0.2.7') { require 'active_wrapper' }
7
+ gem :require, '=0.2.7'
8
+ gem(:rake, '=0.8.7') { require 'rake' }
9
+ gem :rspec, '=1.3.0'
10
+
11
+ gemspec do
12
+ author 'Winton Welsh'
13
+ dependencies do
14
+ gem :active_wrapper
15
+ gem :require
16
+ end
17
+ email 'mail@wintoni.us'
18
+ name 'periodic_counter'
19
+ homepage "http://github.com/winton/#{name}"
20
+ summary "Maintains period fields on any counter column in your database"
21
+ version '0.1.0'
22
+ end
23
+
24
+ bin { require 'lib/periodic_counter' }
25
+
26
+ lib do
27
+ gem :active_wrapper
28
+ require 'yaml'
29
+ end
30
+
31
+ rakefile do
32
+ gem(:active_wrapper)
33
+ gem(:rake) { require 'rake/gempackagetask' }
34
+ gem(:rspec) { require 'spec/rake/spectask' }
35
+ require 'require/tasks'
36
+ end
37
+
38
+ spec_helper do
39
+ gem(:active_wrapper)
40
+ require 'require/spec_helper'
41
+ require 'lib/periodic_counter'
42
+ require 'pp'
43
+ end
44
+
45
+ spec_rakefile do
46
+ gem(:rake)
47
+ gem(:active_wrapper) { require 'active_wrapper/tasks' }
48
+ end
49
+ end
data/spec/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../require")
2
+ Require.spec_rakefile!
3
+
4
+ begin
5
+ ActiveWrapper::Tasks.new(
6
+ :base => File.dirname(__FILE__),
7
+ :env => ENV['ENV']
8
+ )
9
+ rescue Exception
10
+ end
@@ -0,0 +1,2 @@
1
+ counters:
2
+ - counter
@@ -0,0 +1,6 @@
1
+ test:
2
+ adapter: mysql
3
+ database: gem_template
4
+ username: root
5
+ password:
6
+ host: localhost
@@ -0,0 +1,14 @@
1
+ class Counters < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :counters do |t|
4
+ t.integer :counter
5
+ t.integer :counter_last_day
6
+ t.integer :counter_last_2_days
7
+ t.string :counter_data, :limit => 2048
8
+ end
9
+ end
10
+
11
+ def self.down
12
+ drop_table :counters
13
+ end
14
+ end