has_metrics 0.0.6 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/has_metrics.gemspec +32 -20
- data/lib/has_metrics/metrics.rb +115 -43
- data/lib/has_metrics/sql_capturer.rb +29 -0
- data/lib/has_metrics/version.rb +1 -1
- data/lib/has_metrics.rb +12 -1
- data/spec/metrics_spec.rb +97 -16
- data/spec/support/active_record.rb +21 -1
- metadata +15 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f48a7598ad01f84ad88033cbd27f3db579471077
|
4
|
+
data.tar.gz: 8f2bc339c0a146c2c787e9d605c9097963c3dc69
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 05f470ca3023d9c5ed3ebd1a2dd6f8dc3b3c6cbc2a20b96f9af249646b4afc7c72453e653b2a363165b8942d883afa899255615228b48d2b99e0270d809dd871
|
7
|
+
data.tar.gz: 4363c5d42770658fd5a197e36ebad47a495fe75915654dcd39becbe5c9e81a13bb3515e437a5de66d441d19fb70017c9db1100af62b88d8bc8d211715643ad06
|
data/has_metrics.gemspec
CHANGED
@@ -1,25 +1,37 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
require File.expand_path('../lib/has_metrics/version', __FILE__)
|
3
2
|
|
4
|
-
Gem::Specification.new do |
|
5
|
-
|
6
|
-
|
7
|
-
gem.authors = ["Allan Grant"]
|
8
|
-
gem.email = ["allan@allangrant.net"]
|
9
|
-
gem.description = %q{Calculate metrics and store them in the DB.}
|
10
|
-
gem.summary = %q{Calculate "metrics" (any expensive methods) on ActiveRecord entries and memoize them to an automagical table.}
|
11
|
-
gem.homepage = "http://github.com/allangrant/has_metrics"
|
12
|
-
gem.license = "MIT"
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "has_metrics"
|
5
|
+
s.version = "0.1.1"
|
13
6
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Allan Grant"]
|
9
|
+
s.date = "2013-08-12"
|
10
|
+
s.description = "Calculate metrics and store them in the DB."
|
11
|
+
s.email = ["allan@allangrant.net"]
|
12
|
+
s.files = [".gitignore", "Gemfile", "LICENSE", "README.md", "Rakefile", "has_metrics.gemspec", "lib/has_metrics.rb", "lib/has_metrics/metrics.rb", "lib/has_metrics/segmentation.rb", "lib/has_metrics/sql_capturer.rb", "lib/has_metrics/version.rb", "spec/metrics_spec.rb", "spec/segmentation_spec.rb", "spec/spec_helper.rb", "spec/support/active_record.rb"]
|
13
|
+
s.homepage = "http://github.com/allangrant/has_metrics"
|
14
|
+
s.licenses = ["MIT"]
|
15
|
+
s.require_paths = ["lib"]
|
16
|
+
s.rubygems_version = "2.0.3"
|
17
|
+
s.summary = "Calculate \"metrics\" (any expensive methods) on ActiveRecord entries and memoize them to an automagical table."
|
18
|
+
s.test_files = ["spec/metrics_spec.rb", "spec/segmentation_spec.rb", "spec/spec_helper.rb", "spec/support/active_record.rb"]
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
if s.respond_to? :specification_version then
|
21
|
+
s.specification_version = 4
|
22
|
+
|
23
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
24
|
+
s.add_runtime_dependency(%q<activerecord>, ["< 4.0"])
|
25
|
+
s.add_development_dependency(%q<rake>, [">= 0"])
|
26
|
+
s.add_development_dependency(%q<bundler>, [">= 0"])
|
27
|
+
else
|
28
|
+
s.add_dependency(%q<activerecord>, ["< 4.0"])
|
29
|
+
s.add_dependency(%q<rake>, [">= 0"])
|
30
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
31
|
+
end
|
32
|
+
else
|
33
|
+
s.add_dependency(%q<activerecord>, ["< 4.0"])
|
34
|
+
s.add_dependency(%q<rake>, [">= 0"])
|
35
|
+
s.add_dependency(%q<bundler>, [">= 0"])
|
36
|
+
end
|
25
37
|
end
|
data/lib/has_metrics/metrics.rb
CHANGED
@@ -25,7 +25,7 @@ module Metrics
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def metrics
|
28
|
-
@metrics ||= self.class.metrics_class.
|
28
|
+
@metrics ||= self.class.metrics_class.find_or_initialize_by_id(id)
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
@@ -35,22 +35,31 @@ module Metrics
|
|
35
35
|
def metrics_class
|
36
36
|
@metrics_class
|
37
37
|
end
|
38
|
+
|
38
39
|
def has_metric name, options={}, &block
|
40
|
+
options.merge!(single: block) if block
|
41
|
+
define_single_method(name, options) if options[:single]
|
42
|
+
|
43
|
+
metrics[name.to_sym] ||= {}
|
44
|
+
metrics[name.to_sym].merge!(options)
|
45
|
+
metrics_class.class_eval do
|
46
|
+
attr_accessible(name, "updated__#{name}__at")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def has_aggregate_metric(name, sql)
|
51
|
+
has_metric name, {}.merge(aggregate: sql)
|
52
|
+
end
|
53
|
+
|
54
|
+
def define_single_method(name, options)
|
39
55
|
define_method name do |*args|
|
40
|
-
frequency = options[:every] || 20.hours
|
41
56
|
previous_result = metrics.attributes[name.to_s] unless options[:every] == :always
|
42
57
|
datestamp_column = "updated__#{name}__at"
|
43
|
-
datestamp = metrics.attributes[datestamp_column]
|
44
58
|
force = [:force, true].include?(args[0])
|
45
|
-
|
46
|
-
when !force && previous_result && options[:once]
|
47
|
-
# Only calculate this metric once. If it's not nil, reuse the old value.
|
48
|
-
previous_result
|
49
|
-
when !force && frequency.is_a?(Fixnum) && datestamp && datestamp > frequency.ago
|
50
|
-
# The metric was recently calculated and can be reused.
|
59
|
+
if !force && (options.has_key?(:aggregate) || options[:infer_aggregate])
|
51
60
|
previous_result
|
52
61
|
else
|
53
|
-
result = instance_exec(&
|
62
|
+
result = instance_exec(&options[:single])
|
54
63
|
result = nil if result.is_a?(Float) && !result.finite?
|
55
64
|
begin
|
56
65
|
metrics.send "#{name}=", result
|
@@ -66,34 +75,32 @@ module Metrics
|
|
66
75
|
end
|
67
76
|
end
|
68
77
|
|
69
|
-
(
|
70
|
-
|
71
|
-
|
72
|
-
if respond_to?(:has_custom_order_by) # TODO: carve out has_custom_order_by functionality into this gem
|
73
|
-
unless metrics_class == self
|
74
|
-
has_custom_order_by name do |column, order|
|
75
|
-
{ :joins => :metrics, :order => "#{reflect_on_association(:metrics).table_name}.#{column} #{order}" }
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
if options[:type] && (options[:type].to_sym == :float)
|
81
|
-
(@float_metrics ||= []) << name.to_sym
|
78
|
+
if defined?(::NewRelic)
|
79
|
+
::NewRelic::Agent.logger.debug "Installing instrumentation for #{name}"
|
80
|
+
add_transaction_tracer name, category: :task
|
82
81
|
end
|
83
82
|
end
|
84
83
|
|
85
84
|
def metrics
|
86
|
-
@metrics
|
85
|
+
@metrics ||= {}
|
86
|
+
end
|
87
|
+
|
88
|
+
def single_only_metrics
|
89
|
+
metrics.select{ |metric, options| !options.has_key?(:aggregate) }
|
90
|
+
end
|
91
|
+
|
92
|
+
def aggregate_metrics
|
93
|
+
metrics.select{ |metric, options| options.has_key?(:aggregate) }
|
87
94
|
end
|
88
95
|
|
89
96
|
def metrics_column_type(column)
|
90
97
|
case
|
98
|
+
when (metric = metrics.select { |metric, options| metric == column.to_sym && options[:type] }).any?
|
99
|
+
metric.values.first[:type]
|
91
100
|
when (column.to_s =~ /^by_(.+)$/) && respond_to?(:segment_categories) && segment_categories.include?($1.to_sym) # TODO: carve out segementation functionality into this gem
|
92
101
|
:string
|
93
102
|
when (column.to_s =~ /_at$/)
|
94
103
|
:datetime
|
95
|
-
when @float_metrics && @float_metrics.include?(column.to_sym)
|
96
|
-
:float
|
97
104
|
else
|
98
105
|
:integer
|
99
106
|
end
|
@@ -101,35 +108,100 @@ module Metrics
|
|
101
108
|
|
102
109
|
def update_all_metrics!(*args)
|
103
110
|
metrics_class.migrate!
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
+
|
112
|
+
return unless warmup = first
|
113
|
+
|
114
|
+
where("not exists(select id from #{metrics_class.table_name} where #{metrics_class.table_name}.id = #{table_name}.id)").each do |resource|
|
115
|
+
resource.metrics.save!
|
116
|
+
end
|
117
|
+
|
118
|
+
detected_aggregate_metrics, singular_metrics = collect_metrics(warmup)
|
119
|
+
|
120
|
+
bad_guesses = crank_detected_aggregate_metrics!(detected_aggregate_metrics)
|
121
|
+
|
122
|
+
puts "#{bad_guesses.count} bad guesses found, scheduling as singular metrics." if bad_guesses.any?
|
123
|
+
singular_metrics = singular_metrics | bad_guesses
|
124
|
+
|
125
|
+
if singular_metrics.any?
|
126
|
+
crank_singular_metrics!(args, singular_metrics)
|
127
|
+
end
|
128
|
+
|
129
|
+
crank_aggregate_metrics!
|
130
|
+
|
131
|
+
metrics
|
132
|
+
end
|
133
|
+
|
134
|
+
def collect_metrics(warmup)
|
135
|
+
detected_aggregate_metrics = {}
|
136
|
+
metrics.select{|m,o| o[:infer_aggregate] }.each do |metric_name, options|
|
137
|
+
next if options[:aggregate]
|
138
|
+
existing_logger = ActiveRecord::Base.logger
|
139
|
+
sql_capturer = ActiveRecord::Base.logger = SqlCapturer.new(existing_logger)
|
140
|
+
warmup.instance_exec(&options[:single])
|
141
|
+
ActiveRecord::Base.logger = existing_logger
|
142
|
+
|
143
|
+
unless sql_capturer.query.count != 1
|
144
|
+
subquery = sql_capturer.query.first.gsub(warmup.id.to_s, "#{metrics_class.table_name}.id")
|
145
|
+
unless subquery == %Q{SELECT "#{metrics_class.table_name}".* FROM "#{metrics_class.table_name}" WHERE "#{metrics_class.table_name}"."id" = #{metrics_class.table_name}.id LIMIT 1}
|
146
|
+
update_sql = %Q{
|
147
|
+
UPDATE #{metrics_class.table_name}
|
148
|
+
SET #{metric_name} = (#{subquery}),
|
149
|
+
updated__#{metric_name}__at = '#{Time.current.to_s(:db)}';
|
150
|
+
}
|
151
|
+
detected_aggregate_metrics[metric_name] = update_sql
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
return detected_aggregate_metrics, (metrics.keys - aggregate_metrics.keys - detected_aggregate_metrics.keys)
|
156
|
+
end
|
157
|
+
|
158
|
+
def crank_detected_aggregate_metrics!(detected_aggregate_metrics)
|
159
|
+
bad_guesses = []
|
160
|
+
detected_aggregate_metrics.each do |metric_name, update_sql|
|
161
|
+
begin
|
162
|
+
ActiveRecord::Base.connection.execute update_sql
|
163
|
+
rescue
|
164
|
+
bad_guesses << metric_name
|
165
|
+
end
|
166
|
+
end
|
167
|
+
bad_guesses
|
168
|
+
end
|
169
|
+
|
170
|
+
def crank_singular_metrics!(args, singular_metrics)
|
171
|
+
unless ENV['RAILS_ENV'] == 'test'
|
172
|
+
puts "Slow Metrics Found :: #{singular_metrics.count} \n"
|
173
|
+
puts " -- -- -- -- \n"
|
174
|
+
puts "Go implement their aggregate methods in #{self} to speed this up.\n"
|
175
|
+
puts singular_metrics.join(', ')
|
176
|
+
puts "\n ≈ ≈ ≈ ≈ ≈ ≈ ≈ "
|
177
|
+
end
|
178
|
+
|
111
179
|
find_in_batches do |batch|
|
112
180
|
metrics_class.transaction do
|
113
181
|
batch.each do |record|
|
114
|
-
|
115
|
-
|
182
|
+
singular_metrics.each do |singular_metric|
|
183
|
+
record.send(singular_metric, *args)
|
184
|
+
end
|
116
185
|
end
|
117
186
|
end
|
118
|
-
# progress_bar.inc if progress_bar
|
119
187
|
end
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
188
|
+
end
|
189
|
+
|
190
|
+
def crank_aggregate_metrics!
|
191
|
+
aggregate_metrics.each do |metric_name, options|
|
192
|
+
ActiveRecord::Base.connection.execute options[:aggregate]
|
193
|
+
metrics_class.update_all "updated__#{metric_name}__at" => Time.current
|
194
|
+
end
|
124
195
|
end
|
125
196
|
end
|
126
197
|
### END CLASS METHODS, START INSTANCE METHODS
|
127
198
|
|
128
199
|
def update_metrics!(*args)
|
129
|
-
self.class.metrics.each do |metric|
|
200
|
+
self.class.metrics.each do |metric, options|
|
130
201
|
send(metric, *args)
|
131
202
|
end
|
132
203
|
end
|
204
|
+
|
133
205
|
### END INSTANCE METHODS
|
134
206
|
|
135
207
|
### Sets up a class like "SiteMetrics". These are all CLASS methods:
|
@@ -139,11 +211,11 @@ module Metrics
|
|
139
211
|
end
|
140
212
|
|
141
213
|
def metrics_updated_at_columns
|
142
|
-
@object_class.metrics.map{|metric| "updated__#{metric}__at"}
|
214
|
+
@object_class.metrics.keys.map{|metric| "updated__#{metric}__at"}
|
143
215
|
end
|
144
216
|
|
145
217
|
def required_columns
|
146
|
-
@object_class.metrics.map(&:to_s) + metrics_updated_at_columns
|
218
|
+
@object_class.metrics.keys.map(&:to_s) + metrics_updated_at_columns
|
147
219
|
end
|
148
220
|
|
149
221
|
def missing_columns
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class SqlCapturer
|
2
|
+
attr_accessor :query, :existing_logger
|
3
|
+
|
4
|
+
def initialize(existing_logger)
|
5
|
+
self.query = []
|
6
|
+
self.existing_logger = existing_logger
|
7
|
+
self
|
8
|
+
end
|
9
|
+
|
10
|
+
def error(string)
|
11
|
+
raise string
|
12
|
+
end
|
13
|
+
|
14
|
+
def debug?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def warn(message)
|
19
|
+
existing_logger.try(:warn, message)
|
20
|
+
end
|
21
|
+
|
22
|
+
def capture(log)
|
23
|
+
if select_statement = log.index("SELECT")
|
24
|
+
self.query << log[select_statement..-1].gsub(/\e\[(\d+)m/, '')
|
25
|
+
end
|
26
|
+
existing_logger.debug(log) if existing_logger
|
27
|
+
end
|
28
|
+
alias_method :debug, :capture
|
29
|
+
end
|
data/lib/has_metrics/version.rb
CHANGED
data/lib/has_metrics.rb
CHANGED
@@ -1,10 +1,21 @@
|
|
1
1
|
require "has_metrics/version"
|
2
2
|
require "has_metrics/metrics"
|
3
3
|
require "has_metrics/segmentation"
|
4
|
+
require "has_metrics/sql_capturer"
|
4
5
|
|
5
6
|
module HasMetrics
|
6
7
|
def self.included(base)
|
7
8
|
base.send :include, Metrics
|
8
9
|
base.send :include, Segmentation
|
10
|
+
|
11
|
+
if defined?(::NewRelic)
|
12
|
+
base.send :include, ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
|
13
|
+
base.class_eval do
|
14
|
+
class << self
|
15
|
+
include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
|
16
|
+
add_transaction_tracer :update_all_metrics!, category: :task
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
9
20
|
end
|
10
|
-
end
|
21
|
+
end
|
data/spec/metrics_spec.rb
CHANGED
@@ -1,29 +1,65 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
|
4
|
-
describe "defining metrics" do
|
5
|
-
let(:user) { User.create(:name => "Fuzz") }
|
3
|
+
create_tables_for(:user)
|
6
4
|
|
7
|
-
|
8
|
-
create_tables_for(:user)
|
5
|
+
class Pet < ActiveRecord::Base
|
9
6
|
|
10
|
-
|
11
|
-
include Metrics
|
12
|
-
has_metric :name_length do
|
13
|
-
name.length
|
14
|
-
end
|
15
|
-
end
|
7
|
+
end
|
16
8
|
|
17
|
-
|
9
|
+
class Identity < ActiveRecord::Base
|
10
|
+
has_one :user
|
11
|
+
has_many :activities, dependent: :destroy, foreign_key: :actor_id
|
12
|
+
end
|
13
|
+
|
14
|
+
class Activity < ActiveRecord::Base
|
15
|
+
belongs_to :actor, class_name: 'Identity'
|
16
|
+
attr_accessible :actor
|
17
|
+
end
|
18
|
+
|
19
|
+
class User < ActiveRecord::Base
|
20
|
+
attr_accessible :identity, :name
|
21
|
+
include Metrics
|
22
|
+
belongs_to :identity
|
23
|
+
|
24
|
+
has_many :pets, dependent: :destroy
|
25
|
+
has_many :activities, through: :identity
|
26
|
+
|
27
|
+
has_metric :name_length do
|
28
|
+
name.try(:length) || 0
|
29
|
+
end
|
30
|
+
|
31
|
+
has_metric :pets_count do
|
32
|
+
pets.count
|
33
|
+
end
|
34
|
+
|
35
|
+
has_metric :average_pet_weight, infer_aggregate: true do
|
36
|
+
pets.average(:weight)
|
37
|
+
end
|
38
|
+
|
39
|
+
has_metric :sent_activities do
|
40
|
+
activities.count
|
41
|
+
end
|
42
|
+
end
|
43
|
+
UserMetrics.migrate!
|
44
|
+
User.update_all_metrics!
|
45
|
+
|
46
|
+
|
47
|
+
describe Metrics do
|
48
|
+
describe "defining metrics" do
|
49
|
+
let(:user) { User.create(:name => "Fuzz") }
|
50
|
+
before { User.destroy_all }
|
51
|
+
after do
|
52
|
+
User.destroy_all
|
53
|
+
User.metrics.reject! {|m,o| m == :name_length_squared}
|
18
54
|
end
|
19
55
|
|
20
56
|
it "creates rows for the metrics" do
|
21
|
-
UserMetrics.columns.count.should ==
|
57
|
+
UserMetrics.columns.count.should == 9
|
22
58
|
User.has_metric :name_length_squared do
|
23
59
|
name_length * name_length
|
24
60
|
end
|
25
61
|
User.update_all_metrics!
|
26
|
-
UserMetrics.columns.count.should ==
|
62
|
+
UserMetrics.columns.count.should == 11
|
27
63
|
user.name_length_squared.should == 16
|
28
64
|
end
|
29
65
|
|
@@ -33,8 +69,6 @@ describe Metrics do
|
|
33
69
|
|
34
70
|
user.name = "Bib"
|
35
71
|
|
36
|
-
# since 20 hours hasn't passed, the value is pulled from cache, not recalculated
|
37
|
-
user.name_length.should == 4
|
38
72
|
# (true) forces it to recalculate right away
|
39
73
|
user.name_length(true).should == 3
|
40
74
|
|
@@ -51,5 +85,52 @@ describe Metrics do
|
|
51
85
|
User.update_all_metrics!
|
52
86
|
UserMetrics.count(:group => :name_length).should == {4=>1}
|
53
87
|
end
|
88
|
+
|
89
|
+
describe 'aggregate functions' do
|
90
|
+
before { User.create!(name: 'Goose').pets.create! weight: 265 }
|
91
|
+
|
92
|
+
it 'calls aggregate function alone' do
|
93
|
+
user.pets.create!(age: 1, weight: 2)
|
94
|
+
UserMetrics.any_instance.should_not_receive(:average_pet_weight=)
|
95
|
+
User.update_all_metrics!
|
96
|
+
expect(user.metrics.updated__average_pet_weight__at.to_i).to eql Time.current.to_i
|
97
|
+
expect(user.average_pet_weight).to eql 2
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'arrives at the same value as the single instance calculation' do
|
101
|
+
user.pets.create!(age: 1, weight: 2)
|
102
|
+
user.pets.create!(age: 3, weight: 8)
|
103
|
+
User.update_all_metrics!
|
104
|
+
agg_result = user.average_pet_weight
|
105
|
+
single_result = user.average_pet_weight(true)
|
106
|
+
expect(agg_result.to_d).to eql single_result.to_d
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe 'collect_metrics' do
|
111
|
+
after { User.metrics.reject! {|k,v| k == :average_pet_age } }
|
112
|
+
it 'gives preferences to defined aggregate functions over detected ones' do
|
113
|
+
user.pets.create!(age: 1, weight: 2, age: 3)
|
114
|
+
User.has_metric :average_pet_age, single: -> { pets.average(:age) }, aggregate: 'SOME SQL'
|
115
|
+
UserMetrics.any_instance.should_not_receive(:average_pet_age=)
|
116
|
+
detected_aggregate_metrics, singular_metrics = User.collect_metrics(user)
|
117
|
+
expect(detected_aggregate_metrics.count).to eql 1
|
118
|
+
expect(singular_metrics.count).to eql 3
|
119
|
+
expect(User.aggregate_metrics.count).to eql 1
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe 'foreign key enable' do
|
124
|
+
it 'asdfsdf' do
|
125
|
+
User.create! # ensure user and identity don't have the same id
|
126
|
+
user = User.create!(name: 'bill', identity: Identity.create(thoughts: 'hurp'))
|
127
|
+
5.times do
|
128
|
+
Activity.create!(actor: user.identity, type: 'wtf')
|
129
|
+
end
|
130
|
+
expect(user.metrics.sent_activities).to be_nil
|
131
|
+
User.update_all_metrics!
|
132
|
+
expect(user.metrics.reload.sent_activities).to eq(5)
|
133
|
+
end
|
134
|
+
end
|
54
135
|
end
|
55
136
|
end
|
@@ -4,7 +4,27 @@ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
|
|
4
4
|
|
5
5
|
def create_tables_for(model = :user)
|
6
6
|
ActiveRecord::Migration.create_table "#{model}s", :force => true do |t|
|
7
|
-
t.string
|
7
|
+
t.string :name
|
8
|
+
t.integer :identity_id
|
8
9
|
end
|
9
10
|
ActiveRecord::Migration.create_table "#{model}_metrics", :force => true
|
10
11
|
end
|
12
|
+
|
13
|
+
|
14
|
+
ActiveRecord::Migration.create_table :pets, :force => true do |t|
|
15
|
+
t.string :name
|
16
|
+
t.string :type
|
17
|
+
t.integer :user_id
|
18
|
+
t.integer :age
|
19
|
+
t.integer :weight
|
20
|
+
end
|
21
|
+
|
22
|
+
ActiveRecord::Migration.create_table :identities do |t|
|
23
|
+
t.string :thoughts
|
24
|
+
end
|
25
|
+
|
26
|
+
ActiveRecord::Migration.create_table :activities do |t|
|
27
|
+
t.integer :actor_id
|
28
|
+
t.string :type
|
29
|
+
end
|
30
|
+
|
metadata
CHANGED
@@ -1,55 +1,55 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: has_metrics
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Allan Grant
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-08-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - "<"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0'
|
19
|
+
version: '4.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - "<"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0'
|
26
|
+
version: '4.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: bundler
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
description: Calculate metrics and store them in the DB.
|
@@ -59,7 +59,7 @@ executables: []
|
|
59
59
|
extensions: []
|
60
60
|
extra_rdoc_files: []
|
61
61
|
files:
|
62
|
-
- .gitignore
|
62
|
+
- ".gitignore"
|
63
63
|
- Gemfile
|
64
64
|
- LICENSE
|
65
65
|
- README.md
|
@@ -68,6 +68,7 @@ files:
|
|
68
68
|
- lib/has_metrics.rb
|
69
69
|
- lib/has_metrics/metrics.rb
|
70
70
|
- lib/has_metrics/segmentation.rb
|
71
|
+
- lib/has_metrics/sql_capturer.rb
|
71
72
|
- lib/has_metrics/version.rb
|
72
73
|
- spec/metrics_spec.rb
|
73
74
|
- spec/segmentation_spec.rb
|
@@ -83,17 +84,17 @@ require_paths:
|
|
83
84
|
- lib
|
84
85
|
required_ruby_version: !ruby/object:Gem::Requirement
|
85
86
|
requirements:
|
86
|
-
- -
|
87
|
+
- - ">="
|
87
88
|
- !ruby/object:Gem::Version
|
88
89
|
version: '0'
|
89
90
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
91
|
requirements:
|
91
|
-
- -
|
92
|
+
- - ">="
|
92
93
|
- !ruby/object:Gem::Version
|
93
94
|
version: '0'
|
94
95
|
requirements: []
|
95
96
|
rubyforge_project:
|
96
|
-
rubygems_version: 2.
|
97
|
+
rubygems_version: 2.2.2
|
97
98
|
signing_key:
|
98
99
|
specification_version: 4
|
99
100
|
summary: Calculate "metrics" (any expensive methods) on ActiveRecord entries and memoize
|