has_metrics 0.0.6 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 290cd9839b6158645e6952a21045665372b5d2fe
4
- data.tar.gz: f26872bf500a652a99816716a8cbfbe3be85afc9
3
+ metadata.gz: f48a7598ad01f84ad88033cbd27f3db579471077
4
+ data.tar.gz: 8f2bc339c0a146c2c787e9d605c9097963c3dc69
5
5
  SHA512:
6
- metadata.gz: 4ab5e73e945e0698c8fd82e620c64be6cb780d5b6c6ef46bce90d2b5539f252487cfa2f3ffb71fc801934c9715fd8dd5ac02255458fd781a337b07e525f6ebcb
7
- data.tar.gz: aa6fb41dfd66fcaf7b541f3861b2237cc7e4d82faf9f0e5a92736cc41b334598ed399e3e0c3004c970686fe3a57c7f3261b52b60d40a351a53f13ffef99a7ddc
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 |gem|
5
- gem.name = "has_metrics"
6
- gem.version = HasMetrics::VERSION
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
- gem.files = `git ls-files`.split($\)
15
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
- gem.require_paths = ["lib"]
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
- gem.add_dependency "activerecord"
20
- gem.add_development_dependency "rake"
21
- gem.add_development_dependency "bundler"
22
- # gem.add_development_dependency "shoulda"
23
- # gem.add_development_dependency "sqlite3"
24
- # gem.add_development_dependency "pry"
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
@@ -25,7 +25,7 @@ module Metrics
25
25
  end
26
26
 
27
27
  def metrics
28
- @metrics ||= self.class.metrics_class.find_or_create_by_id(id)
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
- case
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(&block)
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
- (@metrics ||= []) << name.to_sym
70
- @metrics.uniq!
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
- # start_time = Time.zone.now
105
- # total = all.count
106
- # if caller.find {|c| c =~ /irb_binding/} # When called from irb
107
- # puts "Updating all metrics on #{name}: #{metrics.join(', ')}"
108
- # puts "Updating #{total} records."
109
- # progress_bar = ProgressBar.new("Progress", total)
110
- # end
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
- # puts "Updating record ##{record.id}: #{record}"
115
- record.update_metrics!(*args)
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
- # progress_bar.finish if progress_bar
121
- # elapsed = Time.zone.now - start_time
122
- # Notifier.deliver_simple_message('allan@curebit.com', '[CUREBIT] Metrics computation time', "Finished calculating #{metrics.count} metrics on #{total} #{name.underscore.humanize.downcase.pluralize} in #{elapsed/60} minutes (#{elapsed/total} sec per entry) (#{elapsed/(total*metrics.count)} sec per metric). \n\nMetrics calculated:\n\n#{metrics.join("\n")}")
123
- metrics
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
@@ -1,3 +1,3 @@
1
1
  module HasMetrics
2
- VERSION = "0.0.6"
2
+ VERSION = "0.0.9"
3
3
  end
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
- describe Metrics do
4
- describe "defining metrics" do
5
- let(:user) { User.create(:name => "Fuzz") }
3
+ create_tables_for(:user)
6
4
 
7
- before do
8
- create_tables_for(:user)
5
+ class Pet < ActiveRecord::Base
9
6
 
10
- class User < ActiveRecord::Base
11
- include Metrics
12
- has_metric :name_length do
13
- name.length
14
- end
15
- end
7
+ end
16
8
 
17
- User.update_all_metrics!
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 == 3
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 == 5
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 "name"
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.0.6
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-05-01 00:00:00.000000000 Z
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.0.3
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