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 +18 -0
- data/README.markdown +43 -0
- data/Rakefile +23 -0
- data/bin/periodic_counter +9 -0
- data/lib/periodic_counter.rb +84 -0
- data/require.rb +49 -0
- data/spec/Rakefile +10 -0
- data/spec/config/counters.yml +2 -0
- data/spec/config/database.yml.example +6 -0
- data/spec/db/migrate/001_counters.rb +14 -0
- data/spec/log/test.log +9183 -0
- data/spec/periodic_counter_spec.rb +69 -0
- data/spec/spec_helper.rb +27 -0
- metadata +86 -0
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,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,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
|