has_metrics 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/has_metrics.gemspec CHANGED
@@ -4,7 +4,7 @@ require File.expand_path('../lib/has_metrics/version', __FILE__)
4
4
  Gem::Specification.new do |gem|
5
5
  gem.authors = ["Allan Grant"]
6
6
  gem.email = ["allan@allangrant.net"]
7
- gem.description = %q{Memoization into activerecord.}
7
+ gem.description = %q{Calculate metrics and store them in the DB.}
8
8
  gem.summary = %q{Calculate "metrics" (any expensive methods) on ActiveRecord entries and memoize them to an automagical table.}
9
9
  gem.homepage = "http://github.com/allangrant/has_metrics"
10
10
 
@@ -15,9 +15,9 @@ Gem::Specification.new do |gem|
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = HasMetrics::VERSION
17
17
 
18
- gem.add_dependency("activerecord")
19
- gem.add_development_dependency("rake")
20
- gem.add_development_dependency("shoulda")
21
- # gem.add_development_dependency("mocha")
22
- gem.add_development_dependency("sqlite3")
18
+ gem.add_dependency "activerecord"
19
+ gem.add_development_dependency "rake"
20
+ gem.add_development_dependency "shoulda"
21
+ gem.add_development_dependency "sqlite3"
22
+ gem.add_development_dependency "pry"
23
23
  end
@@ -53,9 +53,14 @@ module Metrics
53
53
  result = instance_exec(&block)
54
54
  result = nil if result.is_a?(Float) && !result.finite?
55
55
  begin
56
- metrics.update_attributes(name => result, datestamp_column => Time.zone.now)
57
- rescue NoMethodError
58
- # This happens if the migrations haven't run yet. We should still calculate & return the metric.
56
+ metrics.send "#{name}=", result
57
+ metrics.send "#{datestamp_column}=", Time.now
58
+ rescue NoMethodError => e
59
+ raise e unless e.name == "#{name}=".to_sym
60
+ # This happens if the migrations haven't run yet for this metric. We should still calculate & return the metric.
61
+ end
62
+ unless changed?
63
+ metrics.save
59
64
  end
60
65
  result
61
66
  end
@@ -88,7 +93,8 @@ module Metrics
88
93
  :datetime
89
94
  when @float_metrics && @float_metrics.include?(column)
90
95
  :float
91
- else :integer
96
+ else
97
+ :integer
92
98
  end
93
99
  end
94
100
 
@@ -145,7 +151,7 @@ module Metrics
145
151
  raise "Cannot determine if there were extra columns for has_metric when using the table itself for storing the metric! Remove any columns manually"
146
152
  [] # We wont know what columns are excessive if the source changed
147
153
  else
148
- (columns.map(&:name) - %w(id created_at updated_at)).map - required_columns
154
+ (columns.map(&:name) - %w(id created_at updated_at)) - required_columns
149
155
  end
150
156
 
151
157
  end
@@ -0,0 +1,36 @@
1
+ # include Segmentation
2
+ module Segmentation
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ # CLASS METHODS ADDED
8
+ module ClassMethods
9
+ def segment_by category, &definition
10
+ (@segment_categories ||= []) << category.to_sym
11
+ define_method("segment_by_#{category}", definition)
12
+ if respond_to?(:has_metric)
13
+ has_metric "by_#{category}" do
14
+ send("segment_by_#{category}")
15
+ end
16
+ end
17
+ end
18
+ def segment_categories
19
+ @segment_categories
20
+ end
21
+ def update_segments!
22
+ puts "Updating all segments on #{name}: #{segment_categories.join(', ')}"
23
+ all.each do |object|
24
+ segment_categories.each do |category|
25
+ object.update_segment!(category)
26
+ end
27
+ end
28
+ end
29
+ end # END OF CLASS METHODS
30
+
31
+ # INSTANCE METHODS ADDED
32
+ def update_segment!(category)
33
+ update_attribute("by_#{category}", send("segment_by_#{category}"))
34
+ end
35
+ # END OF INSTANCE METHODS
36
+ end
@@ -1,3 +1,3 @@
1
1
  module HasMetrics
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/lib/has_metrics.rb CHANGED
@@ -1,6 +1,10 @@
1
1
  require "has_metrics/version"
2
2
  require "has_metrics/metrics"
3
+ require "has_metrics/segmentation"
3
4
 
4
5
  module HasMetrics
5
-
6
+ def self.included(base)
7
+ base.send :include, Metrics
8
+ base.send :include, Segmentation
9
+ end
6
10
  end
@@ -1,23 +1,5 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + "/../test_helper")
2
2
 
3
- ActiveRecord::Base.establish_connection(:adapter => "sqlite3",
4
- :database => File.expand_path(File.dirname(__FILE__) + "/../test.db"))
5
-
6
- class CreateTestTables < ActiveRecord::Migration
7
- def self.up
8
- create_table "users", :force => true do |t|
9
- t.string "name"
10
- end
11
- create_table "user_metrics", :force => true
12
- end
13
-
14
- def self.down
15
- drop_table "users"
16
- drop_table "user_metrics"
17
- end
18
- end
19
-
20
-
21
3
  class User < ActiveRecord::Base
22
4
  include Metrics
23
5
  has_metric :name_length do
@@ -25,16 +7,12 @@ class User < ActiveRecord::Base
25
7
  end
26
8
  end
27
9
 
28
-
29
10
  class MetricsTest < Test::Unit::TestCase
30
11
  context "when defining metrics" do
31
12
  setup do
32
- root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
33
-
34
- CreateTestTables.up
35
- User.update_all_metrics!
36
- @user_name =
13
+ CreateTestTables.up(:user)
37
14
  @user = User.create(:name => "Fuzz")
15
+ User.update_all_metrics!
38
16
  end
39
17
 
40
18
  should "create rows for the metrics" do
@@ -47,12 +25,27 @@ class MetricsTest < Test::Unit::TestCase
47
25
  assert_equal 16, @user.name_length_squared
48
26
  end
49
27
 
50
- should "they should calculate their block when called" do
28
+ should "calculate their block when called" do
51
29
  assert_equal "Fuzz", @user.name
52
30
  assert_equal 4, @user.name_length
31
+
53
32
  @user.name = "Bib"
54
- assert_equal 3, @user.name_length
33
+
34
+ # since 20 hours hasn't passed, the value is pulled from cache, not recalculated
35
+ assert_equal 4, @user.name_length
36
+ # (true) forces it to recalculate right away
37
+ assert_equal 3, @user.name_length(true)
38
+
39
+ # since it wasn't saved, it's the same in the DB
55
40
  assert_equal 4, User.find_by_name("Fuzz").name_length
41
+
42
+ @user.save
43
+ assert_equal 3, @user.name_length(true)
44
+ assert_equal 3, User.find_by_name("Bib").name_length
45
+ end
46
+
47
+ should "have their values precomputed" do
48
+ assert_equal({4=>1}, UserMetrics.count(:group => :name_length))
56
49
  end
57
50
  end
58
51
  end
@@ -0,0 +1,34 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../test_helper")
2
+
3
+ class Post < ActiveRecord::Base
4
+ include Metrics
5
+ include Segmentation
6
+
7
+ segment_by :name_length do
8
+ case name.length
9
+ when 0..6
10
+ "short"
11
+ when 7
12
+ "seven"
13
+ else
14
+ "long"
15
+ end
16
+ end
17
+ end
18
+
19
+ class SegmentationTest < Test::Unit::TestCase
20
+ context "when defining segments" do
21
+ setup do
22
+ CreateTestTables.up(:post)
23
+
24
+ Post.create(:name => "Shorty")
25
+ Post.create(:name => "Seven!!")
26
+ Post.create(:name => "Really long")
27
+ Post.update_all_metrics!
28
+ end
29
+
30
+ should "segment properly" do
31
+ assert_equal({'short'=>1, 'seven'=>1, 'long'=>1}, PostMetrics.count(:group => :by_name_length))
32
+ end
33
+ end
34
+ end
data/test/test_helper.rb CHANGED
@@ -5,6 +5,24 @@ $:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
5
5
 
6
6
  require 'shoulda'
7
7
  # require 'mocha'
8
- require 'activerecord'
8
+ require 'active_record'
9
9
  require 'sqlite3'
10
- require 'has_metrics/metrics'
10
+ require 'has_metrics'
11
+ require 'pry'
12
+
13
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => File.expand_path(File.dirname(__FILE__) + "/../test.db"))
14
+
15
+
16
+ class CreateTestTables < ActiveRecord::Migration
17
+ def self.up(model = :user)
18
+ create_table "#{model}s", :force => true do |t|
19
+ t.string "name"
20
+ end
21
+ create_table "#{model}_metrics", :force => true
22
+ end
23
+
24
+ def self.down(model = :user)
25
+ drop_table "#{model}s"
26
+ drop_table "#{model}_metrics"
27
+ end
28
+ end
data/test.db ADDED
Binary file
metadata CHANGED
@@ -1,89 +1,103 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: has_metrics
3
- version: !ruby/object:Gem::Version
4
- hash: 29
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
5
  prerelease:
6
- segments:
7
- - 0
8
- - 0
9
- - 1
10
- version: 0.0.1
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Allan Grant
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2012-04-07 00:00:00 -07:00
19
- default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
22
- version_requirements: &id001 !ruby/object:Gem::Requirement
23
- none: false
24
- requirements:
25
- - - ">="
26
- - !ruby/object:Gem::Version
27
- hash: 3
28
- segments:
29
- - 0
30
- version: "0"
31
- requirement: *id001
32
- prerelease: false
12
+ date: 2012-11-28 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
33
15
  name: activerecord
34
- type: :runtime
35
- - !ruby/object:Gem::Dependency
36
- version_requirements: &id002 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
37
17
  none: false
38
- requirements:
39
- - - ">="
40
- - !ruby/object:Gem::Version
41
- hash: 3
42
- segments:
43
- - 0
44
- version: "0"
45
- requirement: *id002
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
46
23
  prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
47
31
  name: rake
48
- type: :development
49
- - !ruby/object:Gem::Dependency
50
- version_requirements: &id003 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
51
33
  none: false
52
- requirements:
53
- - - ">="
54
- - !ruby/object:Gem::Version
55
- hash: 3
56
- segments:
57
- - 0
58
- version: "0"
59
- requirement: *id003
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
60
39
  prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
61
47
  name: shoulda
62
- type: :development
63
- - !ruby/object:Gem::Dependency
64
- version_requirements: &id004 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
65
49
  none: false
66
- requirements:
67
- - - ">="
68
- - !ruby/object:Gem::Version
69
- hash: 3
70
- segments:
71
- - 0
72
- version: "0"
73
- requirement: *id004
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
74
55
  prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
75
63
  name: sqlite3
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
76
70
  type: :development
77
- description: Memoization into activerecord.
78
- email:
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: pry
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: Calculate metrics and store them in the DB.
95
+ email:
79
96
  - allan@allangrant.net
80
97
  executables: []
81
-
82
98
  extensions: []
83
-
84
99
  extra_rdoc_files: []
85
-
86
- files:
100
+ files:
87
101
  - .gitignore
88
102
  - Gemfile
89
103
  - LICENSE
@@ -92,43 +106,44 @@ files:
92
106
  - has_metrics.gemspec
93
107
  - lib/has_metrics.rb
94
108
  - lib/has_metrics/metrics.rb
109
+ - lib/has_metrics/segmentation.rb
95
110
  - lib/has_metrics/version.rb
111
+ - test.db
96
112
  - test/functional/metrics_test.rb
113
+ - test/functional/segmentation_test.rb
97
114
  - test/test_helper.rb
98
- has_rdoc: true
99
115
  homepage: http://github.com/allangrant/has_metrics
100
116
  licenses: []
101
-
102
117
  post_install_message:
103
118
  rdoc_options: []
104
-
105
- require_paths:
119
+ require_paths:
106
120
  - lib
107
- required_ruby_version: !ruby/object:Gem::Requirement
121
+ required_ruby_version: !ruby/object:Gem::Requirement
108
122
  none: false
109
- requirements:
110
- - - ">="
111
- - !ruby/object:Gem::Version
112
- hash: 3
113
- segments:
123
+ requirements:
124
+ - - ! '>='
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ segments:
114
128
  - 0
115
- version: "0"
116
- required_rubygems_version: !ruby/object:Gem::Requirement
129
+ hash: -764161358984080823
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
131
  none: false
118
- requirements:
119
- - - ">="
120
- - !ruby/object:Gem::Version
121
- hash: 3
122
- segments:
132
+ requirements:
133
+ - - ! '>='
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ segments:
123
137
  - 0
124
- version: "0"
138
+ hash: -764161358984080823
125
139
  requirements: []
126
-
127
140
  rubyforge_project:
128
- rubygems_version: 1.5.3
141
+ rubygems_version: 1.8.24
129
142
  signing_key:
130
143
  specification_version: 3
131
- summary: Calculate "metrics" (any expensive methods) on ActiveRecord entries and memoize them to an automagical table.
132
- test_files:
144
+ summary: Calculate "metrics" (any expensive methods) on ActiveRecord entries and memoize
145
+ them to an automagical table.
146
+ test_files:
133
147
  - test/functional/metrics_test.rb
148
+ - test/functional/segmentation_test.rb
134
149
  - test/test_helper.rb