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 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