punching_bag 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/app/models/punch.rb +104 -0
- data/lib/generators/punching_bag/punching_bag_generator.rb +20 -0
- data/lib/generators/punching_bag/templates/create_punches_table.rb +20 -0
- data/lib/punching_bag.rb +25 -0
- data/lib/punching_bag/acts_as_punchable.rb +31 -0
- data/lib/punching_bag/acts_as_taggable_on.rb +22 -0
- data/lib/punching_bag/engine.rb +11 -0
- data/lib/punching_bag/version.rb +5 -0
- data/lib/tasks/punching_bag.rake +29 -0
- metadata +88 -0
data/app/models/punch.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
class Punch < ActiveRecord::Base
|
2
|
+
|
3
|
+
belongs_to :punchable, :polymorphic => true
|
4
|
+
|
5
|
+
attr_accessible # none
|
6
|
+
|
7
|
+
before_validation :set_defaults
|
8
|
+
validates :punchable_id, :punchable_type, :starts_at, :ends_at, :average_time, :hits, :presence => true
|
9
|
+
|
10
|
+
default_scope order('punches.average_time DESC')
|
11
|
+
scope :combos, where('punches.hits > 1')
|
12
|
+
scope :jabs, where(:hits => 1)
|
13
|
+
scope :before, lambda{ |time| where('punches.ends_at <= ?', time) }
|
14
|
+
scope :after, lambda{ |*args|
|
15
|
+
time = args.first
|
16
|
+
time.nil?? scoped : where('punches.average_time >= ?', time)
|
17
|
+
}
|
18
|
+
scope :by_timeframe, lambda{ |timeframe, time|
|
19
|
+
where('punches.starts_at >= ? AND punches.ends_at <= ?', time.send("beginning_of_#{timeframe}"), time.send("end_of_#{timeframe}"))
|
20
|
+
}
|
21
|
+
scope :by_day, lambda { |day| by_timeframe(:day, day) }
|
22
|
+
scope :by_month, lambda { |month| by_timeframe(:month, month) }
|
23
|
+
scope :by_year, lambda { |year|
|
24
|
+
year = DateTime.new(year) if year.is_a? Integer
|
25
|
+
by_timeframe(:year, year)
|
26
|
+
}
|
27
|
+
|
28
|
+
def jab?
|
29
|
+
hits == 1
|
30
|
+
end
|
31
|
+
|
32
|
+
def combo?
|
33
|
+
hits > 1
|
34
|
+
end
|
35
|
+
|
36
|
+
def timeframe
|
37
|
+
if starts_at.month != ends_at.month
|
38
|
+
:year
|
39
|
+
elsif starts_at.day != ends_at.day
|
40
|
+
:month
|
41
|
+
elsif starts_at != ends_at
|
42
|
+
:day
|
43
|
+
else
|
44
|
+
:second
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def day_combo?
|
49
|
+
timeframe == :day
|
50
|
+
end
|
51
|
+
|
52
|
+
def month_combo?
|
53
|
+
timeframe == :month
|
54
|
+
end
|
55
|
+
|
56
|
+
def year_combo?
|
57
|
+
timeframe == :year
|
58
|
+
end
|
59
|
+
|
60
|
+
def find_combo_for(timeframe)
|
61
|
+
punches = punchable.punches.by_timeframe(timeframe, average_time)
|
62
|
+
punches.combos.first || punches.first
|
63
|
+
end
|
64
|
+
|
65
|
+
def combine_with(combo)
|
66
|
+
if combo != self
|
67
|
+
combo.starts_at = starts_at if starts_at < combo.starts_at
|
68
|
+
combo.ends_at = ends_at if ends_at > combo.ends_at
|
69
|
+
combo.average_time = PunchingBag.average_time(combo, self)
|
70
|
+
combo.hits += hits
|
71
|
+
self.destroy if combo.save
|
72
|
+
end
|
73
|
+
combo
|
74
|
+
end
|
75
|
+
|
76
|
+
def combine_by_day
|
77
|
+
unless day_combo? || month_combo? || year_combo?
|
78
|
+
combine_with find_combo_for(:day)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def combine_by_month
|
83
|
+
unless month_combo? || year_combo?
|
84
|
+
combine_with find_combo_for(:month)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def combine_by_year
|
89
|
+
unless year_combo?
|
90
|
+
combine_with find_combo_for(:year)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def set_defaults
|
97
|
+
if date = (self.starts_at ||= DateTime.now)
|
98
|
+
self.ends_at ||= date
|
99
|
+
self.average_time ||= date
|
100
|
+
self.hits ||= 1
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
|
4
|
+
class PunchingBagGenerator < Rails::Generators::Base
|
5
|
+
include Rails::Generators::Migration
|
6
|
+
source_root File.join(File.dirname(__FILE__), 'templates')
|
7
|
+
|
8
|
+
def self.next_migration_number(dirname)
|
9
|
+
sleep 1
|
10
|
+
if ActiveRecord::Base.timestamped_migrations
|
11
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
12
|
+
else
|
13
|
+
"%.3d" % (current_migration_number(dirname) + 1)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def create_migration_file
|
18
|
+
migration_template 'create_punches_table.rb', 'db/migrate/create_punches_table.rb'
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class CreatePunchesTable < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :punches do |t|
|
4
|
+
t.integer :punchable_id, :null => false
|
5
|
+
t.string :punchable_type, :null => false, :limit => 20
|
6
|
+
t.datetime :starts_at, :null => false
|
7
|
+
t.datetime :ends_at, :null => false
|
8
|
+
t.datetime :average_time, :null => false
|
9
|
+
t.integer :hits, :null => false, :default=>1
|
10
|
+
end
|
11
|
+
add_index :punches, [:punchable_type, :punchable_id], :name => :punchable_index, :unique => false
|
12
|
+
add_index :punches, :average_time, :unique => false
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.down
|
16
|
+
remove_index :punches, :name => :punchable_index
|
17
|
+
remove_index :punches, :average_time
|
18
|
+
drop_table :punches
|
19
|
+
end
|
20
|
+
end
|
data/lib/punching_bag.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module PunchingBag
|
2
|
+
require 'punching_bag/engine' if defined?(Rails)
|
3
|
+
require 'punching_bag/acts_as_punchable'
|
4
|
+
|
5
|
+
def self.punch(punchable, request=nil)
|
6
|
+
if request.try(:bot?)
|
7
|
+
false
|
8
|
+
else
|
9
|
+
p = Punch.new
|
10
|
+
p.punchable = punchable
|
11
|
+
p.save ? p : false
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.average_time(*punches)
|
16
|
+
total_time = 0
|
17
|
+
hits = 0
|
18
|
+
punches.each do |punch|
|
19
|
+
total_time += punch.average_time.to_f * punch.hits
|
20
|
+
hits += punch.hits
|
21
|
+
end
|
22
|
+
Time.zone.at(total_time / hits)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module PunchingBag
|
2
|
+
module ActiveRecord
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
def most_hit(since=nil, limit=5)
|
6
|
+
query = self.scoped.joins(:punches).group(:punchable_type, :punchable_id)
|
7
|
+
query = query.where('punches.average_time >= ?', since) unless since.nil?
|
8
|
+
query.order('SUM(punches.hits) DESC').limit(limit)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module InstanceMethods
|
13
|
+
def hits(since=nil)
|
14
|
+
self.punches.after(since).sum(:hits)
|
15
|
+
end
|
16
|
+
|
17
|
+
def punch(request=nil)
|
18
|
+
PunchingBag.punch(self, request)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class ActiveRecord::Base
|
26
|
+
def self.acts_as_punchable
|
27
|
+
extend PunchingBag::ActiveRecord::ClassMethods
|
28
|
+
include PunchingBag::ActiveRecord::InstanceMethods
|
29
|
+
has_many :punches, :as => :punchable
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Adds methods to enable tracking tags through a common polymorphic association
|
2
|
+
module ActsAsTaggableOn
|
3
|
+
class Tag
|
4
|
+
def self.most_hit(since=nil, limit=5)
|
5
|
+
query = Tagging.scoped.
|
6
|
+
joins('INNER JOIN punches ON (taggings.taggable_id = punches.punchable_id AND taggings.taggable_type = punches.punchable_type)').
|
7
|
+
group(:tag_id).
|
8
|
+
order('SUM(punches.hits) DESC').
|
9
|
+
limit(limit)
|
10
|
+
query = query.where('punches.average_time >= ?', since) if since
|
11
|
+
query.map(&:tag)
|
12
|
+
end
|
13
|
+
|
14
|
+
def hits(since=nil)
|
15
|
+
query = Tagging.scoped.
|
16
|
+
joins('INNER JOIN punches ON (taggings.taggable_id = punches.punchable_id AND taggings.taggable_type = punches.punchable_type)').
|
17
|
+
where(:tag_id => self.id)
|
18
|
+
query = query.where('punches.average_time >= ?', since) if since
|
19
|
+
query.sum('punches.hits')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'punching_bag'
|
2
|
+
require 'rails'
|
3
|
+
require 'active_record'
|
4
|
+
|
5
|
+
module PunchingBag
|
6
|
+
class Engine < Rails::Engine
|
7
|
+
initializer 'punching_bag.extend_acts_as_taggable_on' do
|
8
|
+
require 'punching_bag/acts_as_taggable_on' if defined? ActsAsTaggableOn
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
namespace :punching_bag do
|
2
|
+
|
3
|
+
desc 'Combine old hit records together to improve performance'
|
4
|
+
task :combine, :by_day_after, :by_month_after, :by_year_after, :needs=>:environment do |t, args|
|
5
|
+
args.with_defaults :by_day_after => 7, :by_month_after => 1, :by_year_after => 1
|
6
|
+
|
7
|
+
punchables = Punch.all.map(&:punchable).uniq
|
8
|
+
punchables.each do |punchable|
|
9
|
+
|
10
|
+
# by_year
|
11
|
+
punchable.punches.before(args[:by_year_after].years.ago).each do |punch|
|
12
|
+
punch.combine_by_year
|
13
|
+
end
|
14
|
+
|
15
|
+
# by_month
|
16
|
+
punchable.punches.before(args[:by_month_after].months.ago).each do |punch|
|
17
|
+
punch.combine_by_month
|
18
|
+
end
|
19
|
+
|
20
|
+
# by_day
|
21
|
+
punchable.punches.before(args[:by_day_after].days.ago).each do |punch|
|
22
|
+
punch.combine_by_day
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: punching_bag
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Adam Crownoble
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-05-23 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: voight_kampff
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 25
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
- 1
|
32
|
+
- 1
|
33
|
+
version: 0.1.1
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
description: PunchingBag is a hit counting and simple trending engine for Ruby on Rails
|
37
|
+
email: adam@obledesign.com
|
38
|
+
executables: []
|
39
|
+
|
40
|
+
extensions: []
|
41
|
+
|
42
|
+
extra_rdoc_files: []
|
43
|
+
|
44
|
+
files:
|
45
|
+
- app/models/punch.rb
|
46
|
+
- lib/punching_bag/acts_as_punchable.rb
|
47
|
+
- lib/punching_bag/version.rb
|
48
|
+
- lib/punching_bag/engine.rb
|
49
|
+
- lib/punching_bag/acts_as_taggable_on.rb
|
50
|
+
- lib/generators/punching_bag/punching_bag_generator.rb
|
51
|
+
- lib/generators/punching_bag/templates/create_punches_table.rb
|
52
|
+
- lib/punching_bag.rb
|
53
|
+
- lib/tasks/punching_bag.rake
|
54
|
+
homepage: https://github.com/adamcrown/punching_bag
|
55
|
+
licenses: []
|
56
|
+
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
hash: 3
|
68
|
+
segments:
|
69
|
+
- 0
|
70
|
+
version: "0"
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 3
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
version: "0"
|
80
|
+
requirements: []
|
81
|
+
|
82
|
+
rubyforge_project:
|
83
|
+
rubygems_version: 1.8.2
|
84
|
+
signing_key:
|
85
|
+
specification_version: 3
|
86
|
+
summary: PunchingBag hit conter and trending plugin
|
87
|
+
test_files: []
|
88
|
+
|